HTTP 客户端
Spire 的 HTTP 客户端能力分成两层:
soulsoft_net_http提供HttpClient、HttpRequestMessage、HttpContent和处理器管道soulsoft_extensions_http提供IHttpClientFactory、命名客户端和容器集成
建议按这个顺序理解:
- 先学会直接用
HttpClient发请求 - 再理解请求管道里
HttpClient、DelegatingHandler、HttpClientHandler各自做什么 - 最后再接入
IHttpClientFactory
最小示例
import soulsoft_net_http.*
main(): Int64 {
// 直接创建 HttpClient。
let client = HttpClient()
// 发送 GET 请求,并在离开作用域时自动关闭响应对象。
try (response = client.get("https://example.com")) {
response.ensureSuccessStatusCode()
println(response.content.readAsString())
}
// 使用完成后关闭客户端。
client.close()
return 0
}这里先记住三件事:
HttpClient()可以直接使用get()返回HttpResponseMessage,处理完要关闭ensureSuccessStatusCode()会在非 2xx 时抛出异常
常见用法
基础地址和默认请求头
import soulsoft_net_http.*
import stdx.encoding.url.*
let client = HttpClient()
// 设置基础地址,后续可以发送相对路径请求。
client.baseAddress = URL.parse("https://api.example.com")
// 设置默认请求头。
client.defaultRequestHeaders.add("Accept", "application/json")
let body = client.getString("/users/1")
println(body)
// 关闭客户端。
client.close()这部分有两个关键规则:
- 相对地址依赖
baseAddress - 一旦发出过请求,就不能再修改
baseAddress
defaultRequestHeaders 还有一个行为要注意:
- 如果请求对象自己已经设置了同名头部,默认头不会覆盖这组头部
发送请求体
最常用的几个内容类型是:
StringContentJsonContentFormUrlContentMultipartFormDataContent
例如发送 JSON:
import soulsoft_net_http.*
let client = HttpClient()
// 构造 JSON 请求体。
let content = JsonContent.create("{\"username\":\"admin\"}")
try (response = client.post("https://api.example.com/login", content)) {
response.ensureSuccessStatusCode()
}
// 关闭客户端。
client.close()例如上传文件:
import soulsoft_net_http.*
let client = HttpClient()
// 构造一个文件内容对象。
let fileContent = ByteArrayContent("hello file".toArray())
fileContent.headers.add("Content-Type", "text/plain")
// 用 multipart/form-data 包装上传内容。
let form = MultipartFormDataContent()
form.add(fileContent, "file", "hello.txt")
try (response = client.post("https://api.example.com/upload", form)) {
response.ensureSuccessStatusCode()
}
// 关闭客户端。
client.close()有一个容易写错的地方:
Content-Type这类内容头应该写到content.headers- 不应该写到
request.headers
如果你的对象实现了 ISerialization<T>,也可以直接让 JsonContent 负责序列化:
// 如果对象支持序列化,可以直接交给 JsonContent 处理。
let content = JsonContent.create(loginRequest)自定义请求
当快捷方法不够用时,用 HttpRequestMessage:
import soulsoft_net_http.*
let client = HttpClient()
// 构造完整请求对象,手动指定方法和地址。
let request = HttpRequestMessage(HttpMethod.Post, "https://api.example.com/users")
request.headers.add("X-Trace-Id", "trace-001")
request.content = JsonContent.create("{\"name\":\"alice\"}")
try (response = client.send(request)) {
response.ensureSuccessStatusCode()
println(response.content.readAsString())
}
// 关闭客户端。
client.close()读取响应
最常见的读取方式有三种:
readAsString():一次性读成字符串readAsByteArray():一次性读成字节数组readAsStream():边读边处理,适合 SSE 或大响应
import std.io.*
import soulsoft_net_http.*
let client = HttpClient()
try (response = client.get("https://api.example.com/profile")) {
response.ensureSuccessStatusCode()
// 一次性把响应体读成字符串。
let text = response.content.readAsString()
println(text)
}
try (response = client.get("https://api.example.com/events")) {
response.ensureSuccessStatusCode()
// 以流的方式读取响应体。
let stream = response.content.readAsStream()
let chunk = Array<Byte>(256, repeat: 0)
let n = stream.read(chunk)
println(n)
}
// 关闭客户端。
client.close()如果响应是 JSON,并且目标类型实现了 ISerialization<T>,还可以直接反序列化:
// 如果响应 JSON 对应的类型可序列化,可以直接读成对象。
let profile = response.content.readFromJson<UserProfile>()请求管道
一次请求通常会经过三层:
HttpClient 做什么
HttpClient 是调用入口,负责准备请求和发起调用,不直接做底层网络通信。它主要做四件事:
- 提供
get、post、put、delete、send这些请求 API - 提供
getString、getByteArray、getStream这些常用读取方法 - 管理
baseAddress和defaultRequestHeaders - 在发送前解析相对地址,并补齐默认请求头
再记两个实际行为即可:
getString()、getByteArray()、getStream()会先检查响应是否为 2xxdelete()也支持携带HttpContent
DelegatingHandler 做什么
DelegatingHandler 是中间处理器,用来在主处理器前后插入横切逻辑:
- 请求发出前修改请求
- 调用
super.send(request)继续向下传递 - 响应返回后追加后置处理
典型场景就是日志、鉴权、重试和审计。
HttpClientHandler 做什么
HttpClientHandler 是尾节点,负责真正的网络发送:
- 把
HttpRequestMessage转成底层请求 - 合并
request.headers和content.headers - 调用底层
Client.send(...) - 把响应包装成
HttpResponseMessage
所以它通常放在整条请求管道的最后。
HttpClientHandler 和 HttpClient 的关系
两者的分工可以直接记成一句话:
HttpClient面向业务代码,负责“方便地发请求”HttpClientHandler面向底层传输,负责“真正把请求送出去”
如果没有自定义处理器,默认链路基本就是:
HttpClient -> HttpClientHandler -> network一个处理器示例
import soulsoft_net_http.*
class AuthHandler <: DelegatingHandler {
public init(innerHandler: HttpMessageHandler) {
// 把下一个处理器交给基类维护。
super(innerHandler)
}
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
// 在请求发出前统一补鉴权头。
request.headers.set("Authorization", "Bearer token")
return super.send(request)
}
}
// 用自定义处理器包住默认的底层处理器。
let client = HttpClient(AuthHandler(HttpClientHandler()))
try (response = client.get("https://api.example.com/me")) {
response.ensureSuccessStatusCode()
}
// 关闭客户端。
client.close()这里再记一条约束:
DelegatingHandler实例不要复用到多条管道里
如果一个处理器实例已经挂过 innerHandler,再把它放进新的管道,构建时会失败。
与依赖注入集成
如果项目已经使用 soulsoft_extensions_injection,通常不再直接到处 new HttpClient(),而是交给 IHttpClientFactory 管理。
命名客户端
import soulsoft_extensions_http.*
import soulsoft_extensions_injection.*
import soulsoft_net_http.*
import stdx.encoding.url.*
let services = ServiceCollection()
// 注册一个名为 github 的命名客户端。
let _ = services.addHttpClient("github", { client =>
client.baseAddress = URL.parse("https://api.github.com")
client.defaultRequestHeaders.add("Accept", "application/json")
})
// 解析工厂并创建命名客户端。
let root = services.build()
let factory = root.getOrThrow<IHttpClientFactory>()
let client = factory.createClient("github")
try (response = client.get("/users/octocat")) {
response.ensureSuccessStatusCode()
}
// 释放客户端和根容器。
client.close()
root.close()这一层的核心价值只有一句话:
HttpClient可以按需创建,但同名客户端会复用底层处理器
给命名客户端加处理器
import soulsoft_extensions_http.*
import soulsoft_extensions_injection.*
import soulsoft_net_http.*
class AuthHandler <: DelegatingHandler {
public init() {
// 由工厂稍后注入 inner handler。
super()
}
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
// 在发送前附加鉴权头。
request.headers.set("Authorization", "Bearer token")
return super.send(request)
}
}
let services = ServiceCollection()
// 把处理器注册为瞬时服务。
services.addTransient<AuthHandler, AuthHandler>()
// 给命名客户端挂上自定义处理器并设置底层处理器复用时间。
let _ = services.addHttpClient("secured")
.addHttpMessageHandler<AuthHandler>()
.setHandlerLifetime(Duration.minute * 2)
let root = services.build()
let client = root.getOrThrow<IHttpClientFactory>().createClient("secured")
client.close()
root.close()这里有两个实践点:
- 处理器尽量注册成
Transient setHandlerLifetime()控制的是底层处理器复用时间,不是HttpClient对象寿命
核心功能小结
如果只看日常开发里最常用的能力,可以把这套 HTTP 客户端总结成下面几项:
- 用
HttpClient快速发送 GET、POST、PUT、DELETE 请求 - 用
HttpRequestMessage自定义方法、头部和请求体 - 用
StringContent、JsonContent、FormUrlContent、MultipartFormDataContent组织请求体 - 用
readAsString()、readAsByteArray()、readAsStream()读取响应 - 用
DelegatingHandler扩展日志、鉴权、重试等横切逻辑 - 用
IHttpClientFactory在 DI 场景里统一管理客户端和底层处理器
使用建议
- 简单场景直接用
soulsoft_net_http.HttpClient - 已经接入 DI 时,优先使用
IHttpClientFactory - 响应对象优先用
try (response = ...)自动关闭 - 需要边收边处理响应时,用
readAsStream()
当前实现的一个注意点
按当前源码实现,默认的 HttpClientHandler() 会创建底层 ClientBuilder,并使用:
TrustAllTLS 验证模式75秒读超时
这更接近“开箱即可连通”的默认行为。
如果你的生产环境需要严格证书校验、代理或其他网络策略,应当显式自定义主处理器。