.NET 4.8 HttpClient调用服务抛ArgumentException:仅允许http/https协议
解决.NET Framework 4.8 HttpClient无法处理非HTTP/HTTPS协议响应头的问题
问题根源
.NET Framework 4.8的HttpClient依赖底层HttpWebRequest实现,当响应中包含Content-Location这类需要解析为Uri的头时,HttpWebRequest会严格校验协议类型,只允许http/https,一旦遇到resource://这类非标准协议,就会抛出ArgumentException。而.NET Core/.NET 8重构了HttpClient的底层逻辑,对非标准协议头的处理更宽松,不会强制解析为Uri,因此不会触发异常。
另外,异常发生在自定义HttpMessageHandler执行之前——因为默认的handler会先完成响应头的解析工作,所以自定义处理器无法在异常抛出前拦截修改响应头。
解决方案
方案1:直接使用HttpWebRequest手动处理响应
绕过HttpClient的默认解析逻辑,直接用HttpWebRequest发送请求,手动读取并过滤/修改有问题的响应头:
var request = (HttpWebRequest)WebRequest.Create("https://example.com/path/to/resource/123"); request.Method = "GET"; using (var response = (HttpWebResponse)request.GetResponse()) { // 手动遍历响应头,跳过非法的Content-Location foreach (string headerName in response.Headers) { if (!headerName.Equals("Content-Location", StringComparison.OrdinalIgnoreCase)) { // 处理合法的响应头 Console.WriteLine($"{headerName}: {response.Headers[headerName]}"); } } // 读取响应内容 using (var stream = response.GetResponseStream()) using (var reader = new StreamReader(stream)) { var content = reader.ReadToEnd(); // 后续业务逻辑 } }
方案2:自定义HttpMessageHandler替换底层实现
如果需要保留HttpClient的调用方式,可以自定义一个HttpMessageHandler,内部用HttpWebRequest处理请求和响应,手动过滤非法响应头后再封装为HttpResponseMessage:
public class CustomHttpHandler : HttpMessageHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var webRequest = (HttpWebRequest)WebRequest.Create(request.RequestUri); webRequest.Method = request.Method.Method; // 复制请求头(处理受限头) foreach (var header in request.Headers) { if (WebHeaderCollection.IsRestricted(header.Key)) { switch (header.Key.ToLower()) { case "accept": webRequest.Accept = string.Join(", ", header.Value); break; case "content-type": webRequest.ContentType = string.Join(", ", header.Value); break; // 按需处理其他受限请求头 } } else { webRequest.Headers.Add(header.Key, string.Join(", ", header.Value)); } } // 处理请求内容(POST/PUT等场景) if (request.Content != null) { var contentBytes = await request.Content.ReadAsByteArrayAsync(cancellationToken); webRequest.ContentLength = contentBytes.Length; using (var stream = await webRequest.GetRequestStreamAsync()) { await stream.WriteAsync(contentBytes, 0, contentBytes.Length, cancellationToken); } } // 获取响应(处理错误状态码) HttpWebResponse webResponse; try { webResponse = (HttpWebResponse)await webRequest.GetResponseAsync(); } catch (WebException ex) when (ex.Response is HttpWebResponse) { webResponse = (HttpWebResponse)ex.Response; } // 构建HttpResponseMessage,过滤非法响应头 var responseMessage = new HttpResponseMessage((HttpStatusCode)webResponse.StatusCode); var content = new StreamContent(webResponse.GetResponseStream()); // 复制通用响应头 foreach (string headerName in webResponse.Headers) { if (!headerName.Equals("Content-Location", StringComparison.OrdinalIgnoreCase)) { responseMessage.Headers.TryAddWithoutValidation(headerName, webResponse.Headers[headerName]); } } // 复制内容响应头 foreach (string headerName in webResponse.ContentHeaders) { if (!headerName.Equals("Content-Location", StringComparison.OrdinalIgnoreCase)) { content.Headers.TryAddWithoutValidation(headerName, webResponse.ContentHeaders[headerName]); } } responseMessage.Content = content; return responseMessage; } }
使用自定义handler创建HttpClient:
var client = new HttpClient(new CustomHttpHandler()); var response = await client.GetAsync("https://example.com/path/to/resource/123"); // 后续处理响应
结论
.NET Framework 4.8的默认HttpClient确实无法直接处理包含非HTTP/HTTPS协议的响应头,必须通过上述自定义底层处理逻辑的方式绕过协议校验。
内容的提问来源于stack exchange,提问作者Greg Sandstrom
相关产品推荐
相关产品推荐

