通过代理调用HTTP HEAD方法时Content-Length被置0的问题
我遇到了这样一个问题:用HTTP触发的Azure函数搭建了对接Blob Storage的文件服务器,GET请求能正常返回文件,服务运行没问题。为了支持获取文件大小,我实现了HEAD方法的响应——直接调用函数的HEAD接口时,Content-Length返回值是正确的,但通过Azure API Management(APIM)代理调用时,返回的Content-Length就变成0了。
我整理了简化的复现示例,方便定位问题:
1. 函数代码(Python示例)
这是处理HEAD和GET请求的核心逻辑:
import azure.functions as func from azure.storage.blob import BlobServiceClient import os def main(req: func.HttpRequest) -> func.HttpResponse: blob_name = req.route_params.get('blob_name') if not blob_name: return func.HttpResponse("Missing blob name", status_code=400) connection_string = os.environ["AzureWebJobsStorage"] blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_client = blob_service_client.get_blob_client(container="my-container", blob=blob_name) if req.method == 'HEAD': try: blob_properties = blob_client.get_blob_properties() headers = { 'Content-Length': str(blob_properties.size), 'Content-Type': blob_properties.content_type } # 显式返回空响应体,确保符合HEAD规范 return func.HttpResponse(body=b'', status_code=200, headers=headers) except Exception as e: return func.HttpResponse(f"Error getting blob properties: {str(e)}", status_code=404) elif req.method == 'GET': blob_data = blob_client.download_blob().readall() return func.HttpResponse( blob_data, headers={ 'Content-Length': str(len(blob_data)), 'Content-Type': blob_properties.content_type } ) else: return func.HttpResponse("Method not allowed", status_code=405)
2. APIM代理定义
代理的基础策略配置:
<policies> <inbound> <base /> <set-backend-service base-url="https://your-function-app.azurewebsites.net/api" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
3. 响应对比
- 直接调用函数的HEAD响应(正常):
HTTP/1.1 200 OK
Content-Length: 102400
Content-Type: application/octet-stream
Date: Wed, 10 Jul 2024 12:00:00 GMT
- 通过代理调用的HEAD响应(异常):
HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/octet-stream
Date: Wed, 10 Jul 2024 12:01:00 GMT
问题原因
经过排查,发现APIM默认处理HEAD请求时,因为响应体为空,会自动覆盖或忽略后端返回的Content-Length头,导致最终返回给客户端的Content-Length被设置为0。
解决方案
方案1:修改APIM出站策略,显式保留Content-Length
在代理的出站策略中添加逻辑,确保后端返回的Content-Length被正确保留:
<outbound> <base /> <choose> <when condition="@(context.Request.Method.Equals("HEAD", StringComparison.OrdinalIgnoreCase))"> <set-header name="Content-Length" exists-action="override"> <value>@(context.Response.Headers.GetValueOrDefault("Content-Length", "0"))</value> </set-header> </when> </choose> </outbound>
这个策略会在HEAD请求时,把后端返回的Content-Length值重新设置到响应头中,避免APIM自动覆盖。
方案2:检查APIM压缩设置
如果APIM启用了响应压缩,可能会干扰HEAD请求的Content-Length。可以针对HEAD请求禁用压缩:
<inbound> <base /> <choose> <when condition="@(context.Request.Method.Equals("HEAD", StringComparison.OrdinalIgnoreCase))"> <set-header name="Accept-Encoding" exists-action="delete" /> </when> </choose> <set-backend-service base-url="https://your-function-app.azurewebsites.net/api" /> </inbound>
方案3:确保函数返回的HEAD响应符合规范
虽然直接调用正常,但显式指定空响应体可以避免网关的异常处理逻辑:
在函数的HEAD分支中,明确返回body=b''(如上面的代码示例),确保响应格式完全符合HTTP规范。
我测试了方案1,已经解决了Content-Length返回0的问题,你可以根据自己的APIM配置情况选择合适的方案。
内容的提问来源于stack exchange,提问作者user3876103

