IIS 请求与配置故障排查
IIS 请求与配置故障排查
接口返回 405、上传被拒绝、某个子目录报 500.19,都可能发生在业务代码之前。先读详细错误里的模块、通知阶段、子状态和 HRESULT,再检查准确应用范围的有效配置,往往比反复改路由或扩大目录权限更有用。
示例使用 Windows 10 Enterprise 21H1 19043.2364、IIS 10,动态接口为 ASP.NET Framework 4.x、v4.0 集成管道。下文分别处理 WebDAV 方法冲突、上传限额和静态目录配置继承;ASP.NET Core/Kestrel 的请求限制需要使用其对应配置。
1. 定位响应来源
| 现象 | 本次证据 | 处理方向 |
|---|---|---|
| PUT/DELETE 405 | WebDAVModule,MapRequestHandler,无业务响应头 | 模块与处理程序继承 |
| POST 405 | 有 RecordApi 响应头,正文 APP-METHOD-REJECTED | 应用主动拒绝,无需“修成200” |
| 大正文被拒 | RequestFilteringModule,413.1 | IIS 字节上限 |
| 进入应用后正文读取异常 | Maximum request length exceeded | Framework 输入大小上限 |
| 子目录 500.19 | 0x80070021 | 节锁定与委派 |
| 子目录 500.19 | 0x800700b7 | 合并后的重复集合键 |
详细错误页应在受控的服务器排查上下文查看,不用将内部路径和配置公开给远端用户。保留同一请求的 URL、方法、大小、时间及应用日志,不仅截图一个大状态码。
2. WebDAV 405
record.ashx 的 RecordHandler 已允许所有方法,PUT 写入内存值、GET 读取、DELETE 删除。继承 WebDAV 时,GET 能进代码,PUT/DELETE 却返回:
HTTP Error 405.0 - Method Not Allowed
Module: WebDAVModule
Notification: MapRequestHandler
Handler: RecordHandler
Error Code: 0x00000000Handler=RecordHandler 不是业务已执行的证明。实际错误模块是 WebDAVModule,通知阶段为 MapRequestHandler;响应没有应用设置的 X-Business-Handler: RecordApi。反过来,GET 的 MISSING 与 POST 的 APP-METHOD-REJECTED 带有该响应头。

WebDAV 发布组件安装后,其模块/映射可以继承到应用,未启用创作功能不等于它不参与请求。先读合并后的配置,网站子应用使用“网站名/应用路径”:
$ErrorActionPreference='Stop'
$Site='业务网站/接口'
$AppCmd="$env:windir\System32\inetsrv\appcmd.exe"
& $AppCmd list config $Site /section:system.webServer/handlers
if($LASTEXITCODE -ne 0){throw 'Handler configuration query failed'}
& $AppCmd list config $Site /section:system.webServer/modules
if($LASTEXITCODE -ne 0){throw 'Module configuration query failed'}只有确认此应用不使用 WebDAV 时,才在本应用排除对应模块与映射:
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
<!-- Keep the application's existing handler mappings here. -->
</handlers>
</system.webServer>这是合并片段,加入已有 system.webServer,不重复创建 modules/handlers,不移除其他业务映射。上级锁定时在准确范围处理委派,不为单个应用卸载全机功能或全局放开方法。去掉 WebDAV 也不会替应用实现不存在的 PUT 路由。微软 405 排查说明
实测修复后保持同一接口和输入,结果为:
Method Status BusinessHandler Body
PUT 200 RecordApi STORED:order-42
GET 200 RecordApi order-42
DELETE 200 RecordApi DELETED
GET 404 RecordApi MISSING
POST 405 RecordApi APP-METHOD-REJECTED原 PUT 值能读回,删除后由业务返回 MISSING,POST 仍按原业务规则拒绝。这比只试一个 GET 或看到一次 200 更能验收改动边界。若报错模块变成 StaticFile、RequestFilteringModule,或应用日志已经记录了拒绝,则沿新的准确分支排查。
3. 上传大小限制
上传处理器读取原始 POST 正文,返回字节数和 SHA-256。用固定的 65536、262144 字节载荷得到:
| 配置 | IIS 字节上限 | Framework KB 上限 | 小载荷 | 大载荷 |
|---|---|---|---|---|
| 基线 | 524288 | 512 | 200,摘要一致 | 200,摘要一致 |
| 只降低 IIS | 131072 | 512 | 成功 | 413.1 |
| 恢复 IIS、只降低 Framework | 524288 | 128 | 成功 | HttpException,测试处理器返回500 |
| 两层恢复 | 524288 | 512 | 成功 | 成功,摘要一致 |
IIS 的 maxAllowedContentLength 是字节,ASP.NET Framework 的 maxRequestLength 是 KB。该系统 RequestFilteringModule 实际返回 413.1;历史资料的 404.13 不能替代本机状态记录。错误页中的 Error Code=0x00000000 也不意味着请求成功。IIS requestLimits

