C#-HttpClient请求超时排查记录

C# HttpClient 请求超时,curl 却能返回:一次 Expect: 100-continue 排查记录

在一个 .NET Framework 4.7.2 控制台项目中,我用 HttpClient 向某 HTTPS 接口提交 POST 请求。相同接口和参数在 Postman、curl 中能很快收到响应,C# 却一直停在 SendAsync,最后超时。

这次排查得到两个不同层面的结论:关闭当前请求的 Expect: 100-continue 后,C# 能快速收到 HTTP 响应;要拿到正确的业务结果,还必须核对 Cookie 和表单格式。 文中的地址均为示例地址,不是真实接口。

问题代码

原始代码的核心部分如下,地址已替换:

var client = new HttpClient();
var request = new HttpRequestMessage(
   HttpMethod.Post,
   "https://example.com/api/check-status");
​
request.Content = new StringContent("applyData={\"apply\":[]}");
​
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());

程序长时间停在 SendAsync。这里使用的是 await,没有 .Result 或 .Wait();单次请求也不足以支持“连接池耗尽”的判断,因此先对比两种客户端的实际行为。

curl 的“正常返回”是什么

不带登录 Cookie 请求该接口时,curl 很快收到 302 Found,响应包含指向站点入口页的 Location 和新设置的会话 Cookie。这表明当前请求没有得到预期的业务响应,很可能被会话校验拦截。

curl 默认不跟随重定向;HttpClientHandler 默认会跟随。为了看清接口的第一跳响应,诊断时关闭自动重定向:

var handler = new HttpClientHandler
{
   AllowAutoRedirect = false
};

但关闭重定向后,C# 的首次请求仍会超时,说明自动跳转不是这次超时的直接原因。

真正影响超时的请求行为

使用同一套 .NET Framework 请求代码做对照测试,每次设置 20 秒超时,结果如下:

请求条件结果
关闭自动重定向,强制 TLS 1.2 20 秒超时
在上述条件下再关闭代理 仍然超时
关闭 Expect: 100-continue 约 245 毫秒收到 302
只对当前请求设置 ExpectContinue = false 约 306 毫秒收到 302

同时,curl 分别使用 IPv4、IPv6 和不带 User-Agent 请求,都能快速收到响应。这些对照结果表明,本次超时与 .NET Framework 请求中的 Expect: 100-continue 交互有关;代理、地址族以及 User-Agent 不是已观察到的直接触发因素。

Expect: 100-continue 的作用是让客户端先发送请求头,等待服务器允许后再发送请求体。这个机制在正常实现中有等待和继续发送的处理,不能仅凭“服务器没有返回 100 Continue”就断定一定会无限等待。这里能确认的是:在当前客户端与目标站点的组合下,关闭它后超时消失。 没有服务端日志或完整链路抓包,无法进一步确定问题具体发生在源站、反向代理还是中间网关。

对这条请求,最小修改是在 SendAsync 前设置:

request.Headers.ExpectContinue = false;

这样只改变当前请求,不必调整整个进程的 ServicePointManager.Expect100Continue。如果使用 HttpWebRequest,对应的设置是 request.ServicePoint.Expect100Continue = false,但它作用于共享的 ServicePoint,使用时要注意影响范围。

收到响应后,还要修正业务请求

关闭 Expect 后收到的是 302,并非接口成功返回业务数据。原始代码还有两个明确的差异。

第一,代码没有携带登录 Cookie。新建的 HttpClient 不会继承 Postman 或浏览器中的登录会话。需要核对成功请求实际携带的 Cookie,并在 C# 中使用有效会话。未登录响应中新发的会话 Cookie,也不能直接当作已登录会话使用。

第二,new StringContent("applyData=...") 默认设置的是 Content-Type: text/plain; charset=utf-8。如果接口需要的是名为 applyData 的表单字段,应使用 FormUrlEncodedContent:

request.Content = new FormUrlEncodedContent(
   new Dictionary<string, string>
  {
      ["applyData"] = "{\"apply\":[]}"
  });

这样会发送 application/x-www-form-urlencoded,并对字段值中的引号、花括号等字符进行表单编码。手工拼接在参数包含 &、+ 或中文时更容易出错。表单格式错误会影响业务处理,但不是本次已验证的超时原因:原来的 text/plain 请求在关闭 Expect 后也能迅速收到 302。

Referer、User-Agent 等请求头应以真正成功的请求为准,逐项比对。诊断中 curl 去掉 User-Agent 仍能快速得到 302,因此没有证据表明它导致了本次超时。TLS 1.2 和代理设置的单独调整也没有解决超时;关闭 Expect 后 .NET 能建立 HTTPS 连接并收到响应,因此当前现象也不符合典型的证书校验失败。

调整后的示例

下面的代码用于展示关键修改和诊断信息。示例假设接口采用表单提交;实际项目应以成功的 curl 请求或接口文档为准。Cookie 从环境变量读取,避免写入源码。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
​
internal class Program
{
   private static async Task Main(string[] args)
  {
       const string url = "https://example.com/api/check-status";
       string cookie = Environment.GetEnvironmentVariable("API_COOKIE");
​
       using (var handler = new HttpClientHandler
      {
           AllowAutoRedirect = false,
           UseCookies = false
      })
       using (var client = new HttpClient(handler))
       using (var request = new HttpRequestMessage(HttpMethod.Post, url))
      {
           request.Headers.ExpectContinue = false;
​
           if (!string.IsNullOrWhiteSpace(cookie))
          {
               request.Headers.TryAddWithoutValidation("Cookie", cookie);
          }
​
           request.Content = new FormUrlEncodedContent(
               new Dictionary<string, string>
              {
                  ["applyData"] = "{\"apply\":[]}"
              });
​
           using (var response = await client.SendAsync(request))
          {
               string body = await response.Content.ReadAsStringAsync();
​
               Console.WriteLine(
                   $"HTTP {(int)response.StatusCode} {response.StatusCode}");
               Console.WriteLine($"Location: {response.Headers.Location}");
               Console.WriteLine(body);
​
               response.EnsureSuccessStatusCode();
          }
      }
  }
}

诊断时保留 AllowAutoRedirect = false,可以直接发现 Cookie 失效造成的 302。确认业务请求正常后,再决定是否需要自动跟随重定向。对于频繁调用接口的长期运行程序,还应复用 HttpClient,并继续及时释放每次请求和响应。

这次验证完成的是超时定位:关闭单次请求的 ExpectContinue 后,C# 已能快速收到 HTTP 状态码。由于没有用于测试的有效登录 Cookie,业务成功响应尚未验证。遇到类似情况,可以先加这一行,再观察首次响应的状态码和 Location;如果返回 302,接着核对登录会话和请求体格式。

posted @ 2026-09-23 22:20  曲琦  阅读(16)  评论(0)    收藏  举报