amplify-s3-chunk-upload
Version:
A custom storage upload plugin for AWS Amplify. Instead of reading file completely in memory, it helps to read file chunk by chunk.
113 lines (112 loc) • 5.12 kB
JavaScript
/*
* Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AxiosHttpHandler = exports.SEND_PROGRESS_EVENT = void 0;
const protocol_http_1 = require("@aws-sdk/protocol-http");
const querystring_builder_1 = require("@aws-sdk/querystring-builder");
const axios_1 = require("axios");
const core_1 = require("@aws-amplify/core");
const logger = new core_1.ConsoleLogger('axios-http-handler');
exports.SEND_PROGRESS_EVENT = 'sendProgress';
class AxiosHttpHandler {
constructor(httpOptions = {}, emitter) {
this.httpOptions = httpOptions;
this.emitter = emitter;
}
destroy() {
// Do nothing. TLS and HTTP/2 connection pooling is handled by the
// browser.
}
handle(request, options) {
const requestTimeoutInMs = this.httpOptions.requestTimeout;
const emitter = this.emitter;
let path = request.path;
if (request.query) {
const queryString = querystring_builder_1.buildQueryString(request.query);
if (queryString) {
path += `?${queryString}`;
}
}
const port = request.port;
const url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ''}${path}`;
const axiosRequest = {};
axiosRequest.url = url;
axiosRequest.method = request.method;
axiosRequest.headers = request.headers;
// The host header is automatically added by the browser and adding it explicitly in the
// axios request throws an error https://github.com/aws-amplify/amplify-js/issues/5376
// This is because the host header is a forbidden header for the http client to set
// see https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name and
// https://fetch.spec.whatwg.org/#forbidden-header-name
// The reason we are removing this header here instead of in the aws-sdk's client
// middleware is that the host header is required to be in the request signature and if
// we remove it from the middlewares, then the request fails because the header is added
// by the browser but is absent from the signature.
delete axiosRequest.headers['host'];
if (request.body) {
axiosRequest.data = request.body;
}
else {
// Fix for https://github.com/aws-amplify/amplify-js/issues/5432
// If the POST request body is empty but content-type header is set, axios is forcibly removing it
// See https://github.com/axios/axios/issues/1535 and refusing to fix it https://github.com/axios/axios/issues/755
// This change is a workaround to set the data as null (instead of undefined) to prevent axios from
// removing the content-type header. Link for the source code
// https://github.com/axios/axios/blob/dc4bc49673943e35280e5df831f5c3d0347a9393/lib/adapters/xhr.js#L121-L123
if (axiosRequest.headers['Content-Type']) {
axiosRequest.data = null;
}
}
if (emitter) {
axiosRequest.onUploadProgress = function (event) {
emitter.emit(exports.SEND_PROGRESS_EVENT, event);
logger.debug(event);
};
}
// From gamma release, aws-sdk now expects all response type to be of blob or streams
axiosRequest.responseType = 'blob';
const raceOfPromises = [
axios_1.default
.request(axiosRequest)
.then(response => {
return {
response: new protocol_http_1.HttpResponse({
headers: response.headers,
statusCode: response.status,
body: response.data,
}),
};
})
.catch(error => {
// Error
logger.error(error);
throw error;
}),
requestTimeout(requestTimeoutInMs),
];
return Promise.race(raceOfPromises);
}
}
exports.AxiosHttpHandler = AxiosHttpHandler;
function requestTimeout(timeoutInMs = 0) {
return new Promise((resolve, reject) => {
if (timeoutInMs) {
setTimeout(() => {
const timeoutError = new Error(`Request did not complete within ${timeoutInMs} ms`);
timeoutError.name = 'TimeoutError';
reject(timeoutError);
}, timeoutInMs);
}
});
}
;