Ionic 6如何编写HTTP请求代码实现Firebase Hosting文件上传
Firebase Hosting REST API 文件上传问题解答
问题1:上传请求体格式说明
上传文件时直接将gzip压缩后的文件二进制内容放入请求体即可,不需要封装为JSON、表单等其他结构化对象。
官方示例中标注的
content-of-file2.gz,指的就是对应文件经gzip压缩后的原始二进制流,没有额外封装格式。需要注意:URL路径中的fileHash参数是压缩前原文件内容的SHA256哈希值,上传的gzip内容解压后必须和计算哈希的原文件完全一致,否则会触发服务端校验失败。
完整的文件上传流程需要按顺序执行,跳过任意步骤都会报错:
- 先创建一个新的Hosting站点版本,拿到对应的versionId
- 调用populateFiles接口,传入所有待上传文件的部署路径和对应SHA256哈希,接口会返回当前版本缺失、需要实际上传的文件列表(已存在于Hosting缓存的文件不需要重复上传)
- 对每个需要上传的文件,将原文件内容gzip压缩后,调用单文件上传接口,请求体直接放压缩后的二进制流
- 所有文件上传完成后,调用版本finalize接口将版本标记为完成,再执行发布操作即可
问题2:Ionic 6 实现方案与代码修正
你原有代码存在以下几个直接导致运行失败的问题:
HttpHeaders是不可变对象,调用set方法会返回新的实例,你没有接收返回值,实际请求根本没带上你设置的头参数- 请求头配置逻辑混乱:单文件上传接口要求
Content-Type为application/octet-stream,你额外设置application/json会导致服务端无法解析请求体 - 作用域错误:你已经在构造函数注入了
private http: HttpClient,发起POST请求时应该调用this.http.post,直接写http.post会找不到实例 - 缺少gzip压缩步骤:直接上传原文件blob会和服务端记录的哈希值不匹配,校验不通过
- 缺少文件哈希计算逻辑:URL中的
fileHash是原文件内容的SHA256值,不是固定占位字符串 - 嵌套订阅属于RxJS反模式,容易造成内存泄漏和逻辑混乱
可直接参考的实现代码
首先安装需要的依赖:
npm install crypto-js pako npm install -D @types/crypto-js
核心上传逻辑代码:
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import * as CryptoJS from 'crypto-js'; import * as pako from 'pako'; import { switchMap } from 'rxjs/operators'; import { from, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class FirebaseHostingService { // 替换为你自己的站点配置 private readonly siteId = '替换为你的Firebase站点ID'; private readonly accessToken = '替换为有Hosting写权限的OAuth2令牌'; constructor(private http: HttpClient) {} /** * 上传单个文件到指定Hosting版本 * @param versionId 已创建的Hosting版本ID * @param targetPath 文件部署到站点的路径,例如/index.html * @param fileBlob 待上传的原文件Blob对象 */ uploadFile(versionId: string, targetPath: string, fileBlob: Blob): Observable<any> { return from(this.readFileAsArrayBuffer(fileBlob)).pipe( switchMap(async (fileBuffer) => { // 基于原文件内容计算SHA256哈希 const wordArray = CryptoJS.lib.WordArray.create(fileBuffer as any); const fileHash = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Hex); // 对原文件做gzip压缩 const gzippedContent = pako.gzip(new Uint8Array(fileBuffer)); return { fileHash, gzippedContent }; }), switchMap(({ fileHash, gzippedContent }) => { // 配置正确的请求头 const headers = new HttpHeaders({ 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/octet-stream' }); // 发起上传请求,请求体直接传入gzip后的二进制内容 const uploadUrl = `https://upload-firebasehosting.googleapis.com/upload/sites/${this.siteId}/versions/${versionId}/files/${fileHash}`; return this.http.post(uploadUrl, gzippedContent, { headers }); }) ); } /** * 工具方法:将Blob读取为ArrayBuffer */ private readFileAsArrayBuffer(blob: Blob): Promise<ArrayBuffer> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as ArrayBuffer); reader.onerror = reject; reader.readAsArrayBuffer(blob); }); } // 读取本地assets文件并上传的示例 uploadLocalJsonExample(versionId: string) { this.http.get("./assets/file.json", { responseType: "blob" }) .pipe(switchMap(blob => this.uploadFile(versionId, '/file.json', blob))) .subscribe({ next: res => console.log('上传成功', res), error: err => console.error('上传失败', err) }); } }
额外注意事项
- 使用的OAuth2令牌必须申请到Firebase Hosting的写权限,否则会返回403错误
- 不要跳过populateFiles步骤直接上传,否则服务端会返回哈希不存在的错误
- 批量上传时,先一次性收集所有文件的路径和哈希,调用一次populateFiles接口,再上传返回列表里要求上传的文件即可,不需要每个文件都调用一次populateFiles
内容的提问来源于stack exchange,提问作者Bdna Thirysix
相关产品推荐
相关产品推荐

