IIS通过ARR转发multipart/form-data上传请求返回502错误排查
问题描述
我们有一个经典ASP.NET Web API对外提供文件上传端点,该接口接收携带文件的multipart/form-data请求,请求格式如下:
POST https://dev.appname.com/api/v2/visitors/{{visitorId}}/attachments/upload X-API-Key: {{apiKey}} Content-Type: multipart/form-data; boundary=XXX --XXX Content-Disposition: form-data; name="image"; filename="filename.png" Content-Type: image/png {{ file bytes }} --XXX--
我们配置了如下重写规则:
<rule name="Rewrite to dev REST API" enabled="true" stopProcessing="true"> <match url="api/(v[0-9]+/.*)" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false"> <add input="{HTTP_HOST}" matchType="Pattern" pattern="(dev|dev-testing).appname.com" ignoreCase="true" negate="false" /> </conditions> <action type="Rewrite" url="https://restapidev.appname.com/api/{R:1}?tenant={C:1}" appendQueryString="true" /> </rule>
按照上述规则,URL会从https://dev.appname.com/api/v2/visitors/{{visitorId}}/attachments/upload重写为https://restapidev.appname.com/api/v2/visitors/{{visitorId}}/attachments/upload?tenant=dev。
该请求返回502错误,但直接访问https://restapidev.appname.com/api开头的目标URL时,请求可以成功响应。
为了诊断问题,编写了如下代码将请求体写入文件:
private async Task SaveRequestBody() { var req = HttpContext.Current.Request; using var fileStream = File.OpenWrite($"C:\\temp\\{DateTime.Now:yyyy-MM-dd-HH-mm-ss}-UploadAttachment.txt"); await req.GetBufferedInputStream().CopyToAsync(fileStream); }
观测现象
- 通过dev.appname域名访问时:
- ❌ 写入的文件为空
- 直接通过restapidev.appname域名访问时:
- ✅ 包括上传内容在内的完整请求被写入文件
- 上传非multipart/form-data格式的文件(Content-Type为image/png,字节直接放在请求体中提交)时:
- ✅ 包括上传内容在内的完整请求被写入文件
应用日志显示程序在读取请求流时出现“挂起”状态。开启失败请求跟踪后,发现如下记录:
- 12:36:37.144:触发GENERAL_READ_ENTITY_START事件
- 12:38:37.504(间隔2分钟后):触发GENERAL_READ_ENTITY_END事件
- BytesReceived="0",ErrorCode="The I/O operation has been aborted because of either a thread exit or an application request. (0x800703e3)"
注:跟踪日志中还存在
REWRITE_DISABLED_KERNEL_CACHE警告,但成功的请求也存在该警告,因此可以排除该告警影响。受限于长度限制,无法贴出完整日志。
综上,Web应用仅在同时满足以下两个条件时会出现读取请求体挂起的问题:
- 请求为multipart/form-data格式的上传请求
- 请求经过ARR路由转发
可能的配置错误
以下是对应场景下最常见的配置问题:
- ARR请求缓冲配置不匹配
默认情况下ARR对multipart格式请求的缓冲策略和普通请求存在差异,如果ARR层面的bufferRequestBody配置为false,或是缓冲阈值小于上传文件大小,会导致请求体未完整转发就触发路由逻辑,后端服务读取请求流时无法拿到完整内容触发超时。
可检查applicationHost.config中的ARR配置,确认对应后端节点的buffer设置为true,同时调整maxAllowedContentLength到大于最大上传文件的大小:
<configuration> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="104857600" /> <!-- 按实际上传需求调整,示例为100MB --> </requestFiltering> </security> <applicationRequestRouting> <webFarms> <webFarm name="restapidev.appname.com" enabled="true"> <server address="restapidev.appname.com" enabled="true" /> <applicationRequestRouting> <protocol buffer="true" /> </applicationRequestRouting> </webFarm> </webFarms> </applicationRequestRouting> </system.webServer> </configuration>
- Content-Type头被意外修改
部分ARR版本在处理带boundary参数的Content-Type头时,如果存在全局Header重写规则,可能会意外截断或修改boundary值,导致后端服务无法识别multipart请求的边界,持续等待请求体结束触发超时。需检查是否存在全局Header修改规则影响了Content-Type头,必要时在重写规则中显式保留该头。 - ASP.NET表单验证提前消费请求体
经典ASP.NET默认会对multipart/form-data请求提前解析表单内容,将请求流定位到末尾,导致后续ARR转发或业务代码读取时拿到空流。可针对上传接口路径关闭表单自动解析:
<location path="api/visitors/attachments/upload"> <system.web> <httpRuntime maxRequestLength="102400" /> <!-- 对应上方maxAllowedContentLength,单位为KB --> <pages validateRequest="false" /> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="false"> <remove name="FormsAuthentication" /> </modules> </system.webServer> </location>
- ARR超时设置小于上传预期耗时
如果上传文件较大,ARR默认2分钟的超时时间会在文件上传完成前主动断开连接,和观测到的2分钟后报错的现象完全吻合。可调整ARR超时时间到大于最大上传预期耗时:
<system.applicationHost> <webLimits connectionTimeout="00:10:00" /> <!-- 示例调整为10分钟,可按实际需求修改 --> </system.applicationHost>
内容的提问来源于stack exchange,提问作者Sander Declerck
相关产品推荐
相关产品推荐

