HTTP 客户端
HTTP 客户端是 Spire 框架的核心基础设施,提供了高性能、可扩展的 HTTP 通信功能。通过 HttpClientFactory 模式,有效管理 HTTP 请求处理和资源分配,提高应用程序的网络通信性能和可维护性。
快速启动
只需要以下 3 个步骤即可开始使用 HTTP 客户端:
import spire_net_http.*
import spire_extensions_injection.*
main() {
// 1. 创建并配置服务集合
let services = ServiceCollection()
services.addHttpClient<HttpClient, HttpClient>()
// 2. 构建服务提供者并获取工厂
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
// 3. 创建客户端并发送请求
let client = factory.createClient()
let content = client.getString("https://httpbin.org/get")
println(content)
}HTTP 客户端
手动创建
注册默认客户端,可不使用依赖注入容器,最小化依赖:
import spire_net_http.*
main() {
let client = HttpClient()
println(client.getString("https://httpbin.org/get"))
}注意
生产环境不建议使用单例模式,而是配合容器使用,以防止内存泄漏和DNS缓存问题。
说明
框架内部的DNS缓存清除机制实现了同步DNS重定向,目标域名指向IP地址发生改变时,HTTP请求地址也会随之改变,无须重启服务或改写IP地址编码
基本使用
最常用的 GET 调用与多种响应读取方式:状态、字符串、字节、流
import spire_net_http.*
main() {
let client = HttpClient()
// 基本 GET 请求
let response = client.get("https://httpbin.org/get")
println("Status: ${response.status}")
println("Content: ${response.content.readAsString()}")
// 获取字符串响应
let content = client.getString("https://httpbin.org/json")
println("String content: ${content}")
// 获取字节数组响应
let bytes = client.getByteArray("https://httpbin.org/bytes/100")
println("Bytes length: ${bytes.size}")
// 获取流响应
let stream = client.getStream("https://httpbin.org/stream/3")
println("Stream response received successfully")
}内容类型
JsonContent
发送 JSON 正文,请务必设置 Content-Type: application/json
import spire_net_http.*
import stdx.serialization.serialization.*
main() {
let client = HttpClient()
// POST JSON
let request = HttpRequestMessage(HttpMethod.Post, "http://jsonplaceholder.typicode.com/posts")
// 构建一个需要实现序列化接口的数据模型
let data = TokenModel(accessKey: "xxx", accessSecret: "xxx")
request.content = JsonContent.create(data)
let response = client.send(request)
println("POST Status: ${response.status}")
println("POST Response: ${response.content.readAsString()}")
}说明
序列化数据模型实现方式查阅官方文档,或参考如下:
public class TokenModel <: Serializable<TokenModel> {
var accessKey: String = ""
var accessSecret: String = ""
public init(accessKey!: String, accessSecret!: String) {
this.accessKey = accessKey
this.accessSecret = accessSecret
}
// 必须实现的序列化方法
public func serialize(): DataModel {
return DataModelStruct().add(field<String>("accessKey", accessKey)).add(field<String>("accessSecret", accessSecret))
}
// 必须实现的反序列化方法
public static func deserialize(dm: DataModel): TokenModel {
let dms = match (dm) {
case data: DataModelStruct => data
case _ => throw UnsupportedException("Invalid data model")
}
let result = TokenModel(accessKey: "", accessSecret: "")
result.accessKey = String.deserialize(dms.get("accessKey"))
result.accessSecret = String.deserialize(dms.get("accessSecret"))
return result
}
}FormUrlContent
发送表单数据,适用于简单字段提交
import spire_net_http.*
main() {
let client = HttpClient()
// POST 表单数据
let request = HttpRequestMessage(HttpMethod.Post, "https://httpbin.org/post")
let data = [
("username", "john_doe"),
("password", "secret123"),
("remember", "true")
]
request.content = FormUrlContent(data)
let response = client.send(request)
println("Form Status: ${response.status}")
}请求管道(Handlers)
请求管道由多个处理器 (DelegatingHandler) 按顺序组成的单向链表组成。最后一个处理器HttpClientHandler负责实际发送请求。我们可以使用处理器来实现单一职责和代码复用。 比如我们可以定义处理器来专门处理授权或者错误。
执行流程