Framework 分支是在处理器读取 Request.InputStream 时抛 Maximum request length exceeded,处理器捕获后选择返回 500,并设置 TrySkipIisCustomErrors。因此不能说所有 ASP.NET 大请求一律返回 500。先定位原始异常和实际读取路径,再看配置。
读取准确范围的两项有效值:
$ErrorActionPreference='Stop'
$AppCmd="$env:windir\System32\inetsrv\appcmd.exe"
$Scope='业务网站/上传'
& $AppCmd list config $Scope /section:system.webServer/security/requestFiltering
if($LASTEXITCODE -ne 0){throw 'Cannot read IIS effective limit'}
& $AppCmd list config $Scope /section:system.web/httpRuntime
if($LASTEXITCODE -ne 0){throw 'Cannot read ASP.NET effective limit'}示例把两层限制设为同一个 512 KiB 请求容量,并保留较低磁盘缓冲阈值:
<configuration>
<system.web>
<httpRuntime maxRequestLength="512" requestLengthDiskThreshold="80" />
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="524288" />
</requestFiltering>
</security>
</system.webServer>
</configuration>只合并相应元素,保留其余现有属性。requestLengthDiskThreshold 不能高于 maxRequestLength,否则会产生配置错误。Framework MaxRequestLength
限制的是请求大小,multipart 文件上传还有边界和表单字段开销。按业务附件上限留合理余量,不把全部限制随意加到极大,也不在没有超时证据时同时扩大 executionTimeout。若前面还有反向代理或其他应用限制,仍需检查它们,本实验只隔离了 IIS 与 System.Web 两层。
修复的 262144 字节载荷返回 SHA-256:
31a1f9dea0169551092d05e8bf4a446228c8c3eb4c9b713c66adcb7fd53c89be客户端摘要相同,Match=True;小载荷保持成功,独立 786432 字节更大载荷仍被 413.1 拒绝,健康入口保持 HEALTH-OK。既验证原业务恢复,也验证有界限制仍在。实际系统还应核对持久化对象,而不只看处理器返回的正文。
4. 配置继承 500.19
继承实验的三个目录都提供 CONTENT-OK 的 index.txt。一个子路径声明了父级锁定的 staticContent,返回 0x80070021;另一个子路径重复添加父级已有的 .kbtype MIME 项,返回 0x800700b7。相邻目录一直正常。

锁定分支沿详细页 Config File、Config Source 和行号向上查,可能来自 overrideModeDefault 或指定路径的 location。错误页即使显示 CustomErrorModule/SendResponse,也要读取 Config Error 的原因,不能仅靠模块名决定修改哪段业务。
父级集中管理配置、子级不需要覆盖时,删除子级多余声明即可。应用确实需要自行管理时,由管理员在准确应用范围授权,不修改整台服务器的默认锁定行为。
重复分支中 mimeMap 的唯一键是 fileExtension,不是 MIME 值;父级已有 .kbtype,子级再 add 会冲突。若沿用父级配置,删掉子级重复项;若本应用确需覆盖,按同一个键先 remove 再 add:
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".kbtype" />
<mimeMap fileExtension=".kbtype" mimeType="application/octet-stream" />
</staticContent>
</system.webServer>
</configuration>不要为一个键冲突使用 clear 清空全部继承 MIME 映射。0x80070005 转向读取权限,0x8007000d 转向 XML、未识别节或缺少组件,这些不是全局解锁可以统一修复的情况。500.19 按 HRESULT 排查
原失败路径修复后返回 200/CONTENT-OK,兄弟目录仍正常;还要回读目标 MIME 映射和其他静态资源。只访问首页无法证明出错子路径恢复。
5. 验证与回退
先将目标 web.config 备份到站点目录之外,保存原范围的有效配置与请求样本,暂停并行发布。只修改已经定位的元素,按同样身份、相同 URL 和输入复测,再检查相邻功能与明确的负对照。
这些局部配置改动通常无需 iisreset;web.config 变化本身可能重启 ASP.NET AppDomain,影响 InProc Session 与内存缓存,应纳入发布安排。需要回退时比较期间其他改动,恢复本次元素并再次读有效配置,不能拿整份旧文件覆盖他人的发布。
