这是我尝试将文件从浏览器上传到 S3 存储桶的旅程的第二部分。查看第一部分。
将文件 (PDF) 从我的浏览器客户端(Vue/Nuxt 应用程序)上传到 S3 存储桶。
Nuxt、Vue、Axios、Fetch。
JS,无服务器框架,可轻松部署 AWS。
权限很好,除非有一些我不知道的关于发布到 S3 存储桶的权限。 S3 存储桶设置为允许 CORS;所有的方法,所有的起源。 不,此处显示的 Lambda 没有不正确的权限;如果是这样,我将无法成功获取预签名 URL。
基于 this 和 this,我认为我可以通过首先从下面所示的后端代码检索预签名的 URL(称为
s3.getSignedUrl('putObject')
)将 PDF 放入(或发布)到 S3 存储桶
。我能够轻松地将此 URL 获取到前端,但将 PDF 放置(或发布)到返回的 URL 不起作用。
我在第一部分所做的事情被证明是无用的,而且我一直无法找到原因的答案,所以我决定尝试另一个名为
createPresignedPost
的 JS AWS SDK 函数。
我收到的不是 403 错误,而是 412 错误。阅读后,我似乎没有将正确的选项传递到 POST 请求中。看了这个之后,我如下所示设置了我的请求,但仍然收到错误。
const getUploadUrl = async () => {
const fileId = uuidv4();
let params = {
Bucket: 'the-chumiest-bucket',
ContentType: 'application/pdf',
Conditions: [
{'bucket': 'the-chumiest-bucket'}, // added to both places
['starts-with', '$key', 'path/to/where/the/file/should/go/'],
{'acl': 'public-read'},
{'success_action_status': '200'},
['content-length-range', 1, 1024 * 1024 * 15],
['starts-with', '$Content-Type', 'application/pdf'],
]
};
return new Promise((resolve, reject) => {
s3.createPresignedPost(params, function(err, data) {
if (err) {
console.error('Presigning post data encountered an error', err);
reject(err);
} else {
//data.Fields.key = 'path/to/uploads/${filename}';
console.log('The post data is', data);
resolve({
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Credentials': true,
'Content-Type': 'application/pdf',
},
'body': JSON.stringify({
'data': data,
}),
});
}
});
});
};
uploadFile: async function(e) {
const response = await axios({
method: 'get',
url: API_GATEWAY_URL,
data: {
'fileName': this.fileNames[0],
'contentType': this.file.type || 'application/pdf',
'fileLength': this.file.length,
},
body: {
'fileName': this.fileNames[0],
'contentType': this.file.type || 'application/pdf',
'fileLength': this.file.length,
},
});
console.log('upload file response:', response);
let binary = atob(this.file.split(',')[1]);
let array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
let fields = response.data.data.fields;
let blobData = new Blob([new Uint8Array(array)], {type: 'application/pdf'});
if (blobData.type != 'application/pdf') {
console.error('Filetype is wrong! Upload a PFD or youre dead to me');
}
console.log(`Uploading "${this.fileNames[0]}" to bucket "${response.data.data.fields.bucket}"`);
const postHeaders = {
'Policy': fields.Policy,
'X-Amz-Algorithm': fields['X-Amz-Algorithm'],
'X-Amz-Credential': fields['X-Amz-Credential'],
'X-Amz-Security-Token': fields['X-Amz-Security-Token'],
'X-Amz-Signature': fields['X-Amz-Signature'],
'Content-Type': blobData.type || 'application/pdf',
'acl': 'public-read',
'key': 'path/to/where/the/file/should/go/' + this.fileNames[0],
}
const result = await fetch(response.data.data.url, {
method: 'post',
body: blobData,
headers: postHeaders,
});
this.uploadUrl = response.data.uploadURL.split('?')[0];
},
service: ocr-space-service
provider:
name: aws
region: ca-central-1
stage: ${opt:stage, 'dev'}
timeout: 20
plugins:
- serverless-plugin-existing-s3
- serverless-step-functions
- serverless-pseudo-parameters
- serverless-plugin-include-dependencies
layers:
spaceOcrLayer:
package:
artifact: spaceOcrLayer.zip
allowedAccounts:
- "*"
functions:
fileReceiver:
handler: src/node/fileReceiver.handler
role:
events:
- http:
path: /doc-parser/get-url
method: get
cors: true
startStateMachine:
handler: src/start_state_machine.lambda_handler
role:
runtime: python3.7
layers:
- {Ref: SpaceOcrLayerLambdaLayer}
events:
- existingS3:
bucket: ingenio-documents
events:
- s3:ObjectCreated:*
rules:
- prefix:
- suffix: .pdf
startOcrSpaceProcess:
handler: src/start_ocr_space.lambda_handler
role:
runtime: python3.7
layers:
- {Ref: SpaceOcrLayerLambdaLayer}
parseOcrSpaceOutput:
handler: src/parse_ocr_space_output.lambda_handler
role:
runtime: python3.7
layers:
- {Ref: SpaceOcrLayerLambdaLayer}
renamePdf:
handler: src/rename_pdf.lambda_handler
role:
runtime: python3.7
layers:
- {Ref: SpaceOcrLayerLambdaLayer}
parseCorpSearchOutput:
handler: src/node/pdfParser.handler
role:
runtime: nodejs10.x
saveFileToProcessed:
handler: src/node/saveFileToProcessed.handler
role:
runtime: nodejs10.x
stepFunctions:
stateMachines:
ocrSpaceStepFunc:
name: ocrSpaceStepFunc
definition:
StartAt: StartOcrSpaceProcess
States:
StartOcrSpaceProcess:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-startOcrSpaceProcess"
Next: IsDocCorpSearchChoice
Catch:
- ErrorEquals: ["HandledError"]
Next: HandledErrorFallback
IsDocCorpSearchChoice:
Type: Choice
Choices:
- Variable: $.docIsCorpSearch
NumericEquals: 1
Next: ParseCorpSearchOutput
- Variable: $.docIsCorpSearch
NumericEquals: 0
Next: ParseOcrSpaceOutput
ParseCorpSearchOutput:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-parseCorpSearchOutput"
Next: SaveFileToProcessed
Catch:
- ErrorEquals: ["SqsMessageError"]
Next: CorpSearchSqsErrorFallback
- ErrorEquals: ["DownloadFileError"]
Next: CorpSearchDownloadFileErrorFallback
- ErrorEquals: ["HandledError"]
Next: HandledNodeErrorFallback
SaveFileToProcessed:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-saveFileToProcessed"
End: true
ParseOcrSpaceOutput:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-parseOcrSpaceOutput"
Next: RenamePdf
Catch:
- ErrorEquals: ["HandledError"]
Next: HandledErrorFallback
RenamePdf:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-renamePdf"
End: true
Catch:
- ErrorEquals: ["HandledError"]
Next: HandledErrorFallback
- ErrorEquals: ["AccessDeniedException"]
Next: AccessDeniedFallback
AccessDeniedFallback:
Type: Fail
Cause: "Access was denied for copying an S3 object"
HandledErrorFallback:
Type: Fail
Cause: "HandledError occurred"
CorpSearchSqsErrorFallback:
Type: Fail
Cause: "SQS Message send action resulted in error"
CorpSearchDownloadFileErrorFallback:
Type: Fail
Cause: "Downloading file from S3 resulted in error"
HandledNodeErrorFallback:
Type: Fail
Cause: "HandledError occurred"
POST https://s3.{regionName}.amazonaws.com/{bucketName} 412(前提条件失败)
您指定的至少一个前提条件不满足Bucket POST 必须是封装类型 multipart/form-data934186E69EF6F90EAPU1d8pkKL3XxXjZ8T1oXkWuDRECAPZROklZbHBv+lmNRv/ivoLO/8BhoS8QYXA98850RhrGwhI=PreconditionFailed
看起来请求正在被 412 处理,因为它期待
multipart/form-data
,而不是我指定的 application/pdf
我编写了一个函数,将 PDF 转换为 Base64 并将其作为
FormData
对象的一部分上传。
convertToBase64(file) {
if (file.length > 0) {
let fileToLoad = file[0];
let fileReader = new FileReader();
let base64;
fileReader.onload = (fileLoadedEvent) => {
console.log('fileLoadedEvent', fileLoadedEvent);
base64 = fileLoadedEvent.target.result;
console.log('base64', base64);
};
fileReader.readAsDataURL(fileToLoad);
} else {
console.log('file length was 0');
}
},
...
let formData = new FormData();
formData.append('pdfFile', this.convertToBase64(this.file));
// upload code from above...
还是没有爱!不确定我想做的事情是否可能。
我也遇到过这个问题,但已经解决了。
具体方法如下。
首先,这是始终有效的设置:
我有一个 nextjs 应用程序,它是后端 API 和带有测试按钮/功能的索引页面的组合,用于手动测试 API。
createSignedPost
并将签名发送给客户端。formData
将签名和浏览器选择的文件结合起来,然后使用内置的 fetch()
将文件POST
发送到 AWS。我决定编写一个基于节点的 cli 来模拟浏览器并运行所有测试。
对于 cli,我使用 node-fetch-cookies 来跟踪 cookie,模拟登录并向 API 发送登录请求。
注意:
node-fetch-cookies
使用 node-fetch
进行异步 http(s) 调用。
使用
node-fetch-cookies
我尝试使用 formData
并传入文件的完整路径(失败),并使用 fs.createReadStream()
作为文件(也失败)。
什么有效:
我必须更换
node-fetch-cookies
fetch()
调用并使用内置节点 fetch()
调用。
这是演示其工作原理的伪代码:
const api = new API();
const file = fs.createReadStream("/path/to/file");
api
.getSignedUrl()
.then(async (signature) => {
const { url, fields } = signature;
const formData = new FormData();
Object.entries({ ...fields, file }).forEach(([key, value]) => {
formData.append(key, value);
});
// built-in node fetch()
return fetch(url, {
method: "POST",
body: formData,
});
})
.then((response) => {
if (!response.ok) {
throw new Error(`Upload failed ${response.statusText}`);
}
console.log("file uploaded");
});
奖金:
如果您需要在浏览器中执行此操作,只需更换
file
即可使用用户选择的文件。
具体方法如下。
代替:
const file = fs.createReadStream("/path/to/file");
有一个像这样的表单元素:
<input type="file" name="myFile" onChange={(e) => documentGetLocalFile(e)} />
和这样的处理程序:
var file;
documentGetLocalFile(event) {
if (event.target.files && event.target.files[0]) {
file = event.target.files[0];
}
}
然后确保您的
formData
使用此 file
代替您上面删除的 fs.createReadStream()
文件。