一、概述
HttpWebRequest 是 .NET Framework 中用于发送 HTTP 请求的核心类,位于 System.Net 命名空间下。它继承自 WebRequest,对 HTTP 协议进行了完整封装,支持 GET、POST、PUT、DELETE 等标准 HTTP 方法,并提供对 Header、Content、Cookie 等元素的属性和方法支持。
HttpWebRequest 对象不是通过 new 关键字直接创建,而是利用工厂机制通过 WebRequest.Create() 方法创建。调用 HttpWebRequest.GetResponse() 方法会返回一个 HttpWebResponse 对象,通过该对象可读取服务器返回的响应数据。
虽然微软在 .NET 4.5 及后续版本中推荐使用 HttpClient 作为现代替代方案,但 HttpWebRequest 在遗留系统维护、精细控制 HTTP 行为等场景中仍具有实用价值。
二、基础请求操作
2.1 GET 请求
GET 请求用于从服务器获取数据,参数通常附加在 URL 查询字符串中。GET 请求是幂等的,多次执行结果应一致。
**实现步骤:**
- 通过
WebRequest.Create()创建HttpWebRequest实例 - 设置
Method为"GET" - 调用
GetResponse()发送请求并获取响应 - 通过
StreamReader读取响应内容
cs
using System;
using System.IO;
using System.Net;
public class HttpGetExample
{
public static string SendGetRequest(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Accept = "*/*";
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream))
{
return reader.ReadToEnd();
}
}
}
catch (WebException ex)
{
return HandleWebException(ex);
}
}
private static string HandleWebException(WebException ex)
{
if (ex.Response is HttpWebResponse errorResponse)
{
using (StreamReader reader = new StreamReader(errorResponse.GetResponseStream()))
{
return $"HTTP {errorResponse.StatusCode}: {reader.ReadToEnd()}";
}
}
return $"网络错误: {ex.Message}";
}
}
2.2 POST 请求
POST 请求用于向服务器提交数据,数据放在请求体(Body)中,适用于表单提交、创建资源等场景。
**表单数据提交示例:**
cs
using System;
using System.IO;
using System.Net;
using System.Text;
public class HttpPostExample
{
public static string SendPostRequest(string url, string postData)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.UserAgent = "HttpHelper/1.0";
byte[] bytes = Encoding.UTF8.GetBytes(postData);
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
catch (WebException ex)
{
return HandleWebException(ex);
}
}
}
**JSON 数据提交:**
发送 JSON 数据时,需将 ContentType 设置为 "application/json",并确保请求体为合法的 JSON 字符串。
cs
request.ContentType = "application/json";
string jsonPayload = "{\"name\":\"张三\",\"age\":30}";
byte[] bytes = Encoding.UTF8.GetBytes(jsonPayload);
2.3 PUT 请求
PUT 请求用于更新服务器上的现有资源,通常需要携带资源的全部字段。
cs
public class HttpPutExample
{
public static string SendPutRequest(string url, string jsonData)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "PUT";
request.ContentType = "application/json";
byte[] bytes = Encoding.UTF8.GetBytes(jsonData);
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
catch (WebException ex)
{
return HandleWebException(ex);
}
}
}
2.4 DELETE 请求
DELETE 请求用于删除服务器上的指定资源。
cs
request.Method = "DELETE";
// 通常 DELETE 请求不需要请求体,直接发送即可
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// 处理响应
}
三、核心属性详解
HttpWebRequest 提供了一系列重要属性,用于精细控制 HTTP 请求行为:
| 属性 | 说明 |
|---|---|
AllowAutoRedirect |
获取或设置请求是否应跟随重定向响应 |
CookieContainer |
获取或设置与此请求关联的 Cookie 容器 |
Credentials |
获取或设置请求的身份验证信息 |
KeepAlive |
获取或设置是否与 Internet 资源建立持久性连接 |
MaximumAutomaticRedirections |
获取或设置请求将跟随的重定向最大数目 |
Proxy |
获取或设置请求的代理信息 |
SendChunked |
获取或设置是否将数据分段发送到 Internet 资源 |
Timeout |
获取或设置请求的超时值(毫秒) |
ReadWriteTimeout |
获取或设置读写操作的超时值(毫秒) |
UserAgent |
获取或设置 User-Agent HTTP 标头的值 |
ContentType |
获取或设置请求的 Content-Type 标头值 |
ContentLength |
获取或设置请求体的字节长度 |
Method |
获取或设置请求的 HTTP 方法 |
Headers |
获取或设置请求头的集合 |
四、请求头配置
请求头携带了关于请求的元信息,正确配置请求头是 HTTP 通信的关键。
4.1 常用请求头设置
cs
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.example.com/data");
// 通过属性设置标准头
request.ContentType = "application/json";
request.UserAgent = "MyApp/1.0";
request.Accept = "application/json";
request.Referer = "https://example.com/source";
// 通过 Headers 集合设置自定义头
request.Headers["X-API-Key"] = "your-api-key-here";
request.Headers["X-Trace-ID"] = "trace-12345"; // 便于追踪调试
// 设置认证头
request.Headers["Authorization"] = "Bearer your-token-here";
4.2 请求头配置注意事项
- Content-Type 必须小写,如
"application/json",不能写成"Application/JSON" - Content-Length 应让系统自动计算,手动设置容易出错
- Accept 表示客户端期望的响应类型,ContentType 表示发送的请求体类型,两者不可混淆
- 某些头部(如
Content-Type、User-Agent)必须通过特定属性设置,不能通过Headers.Add()方式添加,否则会抛出异常
五、HTTPS 与 SSL/TLS 配置
5.1 启用 TLS 1.2
在 .NET Framework 4.0+ 环境中,为确保与现代 HTTPS 服务兼容,必须显式启用 TLS 1.2:
cs
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
此设置为全局生效,建议在应用程序启动时(如 Main() 方法或 Application_Start() 事件)调用一次。
5.2 忽略 SSL 证书错误(仅限开发调试)
在开发环境中,若需绕过自签名证书或域名不匹配错误,可设置回调:
cs
ServicePointManager.ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
⚠️ 生产环境严禁使用。此操作完全禁用证书验证,存在中间人攻击风险。
六、Cookie 管理
使用 CookieContainer 可以在多个请求之间保持会话状态:
cs
CookieContainer cookies = new CookieContainer();
// 第一个请求:登录
HttpWebRequest loginRequest = (HttpWebRequest)WebRequest.Create("https://api.example.com/login");
loginRequest.CookieContainer = cookies;
loginRequest.Method = "POST";
// ... 发送登录请求 ...
// 第二个请求:访问受保护资源(自动携带登录时获取的 Cookie)
HttpWebRequest dataRequest = (HttpWebRequest)WebRequest.Create("https://api.example.com/protected");
dataRequest.CookieContainer = cookies; // 复用同一个 CookieContainer
dataRequest.Method = "GET";
七、异步请求
7.1 基于 APM 模式的异步请求
HttpWebRequest 支持基于 BeginGetResponse / EndGetResponse 的异步编程模型(APM):
cs
using System;
using System.IO;
using System.Net;
using System.Threading;
public class AsyncRequestExample
{
private static ManualResetEvent allDone = new ManualResetEvent(false);
public static void Main()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.example.com/data");
request.Method = "GET";
RequestState requestState = new RequestState();
requestState.Request = request;
IAsyncResult result = (IAsyncResult)request.BeginGetResponse(
new AsyncCallback(ResponseCallback), requestState);
allDone.WaitOne();
requestState.Response.Close();
}
private static void ResponseCallback(IAsyncResult asynchronousResult)
{
RequestState requestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest request = requestState.Request;
requestState.Response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
using (Stream responseStream = requestState.Response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream))
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
allDone.Set();
}
}
public class RequestState
{
public HttpWebRequest Request { get; set; }
public HttpWebResponse Response { get; set; }
}
7.2 使用 Task/async/await 封装
在 .NET 4.5+ 中,可使用 Task.Factory.FromAsync 将 APM 模式封装为基于任务的异步模式:
cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
public class AsyncHttpHelper
{
public static async Task<string> GetAsync(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)await Task.Factory.FromAsync<WebResponse>(
request.BeginGetResponse, request.EndGetResponse, null))
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return await reader.ReadToEndAsync();
}
}
public static async Task<string> PostAsync(string url, string postData, string contentType = "application/json")
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = contentType;
byte[] bytes = Encoding.UTF8.GetBytes(postData);
request.ContentLength = bytes.Length;
// 异步写入请求流
using (Stream requestStream = await Task.Factory.FromAsync<Stream>(
request.BeginGetRequestStream, request.EndGetRequestStream, null))
{
await requestStream.WriteAsync(bytes, 0, bytes.Length);
}
// 异步获取响应
using (HttpWebResponse response = (HttpWebResponse)await Task.Factory.FromAsync<WebResponse>(
request.BeginGetResponse, request.EndGetResponse, null))
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return await reader.ReadToEndAsync();
}
}
}
八、错误处理与异常捕获
8.1 WebException 处理
HttpWebRequest 的异常主要通过 WebException 捕获。即使服务器返回 4xx/5xx 状态码,GetResponse() 也会抛出 WebException,但其 Response 属性仍包含有效的响应体。
cs
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.example.com/data");
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// 处理成功响应
}
}
catch (WebException ex)
{
// 检查是否为 HTTP 错误响应
if (ex.Response is HttpWebResponse httpResponse)
{
using (StreamReader reader = new StreamReader(httpResponse.GetResponseStream()))
{
string errorBody = reader.ReadToEnd();
Console.WriteLine($"HTTP 错误 {httpResponse.StatusCode}: {errorBody}");
}
}
else
{
// 网络层错误(如超时、DNS 失败)
Console.WriteLine($"网络错误: {ex.Status} - {ex.Message}");
}
}
8.2 常见状态码处理建议
| 状态码 | 含义 | 处理建议 |
|---|---|---|
| 200 | 成功 | 正常处理响应数据 |
| 201 | 已创建 | 资源创建成功,通常返回新资源的位置 |
| 204 | 无内容 | 请求成功但无返回内容 |
| 400 | 请求错误 | 检查请求体格式、参数编码 |
| 401 | 未授权 | 检查认证头(Authorization) |
| 403 | 禁止访问 | 检查权限或 IP 白名单 |
| 404 | 资源未找到 | 验证 URL 路径是否正确 |
| 500 | 服务器内部错误 | 检查服务端日志,客户端可考虑重试 |
九、常见问题与解决方案
9.1 操作超时问题
当遇到 HttpWebRequest 请求一直返回"操作超时",而 Postman 等工具请求正常时,可能是 Expect100Continue 机制导致的问题。
解决方案: 添加以下代码禁用 Expect100Continue:
cs
request.ServicePoint.Expect100Continue = false;
Expect100Continue 的默认行为是在发送请求体前,先询问服务器是否接受请求。当服务器不支持或响应缓慢时,会导致超时。设置为 false 后,客户端将直接发送请求体,跳过等待服务器确认的步骤。
9.2 请求头设置异常
某些头部(如 Content-Type)必须通过特定属性设置,不能通过 Headers.Add() 方式添加:
cs
// 正确方式
request.ContentType = "application/json";
// 错误方式(会抛出异常)
request.Headers.Add("Content-Type", "application/json");
9.3 响应中文乱码
读取响应时,应明确指定编码格式:
cs
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
string result = reader.ReadToEnd();
}
9.4 网络抓包调试
在排查网络通信异常时,可通过添加自定义追踪标识辅助调试:
cs
request.Headers["X-Trace-ID"] = "trace-12345";
在 Wireshark 中使用过滤表达式 http.request.headers contains "trace-12345" 可快速定位对应请求,结合时间戳精确定位阻塞点。
十、最佳实践总结
-
始终使用
using语句 管理HttpWebResponse、Stream和StreamReader,防止资源泄漏 -
设置超时时间 ,避免请求无限挂起:
csrequest.Timeout = 10000; // 连接超时 10 秒 request.ReadWriteTimeout = 10000; // 读写超时 10 秒 -
**避免全局设置
ServerCertificateValidationCallback**,除非在测试环境 -
使用
CookieContainer 管理跨请求会话,而非手动处理Set-Cookie头 -
在 .NET Framework 4.0+ 中,必须显式启用 TLS 1.2
-
生产环境禁止忽略证书验证,应使用有效证书或配置信任链
-
注意请求头大小写 ,
Content-Type值应为小写形式 -
异步方法不应在 UI 线程上调用 ,因为
BeginGetRequestStream需要完成 DNS 解析、代理检测和 TCP 套接字连接等同步设置任务,可能耗时较长
十一、完整封装示例
以下是一个可直接用于生产环境的完整封装类:
cs
using System;
using System.IO;
using System.Net;
using System.Text;
public class HttpHelper
{
static HttpHelper()
{
// 全局设置:启用 TLS 1.2
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
}
/// <summary>
/// 发送 HTTP 请求
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="method">HTTP 方法(GET/POST/PUT/DELETE)</param>
/// <param name="postData">请求体数据(GET 请求时可为 null)</param>
/// <param name="contentType">内容类型</param>
/// <returns>响应字符串</returns>
public static string SendRequest(string url, string method, string postData = null,
string contentType = "application/json")
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.ContentType = contentType;
request.UserAgent = "HttpHelper/1.0";
request.Timeout = 10000;
request.ReadWriteTimeout = 10000;
request.ServicePoint.Expect100Continue = false;
if (!string.IsNullOrEmpty(postData) && method != "GET")
{
byte[] bytes = Encoding.UTF8.GetBytes(postData);
request.ContentLength = bytes.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(bytes, 0, bytes.Length);
}
}
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
catch (WebException ex)
{
if (ex.Response is HttpWebResponse errorResponse)
{
using (StreamReader reader = new StreamReader(errorResponse.GetResponseStream(), Encoding.UTF8))
{
return $"HTTP {errorResponse.StatusCode}: {reader.ReadToEnd()}";
}
}
return $"网络错误: {ex.Message}";
}
}
}
十二、与 HttpClient 的对比
| 特性 | HttpWebRequest | HttpClient |
|---|---|---|
| 推荐状态 | 遗留 API | 官方推荐 |
| 连接复用 | 依赖 ServicePoint,需手动管理 | 自动连接池,单例复用 |
| 异步模型 | 基于 APM(Begin/End) | 基于 TAP(async/await) |
| 性能 | 较低,每次请求开销大 | 高,连接复用显著降低延迟 |
| 生命周期 | 每次请求创建实例 | 应设计为单例或长期存活 |
| 适用场景 | .NET Framework 旧项目、精细控制 | 新项目、跨平台、现代开发 |
对于新项目,建议优先使用 HttpClient。若必须使用 HttpWebRequest,请严格遵循本文档中的最佳实践,确保代码的稳定性和可维护性。