X.Helper.Http
1.0.3
dotnet add package X.Helper.Http --version 1.0.3
NuGet\Install-Package X.Helper.Http -Version 1.0.3
<PackageReference Include="X.Helper.Http" Version="1.0.3" />
<PackageVersion Include="X.Helper.Http" Version="1.0.3" />
<PackageReference Include="X.Helper.Http" />
paket add X.Helper.Http --version 1.0.3
#r "nuget: X.Helper.Http, 1.0.3"
#:package X.Helper.Http@1.0.3
#addin nuget:?package=X.Helper.Http&version=1.0.3
#tool nuget:?package=X.Helper.Http&version=1.0.3
X.Helper.Http
X.Helper.Http 是基于 HttpClient 的轻量封装,面向常见业务 HTTP 场景,提供链式配置与可编程请求体两种用法。
支持能力:
- 常规请求:
GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/TRACE - 请求体:
RAW/x-www-form-urlencoded/multipart/form-data/binary - 文件上传(含进度、取消)
- 文件下载(流式落盘)
- SSE 流式接收
- Header / Cookie / 超时 / 编码 / 重试 / HTTP 版本配置
目标框架
>= net4.6.1netstandard2.0netstandard2.1>= net6.0
说明:SSE 在
.NET Framework目标下已弃用,建议在net6.0+使用。
核心类型
Client
请求入口与链式配置核心类型。
常用能力:
- 基础配置:
SetUri、SetMethod、SetTimeout、SetEncoding、SetHttpVersion - 调试:
SetDebugEnabled、SetDebugLogger - Header:
SetHeader、SetDefaultRequestHeaders、SetReferer、SetOrigin、SetUserAgent - Cookie:
SetCookie、ClearCookies - 请求体:
SetContentType+AddContent/AddFile - 请求发送:
RequestByteAsync、RequestTextAsync、RequestDownloadFile - 上传:
RequestUploadFileAsync(...)(支持IProgress<double>+CancellationToken) - SSE:
RequestSSEWithCallbackAsync(...);.NET 6+额外支持RequestSSEAsyncEnumerable(...)returnRawEventBlock = false(默认):仅返回data:字段returnRawEventBlock = true:回调/枚举返回完整事件块原文(含自定义字段)
Result
统一响应结果:
- 状态:
StatusCode、StatusDescription、IsSuccess - 头与 Cookie:
HeaderCollection、CookieCollection - 内容:
Content(文本)、Bytes(字节) - 其他:
ResponseUri、RedirectUrl、ContentType、DownloadFilePath - 异常与取消:
Exception(内部异常及堆栈,成功时为 null)、IsCanceled(因外部取消令牌终止时为 true;超时被归类为失败而非取消)
HttpHandler
对 HttpClientHandler 的配置封装:
- 代理、自动重定向、自动解压
CookieContainer- 证书验证回调、客户端证书
- 凭据配置
HttpContentCreator
类型全名:X.Helper.Http.Helper.HttpContentCreator。
可编程请求体构建器,适合复杂 body 场景;与 RequestByteAsync(contentCreator) 或 RequestTextAsync(contentCreator) 配合使用。
实例生命周期与线程安全
Client 内部持有 HttpClient,请遵循以下契约:
- 长生命周期复用:同一
Client实例应在多次请求间复用(注册为单例或少量实例),不要每次请求都new Client()后立即Dispose—— 高并发下会带来 socket 耗尽(TIME_WAIT 堆积)风险。 - 非线程安全:同一实例不要并发发起多个请求;需要并发时请为每个并发请求使用独立
Client实例,或自行串行化访问。 - 典型用法(复用同一实例):
// 推荐:作为长生命周期对象复用,而非每次请求 new + Dispose
private static readonly X.Helper.Http.Client _client = new X.Helper.Http.Client(baseUrl);
var result = await _client
.SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
.RequestTextAsync(token);
文档中的"快速开始 / 标准模板"示例为便于阅读,常写成
new Client(...)单次使用;生产环境请改为复用实例。
两种请求体模式(重要)
Client 支持两种 body 配置方式:
- 链式:
SetContentType(...)+AddContent(...)/AddFile(...) - 参数:
RequestTextAsync(contentCreator)或RequestByteAsync(contentCreator)
同一次请求中,这两种方式不可同时使用。若同时配置会抛出异常。
另外:
contentCreator模式下当前不支持自动重试(SetRetryCount > 0会抛异常)。- 链式
MULTIPART_FORM_DATA已支持同时提交表单字段 +AddFile(...)文件。
SetContentType(...) 行为说明
SetContentType(...)的Set语义是“重置并切换类型”。- 调用后会清空此前通过
AddContent(...)添加的内容参数,这是预期行为,不是缺陷。 SetContentType(...)仅清空内容参数,不会清空已通过AddFile(...)添加的文件。- 推荐调用顺序:先
SetContentType(...),再AddContent(...)/AddFile(...)。 - 如果先
AddContent(...)再SetContentType(...),之前内容会被清空。
// 推荐:先 SetContentType,再 AddContent
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
.AddContent("name", "alice")
.RequestTextAsync();
快速开始
1) GET + 文本响应
using var client = new X.Helper.Http.Client("https://httpbin.org/get");
var result = await client
.SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
.RequestTextAsync();
if (result.IsSuccess)
Console.WriteLine(result.Content);
2) POST JSON(链式 body)
using var client = new X.Helper.Http.Client("https://httpbin.org/post");
var result = await client
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"SystemX\",\"age\":18}")
.RequestTextAsync();
3) POST 表单(HttpContentCreator)
var creator = new X.Helper.Http.Helper.HttpContentCreator(
X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED);
creator.AddContent("name", "SystemX")
.AddContent("role", "admin");
using var client = new X.Helper.Http.Client("https://httpbin.org/post");
var result = await client
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.RequestTextAsync(creator);
4) multipart(链式表单 + 文件)
using var client = new X.Helper.Http.Client("https://example.com/upload");
var result = await client
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
.AddContent("bizType", "avatar")
.AddFile("file", @"D:\data\avatar.png")
.RequestTextAsync();
5) 文件上传(进度 + 取消)
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var progress = new Progress<double>(p => Console.WriteLine($"上传进度: {p:P1}"));
using var client = new X.Helper.Http.Client("https://example.com/upload");
var result = await client
.AddFile("file", @"D:\data\test.bin")
.RequestUploadFileAsync(progress, cts.Token);
6) 文件下载(流式)
using var client = new X.Helper.Http.Client("https://example.com/file.zip");
var result = await client
.SetAutoCreateDirectory(true)
.RequestDownloadFile(@"D:\download\file.zip");
Console.WriteLine(result.DownloadFilePath);
覆盖与错误处理:目标文件已存在时默认不覆盖,返回失败
Result(IsSuccess=false且Exception携带IOException)。如需覆盖,调用SetFileDownloadOverwrite(true)。目标目录不存在且未开启SetAutoCreateDirectory(true)时同样返回失败Result(异常为DirectoryNotFoundException)。下载相关错误统一通过Result承载,不再抛出异常。
7) SSE 流式接收(net6.0+ 推荐)
using var client = new X.Helper.Http.Client("https://example.com/sse");
await client.RequestSSEWithCallbackAsync(
onChunk: async chunk =>
{
Console.WriteLine($"SSE: {chunk}");
await Task.CompletedTask;
},
returnRawEventBlock: true);
returnRawEventBlock默认值为false,仅返回data事件块。 如果仅需要返回所有完整事件块(包括data/event/id/retry、注释和自定义标识行)。,请设置returnRawEventBlock: true。
.NET 6+ 可使用异步枚举:
await foreach (var chunk in client.RequestSSEAsyncEnumerable(returnRawEventBlock: true))
{
Console.WriteLine(chunk);
}
标准链式调用模板(建议直接复用)
模板 A:普通 GET
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
.RequestTextAsync(token);
模板 B:JSON 提交(POST/PUT/PATCH)
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent(json)
.RequestTextAsync(token);
模板 C:表单提交(x-www-form-urlencoded)
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
.AddContent("name", "alice")
.AddContent("age", 18)
.RequestTextAsync(token);
模板 D:multipart(表单 + 文件,走 RequestTextAsync)
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
.AddContent("bizType", "avatar")
.AddFile("file", @"D:\\data\\avatar.png")
.RequestTextAsync(cancellationToken: token);
模板 E:专用上传(带进度)
var progress = new Progress<double>(p => Console.WriteLine($"upload: {p:P1}"));
var result = await new X.Helper.Http.Client(url)
.AddFile("file", @"D:\\data\\big.bin")
.RequestUploadFileAsync(progress, token);
模板 F:流式下载到文件
var result = await new X.Helper.Http.Client(url)
.SetAutoCreateDirectory(true)
.RequestDownloadFile(@"D:\\download\\target.zip", token);
模板 G:SSE(net6.0+)
await new X.Helper.Http.Client(url).RequestSSEWithCallbackAsync(
onChunk: chunk =>
{
Console.WriteLine(chunk);
return Task.CompletedTask;
},
cancellationToken: token,
returnRawEventBlock: true);
顺序规则:基础配置 → Header/Cookie →
SetContentType→AddContent/AddFile→ 发送。冲突规则:
SetContentType(...)会清空已添加请求体参数。- 链式 body 与
contentCreator参数不可同时使用。contentCreator模式下不支持自动重试(SetRetryCount > 0会抛异常)。
常见错误示例(反例)
反例 1:先 AddContent 再 SetContentType
// ? 错误:SetContentType 会清空之前 AddContent 的参数
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.AddContent("name", "alice")
.SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
.RequestTextAsync();
// ? 正确:先 SetContentType,再 AddContent
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
.AddContent("name", "alice")
.RequestTextAsync();
反例 2:链式 body 与 contentCreator 同时使用
// ? 错误:同一次请求不能同时使用两套请求体配置
var creator = new X.Helper.Http.Helper.HttpContentCreator(
X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"alice\"}");
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"bob\"}")
.RequestTextAsync(creator); // 将抛 InvalidOperationException
反例 3:contentCreator 模式下启用自动重试
// ? 错误:contentCreator 模式不支持 RetryCount > 0
var creator = new X.Helper.Http.Helper.HttpContentCreator(
X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"alice\"}");
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetRetryCount(2)
.RequestTextAsync(creator); // 将抛 InvalidOperationException
反例 4:默认 GET 却试图发送 body
// ? 错误:未设置方法时默认 GET,GET 场景不会发送请求体
var result = await new X.Helper.Http.Client(url)
.SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"alice\"}")
.RequestTextAsync();
// ? 正确:显式设置 POST/PUT/PATCH 等支持请求体的方法
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
.AddContent("{\"name\":\"alice\"}")
.RequestTextAsync();
反例 5:AddFile 后未使用 multipart 或专用上传接口
// ? 易错:AddFile 后若未设置 MULTIPART_FORM_DATA,文件不会按预期进入普通请求体
var result = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.AddFile("file", @"D:\\data\\a.bin")
.RequestTextAsync();
// ? 方式 A:普通请求中显式设置 multipart
var resultA = await new X.Helper.Http.Client(url)
.SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
.SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
.AddFile("file", @"D:\\data\\a.bin")
.RequestTextAsync();
// ? 方式 B:直接使用专用上传接口
var resultB = await new X.Helper.Http.Client(url)
.AddFile("file", @"D:\\data\\a.bin")
.RequestUploadFileAsync();
常用配置与行为说明
请求体顺序
推荐顺序:
SetContentType(...)AddContent(...)/AddFile(...)
SetContentType(...)会清空当前已添加的请求体参数。
Header
SetDefaultRequestHeaders:作用于当前Client实例生命周期内的所有请求SetHeader:作用于单次请求消息
调试输出
- 默认关闭,通过
SetDebugEnabled(true)开启。 - 可通过
SetDebugLogger(Action<string>)自定义输出位置(如ILogger、文件、控制台)。 - 开启后会输出关键节点信息:
- 请求参数(含 Header/Cookie 完整值、请求体参数、文件参数)
- 请求过程(发送、重试、超时、异常、完成)
- 响应信息(状态码、响应头、Cookie、响应正文/字节长度)
- SSE 每个返回块的完整内容
using var client = new X.Helper.Http.Client(url)
.SetDebugEnabled(true)
.SetDebugLogger(msg => Console.WriteLine(msg));
重试
- 通过
SetRetryCount(int)配置 - 仅
RequestTextAsync、RequestByteAsync参与重试 - 上传/下载/SSE 不参与重试
- 非成功状态码仅对以下状态自动重试:
408、429、5xx - 仅幂等方法重试(默认安全):默认开启「仅幂等重试」,
POST/PATCH等非幂等写请求不会自动重试,避免重复下单/扣款等副作用;GET/HEAD/OPTIONS/TRACE/PUT/DELETE才参与重试。如需恢复"所有方法均重试"的旧行为,调用SetRetryIdempotentOnly(false)(存在重复写风险,请谨慎)。 - 不可 seek 的流不重试:链式
BINARY模式若传入不可随机读取的Stream,为避免重试时发送残缺内容,该请求不会自动重试。 - 退避策略:固定
N×500ms(封顶 2s),并叠加 ±25% 随机 jitter 防止惊群;命中429时优先采用响应头Retry-After指定的等待时间(上限 60s)。
Cookie
支持两种模式:
- 手动
SetCookie(...) - 通过
HttpHandler.SetCookieContainer(CookieContainer)注入自定义CookieContainer
SetCookieContainer(...)传入的CookieContainer会被库复用(共享 Cookie 容器,不会被内部替换为新的空容器),可实现跨请求维持会话。- 建议不要混用两种模式;混用时以实际请求写入为准。
HTTP 版本
- 默认
HTTP/1.1 - 可通过
SetHttpVersion(...)指定版本
结果与异常建议
- 业务判断优先使用
result.IsSuccess - 失败优先查看
result.StatusDescription - 文本响应读取
result.Content - 字节响应读取
result.Bytes - 上传/下载/SSE 建议始终传入
CancellationToken - 需要排查失败时,读取
result.Exception(内部异常及堆栈,成功或纯取消时为 null) - 区分「外部取消」与「真实失败」:取消时
result.IsCanceled == true且Exception == null;超时 / 网络异常时IsCanceled == false且Exception非空 - 大响应保护:
RequestByteAsync/RequestTextAsync默认最多将 64MB 响应读入内存,超过阈值会拒绝读取并返回失败Result(含Exception)。超大响应请改用RequestDownloadFile流式落盘,或调用SetMaxResponseBufferSize(long)调高阈值。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.1
- Microsoft.NETFramework.ReferenceAssemblies.net461 (>= 1.0.3)
- X.Helper (>= 1.0.5)
- X.Helper.Extension (>= 1.0.10.2)
-
.NETStandard 2.0
- X.Helper (>= 1.0.5)
- X.Helper.Extension (>= 1.0.10.2)
-
.NETStandard 2.1
- X.Helper (>= 1.0.5)
- X.Helper.Extension (>= 1.0.10.2)
-
net6.0
- X.Helper (>= 1.0.5)
- X.Helper.Extension (>= 1.0.10.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.