微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Angular - 文件下载的最佳实践

如何解决Angular - 文件下载的最佳实践

对于在我正在开发的应用程序上启动文件下载的最佳策略是什么,我有点困惑。后端(django RESTful API)和前端(angular 9)都可以进行修改,以最好的方式实现这一点。

最低背景: 应用程序需要允许用户下载文件。目前,后端允许对其存储文件的方式进行多种配置——文件可以存储在 API 主机上,也可以存储在存储桶中(此处为谷歌云,但这应该无关紧要)。

显然,客户不应该关心如何存储这些文件。他们应该能够转到像 https://example.com/api/files/<file UUID>/download/ 这样的端点开始下载。 正如目前所写,当后端收到请求时,它会根据使用的存储类型做出不同的响应。如果文件存储在服务器上,它只用文件内容响应:

def get(self,request,*args,**kwargs):
  ...
  contents = open(filepath,'rb')
  mime_type,_ = mimetypes.guess_type(filepath)
  response = HttpResponse(content = contents)
  response['Content-Type'] = mime_type
  response['Content-disposition'] = 'attachment; filename=%s' % os.path.basename(filepath)
  return response

另一方面,如果文件存储在存储桶中,我会使用谷歌云 SDK 创建一个签名 URL,并返回一个 302,将该签名 URL 作为重定向位置。

如果我直接与 API 交互,那一切都很好。一旦我涉及 Angular,我不知道如何将其连接起来。理想情况下,UI 应该只是一个按钮——用户点击并开始下载。

基于查看不同的来源(特别是 Angular 6 Downloading file from rest api),我想我可以通过创建自定义 angular 指令来尝试这个。例如,HTML 有:

<a fileDownload [resourceId]="row.id">
    <button [disabled]="!row.is_active">
        Download
    </button>
</a>

fileDownload 指令(部分)类似于:

@Directive({
    selector: '[fileDownload]',exportAs: 'fileDownload',})
export class FileDownloadDirective implements OnDestroy {

    @input() resourceId: string;
    private destroy$: Subject<void> = new Subject<void>();

    constructor(private ref: ElementRef,private fileService: FileService) {}

    @HostListener('click')
    onClick(): void {
        this.fileService.downloadFile(this.resourceId)
            .subscribe(x => {
                // what should go here????
            });
    }

    ngOnDestroy(): void {
        this.destroy$.next();
        this.destroy$.complete();
    }
}

而我的 fileService 方法是:

  downloadFile(id: number | string): Observable<any> {
    return this.httpClient.get(`${this.API_URL}/resources/download/${id}/`);
  }

正如我在上面的代码中所评论的那样,我不确定订阅时与响应有什么关系(如果有的话)。我在引用的 SO 线程中尝试了该版本,但无论我在订阅中做什么,我都会收到 CORS 错误。我在那里放了一些 console.log 语句,但它们没有出现,所以我什至没有进入 subscribe 回调。

无论如何,浏览器都会拦截302,然后立即尝试跟随重定向。这会导致 CORS 失败,我似乎无法修复。基于此 https://github.com/googleapis/python-storage/issues/3 ,我查看了预检选项请求。由于没有 CORS Access-Control-Allow-Origin 标头,浏览器的检查器显示此失败。请求是:

OPTIONS /my-bucket/...<snip>...
Host: storage.googleapis.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip,deflate,br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Referer: http://example.com:4200/
Origin: http://example.com:4200
Connection: keep-alive

和响应

HTTP/2 200 OK
x-guploader-uploadid: <...>
date: Sat,20 Mar 2021 12:22:03 GMT
expires: Sat,20 Mar 2021 12:22:03 GMT
cache-control: private,max-age=0
content-length: 0
server: UploadServer
content-type: text/html; charset=UTF-8
alt-svc: h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
X-Firefox-Spdy: h2

值得注意的是,没有 Access-Control-Allow-Origin 标头,浏览器不会继续处理对文件的实际 GET 请求。

使用 cURL,我可以复制 OPTIONS 请求不返回 Access-Control-Allow-Origin 标头。签名的 URL 有效,因为我可以通过 GET 请求使用 cURL 下载文件

这是网络活动:

enter image description here

据我所知,我相信我的存储桶设置正确:

$ gsutil cors get gs://my-bucket
[{"maxAgeSeconds": 3600,"method": ["GET"],"origin": ["*"],"responseHeader": ["Content-Type","Access-Control-Allow-Origin"]}]

有没有更好的方法来做到这一点?我想我可以编写一个 HttpInterceptor 来捕获 302 并修改请求。或者最好不要让 API 以 302 响应?由于我告诉它从 google 存储桶存储中获取文件,这似乎是最理想的响应,因为我有效地重定向了请求。

感谢您的任何帮助/建议!

解决方法

可能是 fileService 方法中的请求不正确。检查 documentation,向 Cloud Storage API 发出下载文件的请求:

https://storage.googleapis.com/download/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?alt=media

用适当的值替换 BUCKET_NAMEOBJECT_NAME

此外,您可能会发现这个 NodeJS sample code 很有用,它演示了如何从 Cloud Storage 下载文件:


// The ID of your GCS bucket 
// const bucketName = 'your-unique-bucket-name';

// The ID of your GCS file 
// const fileName = 'your-file-name';

// The path to which the file should be downloaded 
// const destFileName = '/local/path/to/file.txt';

// Imports the Google Cloud client library 
const {Storage} = require('@google-cloud/storage');

// Creates a client 
const storage = new Storage();

async function downloadFile() {   
  const options = {
    destination: destFileName,};

  // Downloads the file   
  await storage.bucket(bucketName).file(fileName).download(options);

  console.log(
    `gs://${bucketName}/${fileName} downloaded to ${destFileName}.`
  );
}

downloadFile().catch(console.error);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。