如何向Google存储桶预签名URL上传文件?签名报错求助
错误原因分析
x-goog-content-length-range头部误用
该头部的作用是限制整个上传文件的大小范围(例如0,16383表示仅允许上传0-16383字节的文件),而不是标记分块的位置。你把它当成Content-Range来传递分块起止位置,完全不符合GCS的规范,直接触发了大小校验和签名验证失败。请求包含未签名头部
预签名URL的X-Goog-SignedHeaders明确指定仅对host、x-goog-content-length-range、x-goog-hash这三个头部签名,但你额外添加了Content-Length头部——这个头部不在签名范围内,GCS会直接拒绝请求,返回签名验证错误。分块上传方式错误
GCS的分块(断点续传)上传需要依赖可恢复上传会话,而非对同一个预签名URL重复发起PUT请求。多次PUT同一个预签名URL只会覆盖之前的内容,无法实现分块拼接。
解决方案
方案1:直接上传整个文件(适配当前预签名URL)
API返回的预签名URL大概率是用于单文件上传,而非分块。直接将整个文件内容PUT到该URL即可:
def calculate_crc32c(data: bytes): return base64.b64encode(crc32c(data).to_bytes(4, 'big')).decode('utf-8') def put_file(api: 'API', file: 'File', data: bytes): checksum_bs4 = calculate_crc32c(data) content_length = len(data) with httpx.Client(http2=False) as request: response = request.put( FILES_URL.format(api.document_storage_uri, file.hash), content=data, headers=(headers := { **api.session.headers, 'content-length': str(content_length), 'content-type': 'application/octet-stream', 'x-goog-hash': f'crc32c={checksum_bs4}', }) ) if response.status_code == 302: print("Full google upload detected, continuing") url = response.headers.get("Location") print(url) with httpx.Client(http2=False) as request: # 直接上传整个文件,移除分块逻辑 response = request.put( url, content=data, headers={ 'host': 'storage.googleapis.com', 'x-goog-hash': f'crc32c={checksum_bs4}', } ) if response.status_code != 200: raise Exception(f"Put file failed - {response.status_code}\n{response.text}") else: print(file.uuid, "uploaded")
方案2:使用可恢复分块上传(需API支持)
如果必须分块上传,需要确认API返回的是可恢复上传预签名URL,然后按照GCS规范流程操作:
- 初始化会话:向预签名URL发起PUT请求,携带
X-Goog-Resumable: start和Content-Length: 0,获取会话URL。 - 上传分块:使用会话URL,每个分块请求携带
Content-Range: bytes start-end/total头部,以及分块的CRC32C值。
关键注意事项
- 预签名URL有效期仅59秒,大文件上传需要求API返回更长有效期的URL,或采用可恢复上传。
- 确保
crc32c计算正确:当前代码的大端字节序处理符合GCS要求,无需修改。
内容的提问来源于stack exchange,提问作者RedTTG
相关产品推荐
相关产品推荐

