如何使用适用于浏览器的 aws-sdk V3 跟踪到 S3 的上传进度 (javascript)

问题描述 投票:0回答:4

我可以在网上找到很多关于如何使用 aws-sdk V2 跟踪上传到 S3 的进度的资源,监听事件如下:

.on('httpUploadProgress', event => {}

但是自从我将 aws-sdk 更新到 V3 后,就不再有监听器了。我相信我现在必须使用中间件功能,但我尝试了一些方法但没有成功。我还深入研究了 API 参考文档github 存储库,但没有成功。

我当前的代码是这样的:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

export const UploadToS3 = (credentials, fileData) => {

    const s3 = new S3Client({
        region: credentials.region,
        credentials: {
            accessKeyId: credentials.access_key,
            secretAccessKey: credentials.secret_key,
            sessionToken: credentials.session_token,
        }
    });

    return new Promise((resolve) => {
        s3.send(new PutObjectCommand({
            Bucket: credentials.bucket,
            Key: credentials.file,
            Body: fileData,
        }));
    });
};

如有任何帮助,我们将不胜感激

javascript amazon-web-services amazon-s3 aws-sdk
4个回答
9
投票

我遇到了完全相同的问题(从 aws-sdk v2 切换到 v3),发现这是因为该库对所有 HTTP 请求使用 Fetch API,并且

Fetch
(尚)不支持跟踪上传进度

为了解决这个问题,我将

Fetch
至少换成了
XMLHttpRequest
请求,您可以通过在初始化 S3Client 时提供自定义
requestHandler
来完成。 PUT

自定义请求处理程序只是从 @aws-sdk/fetch-http-handler 扩展 
import { S3Client } from '@aws-sdk/client-s3'; const myHttpHandler = new MyHttpHandler(); myHttpHandler.onProgress$.subscribe(progress => { const percentComplete = progress.progressEvent.loaded / progress.progressEvent.total * 100; console.log('upload progress', percentComplete); }); const myClient = new S3Client({ endpoint: this.configService.s3Api, region: 'eu', credentials: { ... }, requestHandler: myHttpHandler });

。如果方法是

FetchHttpHandler
并且有一个正文(因此我们想要上传一些内容),它会使用自定义 XHR 处理程序 - 否则它只使用
PUT
类中的
Fetch
处理程序。 在 XHR 处理程序中,您可以将某些内容绑定到 XHR 处理程序的
super
事件 - 在我的例子中,我发出一个 rxjs
progress
,我可以在自定义处理程序之外使用它。
Subject



4
投票
github问题

我刚刚发现import { FetchHttpHandler, FetchHttpHandlerOptions } from '@aws-sdk/fetch-http-handler'; import { HeaderBag, HttpHandlerOptions } from '@aws-sdk/types'; import { buildQueryString } from '@aws-sdk/querystring-builder'; import { HttpResponse, HttpRequest } from '@aws-sdk/protocol-http'; import { Subject } from 'rxjs'; class MyHttpHandler extends FetchHttpHandler { private myRequestTimeout; onProgress$: Subject<{ path: string, progressEvent: ProgressEvent }> = new Subject(); constructor({ requestTimeout }: FetchHttpHandlerOptions = {}) { super({ requestTimeout }); this.myRequestTimeout = requestTimeout; } handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { // we let XHR only handle PUT requests with body (as we want to have progress events here), the rest by fetch if (request.method === 'PUT' && request.body) { return this.handleByXhr(request, { abortSignal }); } return super.handle(request, { abortSignal }); } /** * handles a request by XHR instead of fetch * this is a copy the `handle` method of the `FetchHttpHandler` class of @aws-sdk/fetch-http-handler * replacing the `Fetch`part with XHR */ private handleByXhr(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse}> { const requestTimeoutInMs = this.myRequestTimeout; // if the request was already aborted, prevent doing extra work if (abortSignal?.aborted) { const abortError = new Error('Request aborted'); abortError.name = 'AbortError'; return Promise.reject(abortError); } let path = request.path; if (request.query) { const queryString = buildQueryString(request.query); if (queryString) { path += `?${queryString}`; } } const { port, method } = request; const url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ''}${path}`; // Request constructor doesn't allow GET/HEAD request with body // ref: https://github.com/whatwg/fetch/issues/551 const body = method === 'GET' || method === 'HEAD' ? undefined : request.body; const requestOptions: RequestInit = { body, headers: new Headers(request.headers), method, }; const myXHR = new XMLHttpRequest(); const xhrPromise = new Promise<{headers: string[], body: Blob, status: number}>((resolve, reject) => { try { myXHR.responseType = 'blob'; // bind the events myXHR.onload = progressEvent => { resolve({ body: myXHR.response, headers: myXHR.getAllResponseHeaders().split('\n'), status: myXHR.status }); }; myXHR.onerror = progressEvent => reject(new Error(myXHR.responseText)); myXHR.onabort = progressEvent => { const abortError = new Error('Request aborted'); abortError.name = 'AbortError'; reject(abortError); }; // progress event musst be bound to the `upload` property if (myXHR.upload) { myXHR.upload.onprogress = progressEvent => this.onProgress$.next({ path, progressEvent }); } myXHR.open(requestOptions.method, url); // append headers if (requestOptions.headers) { (requestOptions.headers as Headers).forEach((headerVal, headerKey, headers) => { if (['host', 'content-length'].indexOf(headerKey.toLowerCase()) >= 0) { // avoid "refused to set unsafe header" error message return; } myXHR.setRequestHeader(headerKey, headerVal); }); } myXHR.send(requestOptions.body); } catch (e) { console.error('S3 XHRHandler error', e); reject(e); } }); const raceOfPromises = [ xhrPromise.then((response) => { const fetchHeaders = response.headers; const transformedHeaders: HeaderBag = {}; fetchHeaders.forEach(header => { const name = header.substr(0, header.indexOf(':') + 1); const val = header.substr(header.indexOf(':') + 1); if (name && val) { transformedHeaders[name] = val; } }); const hasReadableStream = response.body !== undefined; // Return the response with buffered body if (!hasReadableStream) { return response.body.text().then(body => ({ response: new HttpResponse({ headers: transformedHeaders, statusCode: response.status, body, }), })); } // Return the response with streaming body return { response: new HttpResponse({ headers: transformedHeaders, statusCode: response.status, body: response.body, }), }; }), this.requestTimeoutFn(requestTimeoutInMs), ]; if (abortSignal) { raceOfPromises.push( new Promise<never>((resolve, reject) => { abortSignal.onabort = () => { myXHR.abort(); }; }) ); } return Promise.race(raceOfPromises); } private requestTimeoutFn(timeoutInMs = 0): Promise<never> { 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); } }); } } 不支持上传进度跟踪,因为它在幕后使用了

@aws-sdk/client-s3
。推荐的方法是使用
fetchHttpHandler
,我还没有尝试过,但看起来很有希望!
    


4
投票

但是现在 JS SDK v3 有一个名为 @aws-sdk/xhr-http-handler 的新包,可以使用它来代替 @aws-sdk/fetch-http-handler,以便在我们使用时获取细粒度的文件上传进度正在进入 v2。

您可以在链接上找到该代码

https://github.com/aws/aws-sdk-js-v3/tree/main/packages/xhr-http-handler

@aws-sdk/lib-storage



0
投票
上面 Jan 的回答

对我来说适用于分段上传,但需要修复一个错误(使用 @aws-sdk/client-s3 的 v3.438.0),因为 PUT 请求的响应标头未正确解析. 我变了

import { S3Client } from '@aws-sdk/client-s3'; import { XhrHttpHandler } from '@aws-sdk/xhr-http-handler'; import { Upload } from '@aws-sdk/lib-storage'; const s3Client = new S3Client({ requestHandler: new XhrHttpHandler({}), }); const upload = new Upload({ client:s3Client, params: { bucket, key, }, }); upload.on("httpUploadProgress", (progress) => { console.log( progress.loaded, // Bytes uploaded so far. progress.total // Total bytes. Divide these two for progress percentage. ); }); await upload.done();

fetchHeaders.forEach((header) => { const name = header.substr(0, header.indexOf(':') + 1); const val = header.substr(header.indexOf(':') + 1); if (name && val) { transformedHeaders[name] = val; } });

© www.soinside.com 2019 - 2024. All rights reserved.