简体   繁体   English

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

[英]How to track upload progress to S3 using aws-sdk V3 for browser (javascript)

I can find a lot of resources online on how to track upload progress to S3 using aws-sdk V2, listening to the event like:我可以在网上找到很多关于如何使用 aws-sdk V2 跟踪上传到 S3 的进度的资源,例如:

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

But since I updated the aws-sdk to V3, there are no listeners anymore.但是自从我将 aws-sdk 更新到 V3 后,就不再有监听器了。 I believe I have to use the middleware function now, but I've tried a few things and it didn't work.我相信我现在必须使用中间件 function,但是我尝试了一些东西,但没有成功。 I've also went deep into the API reference docs and the github repository without success.我还深入研究了API 参考文档github 存储库,但没有成功。

My current code is like this:我当前的代码是这样的:

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,
        }));
    });
};

Any help would be appreciated任何帮助,将不胜感激

I had exactly the same problem (switched from aws-sdk v2 to v3) and found out that it is because the library uses the Fetch API for all HTTP Requests and Fetch does not (yet) support tracking upload progress我遇到了完全相同的问题(从 aws-sdk v2 切换到 v3),发现这是因为库对所有 HTTP 请求使用Fetch API 并且Fetch 不(还)支持跟踪上传进度

To solve that problem I exchanged Fetch by good old XMLHttpRequest at least for PUT requests, which you can accomplish by providing a custom requestHandler when initializing the S3Client.为了解决这个问题,我至少为PUT请求交换了旧的XMLHttpRequestFetch ,您可以通过在初始化 S3Client 时提供自定义requestHandler来完成。

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
});

The custom request handler simply extends the FetchHttpHandler from @aws-sdk/fetch-http-handler.自定义请求处理程序只是从 @aws-sdk/fetch-http-handler 扩展FetchHttpHandler If the method is PUT and there is a body (so we want to upload something), it uses a custom XHR handler - otherwise it just uses the Fetch handler from it's super class.如果方法是PUT并且有一个主体(所以我们想上传一些东西),它使用自定义 XHR 处理程序 - 否则它只使用来自它的super class 的Fetch处理程序。 And in the XHR handler you can bind something to the progress event of the XHR handler - in my case I emit a rxjs Subject which I can consume outside the custom handler.在 XHR 处理程序中,您可以将某些内容绑定到 XHR 处理程序的progress事件 - 在我的情况下,我发出一个 rxjs Subject ,我可以在自定义处理程序之外使用它。

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);
      }
    });
  }
}

Looking through the github issues I've just found that @aws-sdk/client-s3 don't support upload progress tracking, since it uses fetchHttpHandler under the covers.查看github 问题,我刚刚发现@aws-sdk/client-s3不支持上传进度跟踪,因为它在后台使用了fetchHttpHandler The recommended way is to use @aws-sdk/lib-storage which I have not tried yet, but looks promising!推荐的方法是使用我还没有尝试过@aws-sdk/lib-storage ,但看起来很有希望!

I have also faced the same issue that is i have to upgrade aws-sdk from v2 to v3 but file upload progress feature is missing in v3.The reason for that is in JS SDK v2 for S3 file upload they uses XHR for browser network requests, which has a good interface for progress tracking.我也遇到了同样的问题,我必须将 aws-sdk 从 v2 升级到 v3,但 v3 中缺少文件上传进度功能。原因在于 JS SDK v2 用于 S3 文件上传,他们使用 XHR 进行浏览器网络请求,它具有良好的进度跟踪界面。 Whereas JS SDK v3 uses fetch for the same, which offers some other advantages but one downside is that it does not seem to support upload progress yet, and progress on that feature is extremely slow.而 JS SDK v3 也同样使用 fetch,它提供了一些其他优点,但一个缺点是它似乎还不支持上传进度,并且该功能的进展非常缓慢。

But now JS SDK v3 has a new package called @aws-sdk/xhr-http-handler which can be used in place of @aws-sdk/fetch-http-handler in order to get the fine grained file upload progress as we are getting in v2.但是现在 JS SDK v3 有一个名为 @aws-sdk/xhr-http-handler 的新 package 可以用来代替 @aws-sdk/fetch-http-handler 以获得细粒度的文件上传进度进入 v2。

You can find out the code for that on the link https://github.com/aws/aws-sdk-js-v3/tree/main/packages/xhr-http-handler您可以在链接https://github.com/aws/aws-sdk-js-v3/tree/main/packages/xhr-http-handler

       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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 AWS S3:缺少凭证 (aws-sdk v3) - AWS S3: Credentials missing (aws-sdk v3) 如何实际跟踪 S3 上传进度(JavaScript SDK) - How to actually track S3 upload progress (JavaScript SDK) S3:如何使用aws-sdk在nodejs中使用S3上传大文件 - S3 : How to upload a large file using S3 in nodejs using aws-sdk S3 为使用 aws-sdk v3 预签名的 PutObject 命令 url 提供 SignatureDoesNotMatch 错误 - S3 gives SignatureDoesNotMatch error for PutObject command pre-signed url using aws-sdk v3 如何使用 React 和使用 aws-sdk 预签名的 axios 将文件上传到 S3 存储桶? - How to upload a file to S3 bucket with axios using React and a presigned using aws-sdk? 使用适用于 JavaScript 的 AWS 开发工具包 v3 完成多部分上传到 S3 的 XML 错误 - XML error completing a multi-part upload to S3 with AWS SDK for JavaScript v3 获取 AWS S3 上传 URL - NodeJs AWS-SDK - Get AWS S3 Upload URL - NodeJs AWS-SDK 如何在浏览器中使用 javascript sdk 在 aws s3 存储桶中上传多部分文件 - How to upload multipart files in aws s3 bucket using javascript sdk in browser 如何通过NodeJS将文件上传到S3并跟踪浏览器的进度? - How to upload a file to S3 via NodeJS and track progress in the browser? 如何在 aws-sdk v3 中调用 lambda 函数 - How to invoke a lambda function in aws-sdk v3
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM