进阶使用
Multipart / Form-Data 上传
头像、截图、日志上传等场景。MultipartFormData 按 RFC 7578 构造 body:
var form = new MultipartFormData();
form.AddText("userId", "42");
form.AddText("desc", "avatar upload");
form.AddFile("avatar", "photo.jpg", fileBytes, "image/jpeg");
using var resp = await client.PostMultipartAsync("https://api.example.com/upload", form);
大文件用 Stream 版本(不进内存):
using var fs = File.OpenRead(path);
var form = new MultipartFormData();
form.AddText("name", "big-video");
form.AddFile("file", Path.GetFileName(path), fs, fs.Length, "video/mp4");
// PostMultipartAsync 检测到 Stream part, 自动走流式上传(BodyStream 通路)
using var resp = await client.PostMultipartAsync(url, form);
流式上传 raw body
不用 multipart,直接流式发整个 body:
using var fs = File.OpenRead(largeFilePath);
var req = new HttpRequest
{
Method = HttpMethod.Post,
Url = "https://api.example.com/upload-raw",
BodyStream = fs,
BodyLength = fs.Length, // 已知长度 → 发 Content-Length;
// null → Transfer-Encoding: chunked
};
using var resp = await client.SendAsync(req);
流式下载
下载大文件 / 边下边处理:
using var outFile = File.OpenWrite(destPath);
var req = new HttpRequest
{
Url = bigFileUrl,
OnDataReceived = (buffer, offset, length) =>
{
// 注意: 这个回调在 libcurl 的 worker 线程,不要阻塞
outFile.Write(buffer, offset, length);
},
};
using var resp = await client.SendAsync(req);
// 此时 resp.Body == null (流式下载不缓冲到内存)
代理
// 设置代理(client 级,对后续请求生效)
client.SetProxy(new HttpProxy("http://127.0.0.1:7890"));
// 带认证
client.SetProxy(new HttpProxy(
"http://proxy.example.com:8080",
new NetworkCredential("user", "pwd")));
// SOCKS5 走 URL scheme
client.SetProxy(new HttpProxy("socks5://socks-proxy:1080"));
// 或 socks5h:// (DNS 在 proxy 侧解析)
// 关闭代理
client.ClearProxy();
注意 HTTP/3 限制:QUIC 无法通过 HTTP CONNECT 隧道,启用代理后即使 PreferredVersion 是 PreferH3,libcurl 也会回退到 HTTP/2 over TCP。
Cookie 跨请求共享
var req1 = new HttpRequest
{
Url = "https://example.com/login",
Method = HttpMethod.Post,
Body = loginBody,
EnableCookies = true, // 把 Set-Cookie 写入 client 的 jar
};
using var r1 = await client.SendAsync(req1);
var req2 = new HttpRequest
{
Url = "https://example.com/profile",
EnableCookies = true, // 自动带上之前 jar 里匹配的 cookie
};
using var r2 = await client.SendAsync(req2);
jar 绑在 CurlHttpClient 实例上,纯内存存储,Dispose() 后清空。
HTTP 版本控制
// 默认: PreferH3 (优先 H3,server 不支持会降级到 H2/H1.1)
client.PreferredVersion = HttpVersion.PreferH3;
// 强制 HTTP/2 (调试某些老 server 对 H2 有兼容问题时)
client.PreferredVersion = HttpVersion.Http2;
// 强制 H3 不降级(调试用,生产别这么做)
client.PreferredVersion = HttpVersion.Http3Only;
响应里 resp.Version 告诉实际协商出的协议:
Debug.Log($"Protocol: {resp.Version}"); // e.g. Http3 / Http2 / Http11
自动响应解压
默认开启(AutoDecompressResponse = true),libcurl 发 Accept-Encoding: gzip, deflate,自动解压 resp.Body。对 JSON/HTML 下行流量降 3-5x。
var req = new HttpRequest
{
Url = "...",
AutoDecompressResponse = false, // 不常见, 比如想看原始压缩字节
};
诊断统计
构造时开启:
using var client = new CurlHttpClient(enableDiagnostics: true);
// ...跑一些请求...
using var resp = await client.GetAsync("https://api.example.com/");
// 单个请求的 timing
var timing = client.Diagnostics.GetTiming(resp);
Debug.Log($"DNS: {timing.DnsTimeUs}μs, TLS: {timing.TlsTimeUs}μs, TTFB: {timing.FirstByteTimeUs}μs");
// 聚合快照
var snapshot = client.Diagnostics.GetSnapshot();
Debug.Log(snapshot); // Requests=N (ok=X fail=Y) Connections=Z Reuse=W%...
- 耗时单位均为微秒 (μs)
- 连接复用率
ConnectionReuseRate基于 libcurl 内部 connection ID 去重 - 需要分阶段采样时调
Diagnostics.Reset()清零
SSL 证书验证
默认开启,各平台走原生证书库(macOS/iOS SecTrust、Android JNI 提取、Windows CryptoAPI)。开发调试 期间接入抓包工具(Charles/mitmproxy)时可以临时关闭:
client.VerifySSL = false; // 仅调试!生产环境务必保持 true
默认 User-Agent
client 级默认:
client.UserAgent = "MyGame/1.2.3"; // 覆盖默认 "CurlUnity/0.1.0"
单个请求覆盖:
var req = new HttpRequest
{
Url = "...",
Headers = new[]
{
new KeyValuePair<string, string>("User-Agent", "CrashReporter/1.0"),
},
};
请求级 header 优先于 client 级 UA(libcurl 的 slist 优先于 CURLOPT_USERAGENT)。
线程模型要点
SendAsync是真正异步,I/O 在专属 worker 线程驱动,不会卡 Unity 主线程- 完成后 Task 的 continuation 默认回原 SynchronizationContext(Unity 主线程)
OnDataReceived回调在 worker 线程 执行,不要碰 Unity APICurlHttpClient实例长期持有,不要为每次请求重建