Starlette如何正确提供预压缩gz文件并规避修改request.url属性报错
Starlette返回预压缩.gz静态资源的正确实现方案
问题原因说明
- 首先
AttributeError: Can't set attribute报错是因为Starlette的URL对象是不可变类型,设计层面禁止直接修改属性,且修改请求路径的思路本身不符合Starlette的设计逻辑,不需要效仿Express的实现方式。 - 直接返回.gz文件内容浏览器无法识别,是因为响应头缺少两个必要字段:
- 没有
Content-Encoding: gzip声明内容是gzip压缩格式 - 直接复用S3返回的头会导致
Content-Type被设置为application/gzip之类的压缩文件类型,而非原资源的text/css、application/javascript等正确MIME类型
- 没有
正确实现方案
你不需要修改请求路径,只需要在返回响应的时候手动修正响应头即可,修改后的代码如下:
import mimetypes async def theme(request): try: file_path = request.path_params.get('file_path') # 提前获取原文件的MIME类型 content_type, _ = mimetypes.guess_type(file_path) is_asset = file_path.split('.')[-1] in ['ico', 'png'] use_gzip = False if is_asset: full_path = f'assets/{file_path}' else: # 兼容没有Accept-Encoding头的情况,避免KeyError accept_encoding = request.headers.get('accept-encoding', '') if 'gzip' in accept_encoding.lower(): use_gzip = True full_path = f'theme_build/{file_path}.gz' else: full_path = f'theme_build/{file_path}' print(full_path, flush=True) s3_result = get_file(full_path) # 复制S3返回的头,再修改必要字段 response_headers = s3_result['ResponseMetadata']['HTTPHeaders'].copy() # 修正Content-Type为原文件的正确类型 if content_type: response_headers['Content-Type'] = content_type if use_gzip: # 声明内容为gzip压缩 response_headers['Content-Encoding'] = 'gzip' # 通知缓存服务器根据Accept-Encoding返回不同内容 response_headers['Vary'] = 'Accept-Encoding' return StreamingResponse( s3_result['Body'], headers=response_headers ) except ...: # 你的异常处理逻辑
注意事项
- 如果S3存储的.gz文件自带了
Content-Length头可以直接保留,不需要额外修改 - 对于图片、图标这类已经是压缩格式的资源,不需要走gzip逻辑,你原来的判断逻辑可以保留
- 如果你的S3桶内的.gz文件已经配置了正确的头,也可以直接用,但手动覆盖响应头兼容性更强,不容易因为S3配置错误出现问题
内容的提问来源于stack exchange,提问作者Wu Wei
相关产品推荐
相关产品推荐

