Что я пытаюсь сделать
Загрузите файл PDF из браузера клиента, не раскрывая учетные данные или что-либо сомнительное. Основываясь на этом , я думал, что это можно сделать, но мне кажется, это не сработает.
Помещение:
вы запрашиваете предварительно подписанный URL-адрес в S3 Bucket на основе набора параметров, предоставленных функции, являющейся частью JavaScript AWS SDK
вы передаете этот URL-адрес во внешний интерфейс, который может использовать его для размещения файла в S3 Bucket без необходимости использования каких-либо учетных данных или аутентификации на внешнем интерфейсе.
ПОЛУЧИТЕ предварительно подписанный URL от S3
Эта часть проста и у меня работает. Я просто запрашиваю URL у S3 с этим маленьким слепком JS:
const s3Params = {
Bucket: uploadBucket,
Key: `${fileId}.pdf`,
ContentType: 'application/pdf',
Expires: 60,
ACL: 'public-read',
}
let uploadUrl = s3.getSignedUrl('putObject', s3Params);
Используйте предварительно подписанный URL для загрузки файла на S3
Эта часть не работает, и я не могу понять, почему.
Этот небольшой фрагмент кода в основном отправляет большой объем данных на предварительно подписанный URL-адрес корзины S3 с помощью запроса PUT.
const result = await fetch(response.data.uploadURL, {
method: 'put',
body: blobData,
});
PUT или POST?
Я обнаружил, что использование любых запросов POST приводит к 400 Bad Request
, поэтому PUT это так.
Что я смотрел
Content-Type (в моем случае это будет application / pdf, поэтому blobData.type
) - они совпадают между внутренним и внешним интерфейсом.
x-amz-acl header
Тип контента
Аналогичный вариант использования . Глядя на это, кажется, что в запросе PUT не нужно указывать заголовки, а сам подписанный URL-адрес - это все, что необходимо для загрузки файла.
Что-то странное , что я не понимаю. Похоже, мне может понадобиться передать длину и тип файла в getSignedUrl
вызов S3.
Раскрытие моего ведра для публики (без буэно)
Загрузить файл на s3 с POST
Внешний интерфейс (fileUploader.js, использующий Vue):
...
uploadFile: async function(e) {
/* receives file from simple input element -> this.file */
// get signed URL
const response = await axios({
method: 'get',
url: API_GATEWAY_URL
});
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 blobData = new Blob([new Uint8Array(array)], {type: 'application/pdf'});
console.log('uploading to:', response.data.uploadURL);
console.log('blob type sanity check:', blobData.type);
const result = await fetch(response.data.uploadURL, {
method: 'put',
headers: {
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Origin': '*',
'x-amz-acl': 'public-read',
'Content-Type': blobData.type
},
body: blobData,
});
console.log('PUT result:', result);
this.uploadUrl = response.data.uploadURL.split('?')[0];
}
Backend (fileReceiver.js):
'use strict';
const uuidv4 = require('uuid/v4');
const aws = require('aws-sdk');
const s3 = new aws.S3();
const uploadBucket = 'the-chumiest-bucket';
const fileKeyPrefix = 'path/to/where/the/file/should/live/';
const getUploadUrl = async () => {
const fileId = uuidv4();
const s3Params = {
Bucket: uploadBucket,
Key: `${fileId}.pdf`,
ContentType: 'application/pdf',
Expires: 60,
ACL: 'public-read',
}
return new Promise((resolve, reject) => {
let uploadUrl = s3.getSignedUrl('putObject', s3Params);
resolve({
'statusCode': 200,
'isBase64Encoded': false,
'headers': {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Credentials': true,
},
'body': JSON.stringify({
'uploadURL': uploadUrl,
'filename': `${fileId}.pdf`
})
});
});
};
exports.handler = async (event, context) => {
console.log('event:', event);
const result = await getUploadUrl();
console.log('result:', result);
return result;
}
Конфигурация без сервера (serverless.yml):
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
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"
Ошибка:
403 Запрещено
PUT Response
Response {тип: "cors", URL: "https://{bucket -name} .s3. {Region-id} .amazonaw… nedHeaders = хост% 3Bx-amz-acl & x-amz-acl = public-read", перенаправлено: false, статус: 403, ok: false,…}
тело: (...)
bodyUsed: false
Заголовки: Заголовки {}
хорошо: ложь
перенаправлено: нет
статус: 403
statusText: «Запрещено»
Тип: "Корс"
url: "https://{bucket -name} .s3. {region-id} .amazonaws.com / actionID.pdf? Content-Type = application% 2Fpdf & X-Amz-Algorithm = SHA256 & X-Amz-Credential = CREDZ- & X-Amz- Date = 20190621T192558Z & X-Amz-Истекает = 900 & X-Amz-Security-токен = {маркер} и X-Amz-SignedHeaders = хост% 3BX-АМЗ-х и ACL-АМЗ-= ACL общественного чтения»
proto : Response
Что я думаю
Я думаю, что параметры, предоставляемые для вызова getSignedUrl
с использованием AWS S3 SDK, не верны, хотя они соответствуют структуре, предложенной в документации AWS (объяснение здесь ). Кроме того, я действительно не понимаю, почему мой запрос отклонен. Я даже пытался полностью раскрыть свой Bucket публике, и он все еще не работал.
Редактировать
# 1:
После прочтения этого я попытался структурировать свой запрос PUT следующим образом:
let authFromGet = response.config.headers.Authorization;
const putHeaders = {
'Authorization': authFromGet,
'Content-Type': blobData,
'Expect': '100-continue',
};
...
const result = await fetch(response.data.uploadURL, {
method: 'put',
headers: putHeaders,
body: blobData,
});
это привело к 400 Bad Request
вместо 403; разные, но все же неправильно. Очевидно, что указывать заголовки в запросе неправильно.