代码演示
我们可以通过实现DelegatingHandler抽象类来定义自定义处理器,例如:
代码示例:
import spire_net_http.*
main() {
// primaryHandler 是真实发请求的末端处理器
let primary = HttpClientHandler()
// 创建请求管道
let pipe = Middleware1(Middleware2(Middleware3(primary)))
let client = HttpClient(pipe, false)
let response = client.get("http://httpbin.org/get")
println("Done: ${response.status}")
}
// 处理器1
public class Middleware1 <: DelegatingHandler {
init(next: HttpMessageHandler) {
super(next)
}
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
println("middleware1 start")
let response = super.send(request)
println("middleware1 endle")
return response
}
}
// 处理器2
public class Middleware2 <: DelegatingHandler {
init(next: HttpMessageHandler) {
super(next)
}
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
println("middleware2 start")
let response = super.send(request)
println("middleware2 endle")
return response
}
}
// 处理器3
public class Middleware3 <: DelegatingHandler {
init(next: HttpMessageHandler) {
super(next)
}
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
println("middleware3 start")
let response = super.send(request)
println("middleware3 endle")
return response
}
}容器集成
我们通过与容器集成来实现依赖注入的同时,还可以更加便捷的构建请求管道和设置请求管道的生命周期,来避免内存泄漏和DNS缓存失效问题。
注册和解析
利用http客户端工厂将服务集合中的配置好的客户端实例化,工厂模式下创建的默认客户端是互相独立的实例
import spire_net_http.*
import spire_extensions_injection.*
main() {
let services = ServiceCollection()
// 注册 HTTP 客户端
services.addHttpClientCore()
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
// 使用工厂创建客户端
let client1 = factory.createClient()
// 直接解析
let client2 = provider.getOrThrow<HttpClient>()
}非命名客户端
import spire_net_http.*
import spire_extensions_injection.*
main() {
let services = ServiceCollection()
// 注册非命名客户端核心与处理器
services.addHttpClientCore()
services.addTransient<LoggingHandler, LoggingHandler>()
// 配置非命名客户端
services.configureHttpClientDefaults { builder =>
// 配置生命周期
builder.setHandlerLifetime(Duration.minute * 2)
builder.addHttpMessageHandler<LoggingHandler>()
}
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
// 创建非命名客户端
let client = factory.createClient()
}
class LoggingHandler <: DelegatingHandler {
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
println("Request: ${request.method} ${request.requestUri}")
let response = super.send(request)
println("Response: ${response.status}")
return response
}
}注意
非命名客户端共享同一个请求管道和配置
命名客户端
命名客户端非常适合多租户的场景下使用
import spire_extensions_injection.*
main() {
let services = ServiceCollection()
// 配置命名客户端(返回 HttpClientBuilder 再配置)
services.addHttpClient("tenant")
.setHandlerLifetime(Duration.minute * 2)
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
// 创建tenant客户端
let client = factory.createClient("tenant")
}注意
命名客户端与非命名客户端之间的区别是:命名客户端拥有独立的请求管道和配置
类型化客户端
按领域封装 HttpClient,对外暴露清晰的业务方法
import spire_extensions_injection.*
import spire_net_http.*
import stdx.encoding.url.*
main() {
let services = ServiceCollection()
// 使用命名客户端配置管道,名称为第一个泛型参数的完全限定名
services.addHttpClient<OpenAiClient, OpenAiClient>()
.configureHttpClient { client =>
client.baseAddress = URL.parse("https://api.siliconflow.cn/v1")
}
let provider = services.build()
let client = provider.getOrThrow<OpenAiClient>()
let response = client.chat()
println("Response: ${response.status}")
println("Response: ${response.content.readAsString()}")
return 0
}
// 类型化客户端:封装 HttpClient
class OpenAiClient {
public OpenAiClient(let client: HttpClient) { }
public func chat(): HttpResponseMessage {
let content = StringContent("Hello, This is Spire!")
return client.post("/chat/completions", content)
}
}请求管道
import spire_net_http.*
import spire_extensions_injection.*
main() {
let services = ServiceCollection()
// 注册处理器
services.addTransient<Logging1DelegatingHandler, Logging1DelegatingHandler>()
services.addTransient<Logging2DelegatingHandler, Logging2DelegatingHandler>()
// 配置请求管道
services.configureHttpClientDefaults {builder => builder
.addHttpMessageHandler<Logging1DelegatingHandler>()
.addHttpMessageHandler<Logging2DelegatingHandler>()
}
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
let client = factory.createClient()
}
class Logging1DelegatingHandler <: DelegatingHandler {
public override func send(request: HttpRequestMessage) {
//println("Logging1:start...")
let response = super.send(request)
//println("Logging1:end...")
return response
}
}
class Logging2DelegatingHandler <: DelegatingHandler {
public override func send(request: HttpRequestMessage) {
//println("Logging2:start...")
let response = super.send(request)
//println("Logging2:end...")
return response
}
}生命周期
import std.runtime.*
import spire_extensions_injection.*
import spire_net_http.*
main() {
let services = ServiceCollection()
services.addTransient<LifetimeHandler, LifetimeHandler>()
services.addHttpClient("short-lived")
.addHttpMessageHandler<LifetimeHandler>()
.setHandlerLifetime(Duration.second * 5)
let provider = services.build()
let factory = provider.getOrThrow<IHttpClientFactory>()
// 弃元通知gc尽快回收
let _ = factory.createClient("short-lived")
gc()
// 等待生命周期过期
sleep(Duration.second * 10)
}
class LifetimeHandler <: DelegatingHandler {
protected override func send(request: HttpRequestMessage): HttpResponseMessage {
return super.send(request)
}
protected override func close(closing: Bool) {
println("'LifetimeHandler' closed")
super.close(closing)
}
}回收机制
当客户端过期,对其进行标记而不销毁对象,回调外部处理函数将过期客户端从激活队列移出,封装为可跟踪状态的 ExpiredHandlerTrackingEntry(带 WeakRef),并移入过期队列,等待清理程序处理
清理程序会定时检查循环清理,每 5 秒触发一次清理操作
注意
框架通过 WeakRef“观察”对象是否已无强引用;真正的内存回收由运行时 GC 决定。
到期只是进入“过期队列”,不是立即销毁;销毁前提是 WeakRef 指向对象已被 GC 判定为可回收(无强引用)。
最佳实践
性能优化建议
- 正确设置生命周期:setHandlerLifetime 在复用与刷新间权衡;过短增加建链开销,过长可能滞留资源
- 避免持有多余强引用:不把 HttpClient 缓存到全局静态结构,交由 DI/工厂管理,利于过期与清理
- 异步并发要收集句柄:spawn 后收集 Future 并在末尾 get(),确保任务完成与异常可见
架构设计
- 使用命名客户端隔离配置:不同后端/租户/功能使用不同名称,配置独立(基址、处理器、超时、生命周期)
- 类型化客户端封装领域 API:业务代码依赖服务(类型化客户端)而非裸 HttpClient,便于测试与复用
- 统一默认配置再按需覆盖:用 configureHttpClientDefaults 设定全局默认;命名客户端按需覆盖,避免重复
- 处理器用 DI 注册:自定义 DelegatingHandler 先注册到容器,再通过 addHttpMessageHandler<T>() 引入
开发建议
- 基址与相对路径:相对路径需配置 baseAddress;传字符串 URL 时请用完整 URL,避免解析差异