黑龙江物联网软件开发 使用 Swift 搭建一个 HTTP 代理
我将通过这篇著述胪陈一下如何用Swift搭建一个HTTP代理处事器。本文将使用Hummingbird[1]算作处事端的基本HTTP框架,以及使用AsyncHTTPClient[2]算作Swift的HTTP客户端来苦求预备处事。
1. 川崎前锋最早成立于1955年,球队前身为富士通足球俱乐部,是日本足球联盟元老俱乐部之一。由于部分原因俱乐部在上世纪遭到降级,直到2000年才重返顶级联赛。在2017年之后,队伍的整体表现日渐强大,先后在2017、2018、2020以及2021年获得联赛冠军。随后还夺得了日本天皇杯冠军、日本联赛杯冠军、以及日本超级杯。
什么是代理处事器代理处事器是一个搭载在客户端和另一个处事端(背面咱们成为预备处事端)的中间处事器,它从客户端转发音书到预备处事端,而且从预备处事端取得反应信息传回给客户端。在转发音书之前,它不错以某种面孔处理这些音书,相似,它也不错处理复返的反应。
让咱们试着构建一个在本文中,咱们将构建一个只将HTTP数据包转发到预备处事的代理处事器。您不错在这里找到本文的示例代码。
创建时势咱们使用Hummingbird模板时势[3] 现时最低版块适配 Swift5.5 算作咱们处事的运转模板。读者不错选拔clone这个存储库,或者奏凯点击Github时势主页上use this template按钮来创建咱们我方的存储库。用这个模板时势创建一个处事端而且启动它,不错使用一些戒指台选项和文献来设立咱们的运用。详见here[4]
增多 AsyncHTTPClient咱们将把AsyncHTTPClient算作依赖加入Package.swift以便咱们背面来使用
dependencies: 黑龙江物联网软件开发[ ... .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.6.0"), ],
然后在预备依赖也添加一下
targets: [ .executableTarget(name: "App", dependencies: [ ... .product(name: "AsyncHTTPClient", package: "async-http-client"), ],
咱们将把HTTPClient算作HBApplicatipn的扩张。这么浮浅咱们不停HTTPClient的人命周期以及在HTTPClient删除前调用syncShutdown行动。
extension HBApplication { var httpClient: HTTPClient { get { self.extensions.get(\.httpClient) } set { self.extensions.set(\.httpClient, value: newValue) { httpClient in try httpClient.syncShutdown() }} } }
当HBApplication关闭手艺会调用set内部的闭包。这意味着咱们当咱们援用了HBApplication,即使不使用HTTPClient,咱们也有权限去调用它
app 增多 middleware[中间件]咱们将把咱们的代理处事器算作中间件。中间件将取得一个苦求,然后将它发送到预备处事器而且从预备处事器取得反应信息。底下使咱们运转版块的中间件,它需要HTTPClient和预备处事器的URL两个参数。
struct HBProxyServerMiddleware: HBMiddleware { let httpClient: HTTPClient let target: String func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> { return httpClient.execute( request: request, eventLoop: .delegateAndChannel(on: request.eventLoop), logger: request.logger ) } }
现时咱们有了HTTPClient和HBProxyServerMiddleware中间件,咱们将它们加入设立文献HBApplication.configure。然后建立咱们代理处事地址为http://httpbin.org
func configure(_ args: AppArguments) throws { self.httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.eventLoopGroup)) self.middleware.add(HBProxyServerMiddleware(httpClient: self.httpClient, target: "http://httpbin.org")) }退换类型
当咱们完成上头的设施,构建会透露失败。因为咱们还需要退换Hummingbird和AsyncHTTPClient之间的请乞降反应类型。同期咱们需要兼并预备处事的URL到苦求里。
苦求退换为了将Hummingbird HBRequest转念为AsyncHTTPClient HTTPClient.Request,
原因: 咱们最初需要整理可能仍在加载的HBRequest的body信息,退换经由是异步的
不停有预备:是以它需要复返一个包含背面退换终结的EventLoopFuture,让咱们将退换函数放到HBRequest内部
extension HBRequest { func ahcRequest(host: String) -> EventLoopFuture<HTTPClient.Request> { // consume request body and then construct AHC Request once we have the // result. The URL for the request is the target server plus the URI from // the `HBRequest`. return self.body.consumeBody(on: self.eventLoop).flatMapThrowing { buffer in return try HTTPClient.Request( url: host + self.uri.description, method: self.method, headers: self.headers, body: buffer.map { .byteBuffer($0) } ) } } }反应信息装换
从HTTPClient.Response到HBResponse的退换超过约略
extension HTTPClient.Response { var hbResponse: HBResponse { return .init( status: self.status, headers: self.headers, body: self.body.map { HBResponseBody.byteBuffer($0) } ?? .empty ) } }
咱们现时将这两个退换设施加入HBProxyServerMiddleware的apply函数中。同期加入一些日记打印信息
func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> { // log request request.logger.info("Forwarding \(request.uri.path)") // convert to HTTPClient.Request, execute, convert to HBResponse return request.ahcRequest(host: target).flatMap { ahcRequest in httpClient.execute( request: ahcRequest, eventLoop: .delegateAndChannel(on: request.eventLoop), logger: request.logger ) }.map { response in return response.hbResponse } }
现时应该不错平常编译了。中间件将整理HBRequest的苦求体,将它转念为HTTPRequest.Request,然后使用HTTPClient将苦求转发给预备处事器。取得的反应信息会转念为HBResponse复返给运用。
运走运用,掀开网页掀开localhost:8080。咱们应该能看到咱们之前建立代理的httpbin.org网页信息
Streaming[流]上头的建立不口角常理思。它会恭候苦求竣工加载,然后才将苦求转发给预备处事端。同理反应转发亦然需要恭候反应竣工加载后才会转发。这镌汰了音书发送的效果,相似会导致苦求占用大量内存或者反应信息很大。
咱们不错通过流式传输请乞降反应负载来检阅这小数。一朝咱们有了它的头部,就脱手将苦求发送到预备处事,并在继承到主体部分时对其进行流式处理。访佛地,黑龙江物联网软件开发一朝咱们有了它的头,在另一个场地脱手发送反应。排斥对无缺苦求或反应的恭候将进步代理处事器的性能。
若是客户端和代理之间的通讯以及代理和预备处事之间的通讯以不同的速率运行,咱们仍然会遭遇内存问题。若是咱们继承数据的速率比处理数据的速率快,数据就会脱手备份。为了幸免这种情况发生,咱们需要大约施加背压以罢手读取稀奇的数据,直到咱们处理了饱和多的内存中的数据。有了这个,咱们不错将代理使用的内存量保捏在最低终结。
流式苦求流式传输苦求负载是一个超过约略的经由。本色上,它简化了构造 HTTPClient.Request 的经由因为咱们不需要恭候苦求竣工加载。咱们如何构造 HTTPClient.Request 主体将基于无缺的 HBRequest 是否照旧在内存中。若是咱们复返流苦求,则会自动运用背压,因为 Hummingbird 处事器框架会为咱们实行此操作。
func ahcRequest(host: String, eventLoop: EventLoop) throws -> HTTPClient.Request { let body: HTTPClient.Body? switch self.body { case .byteBuffer(let buffer): body = buffer.map { .byteBuffer($0) } case .stream(let stream): body = .stream { writer in // as we consume buffers from `HBRequest` we write them to // the `HTTPClient.Request`. return stream.consumeAll(on: eventLoop) { byteBuffer in writer.write(.byteBuffer(byteBuffer)) } } } return try HTTPClient.Request( url: host + self.uri.description, method: self.method, headers: self.headers, body: body ) }流式反应
流式反应需要一个解任 HTTPClientResponseDelegate 的class. 这将在 HTTPClient 反应可用时立即从反应中继承数据。反应正文是 ByteBuffers 体式. 咱们不错将这些 ByteBuffers 提供给 HBByteBufferStreamer. 咱们讲述的 HBResponse 是由这些流构造,而不是静态的 ByteBuffer。
若是咱们将苦求流与反应流代码迷惑起来,咱们的最终的 apply 函数应该是这么的
func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> { do { request.logger.info("Forwarding \(request.uri.path)") // create request let ahcRequest = try request.ahcRequest(host: target, eventLoop: request.eventLoop) // create response body streamer. maxSize is the maximum size of object it can process // maxStreamingBufferSize is the maximum size of data the streamer is allowed to have // in memory at any one time let streamer = HBByteBufferStreamer(eventLoop: request.eventLoop, maxSize: 2048*1024, maxStreamingBufferSize: 128*1024) // HTTPClientResponseDelegate for streaming bytebuffers from AsyncHTTPClient let delegate = StreamingResponseDelegate(on: request.eventLoop, streamer: streamer) // execute request _ = httpClient.execute( request: ahcRequest, delegate: delegate, eventLoop: .delegateAndChannel(on: request.eventLoop), logger: request.logger ) // when delegate receives head then signal completion return delegate.responsePromise.futureResult } catch { return request.failure(error) } }
你会珍摄到在上头的代码中咱们不恭候httpClient.execute. 这是因为若是咱们这么作念了,该函数将在不竭之前恭候系数反应主体在内存中。咱们但愿立即处理反应,因此咱们向托福添加了一个promise: 一朝咱们收到头部信息,就和会过保存头部笃定和流到HBResponse来竣事。EventLoopFuture这个 promise的是咱们从apply函数传回的。
我莫得在StreamingResponseDelegate这里包含代码,但您不错在无缺的示例代码中[5]找到它。
示例代码添加该示例代码[6]可能在上头的基础上作念了部分修改。
默许绑定地址端口是 8081 而不是 8080。大多数 Hummingbird 示例在 8080 上运行,因此要在这些示例傍边使用代理,它需要绑定到不同的端口。 我添加了一个位置选项,它允许咱们只转发来自特定基本 URL 的苦求 我为预备和位置添加了大喊行选项,因此不错在不重建运用圭臬的情况下改换这些选项 我删除了 host 标题或苦求,以便不错用正确的值填写 若是提供了 content-length 标头,则在退换流苦求时,我将其传递给 HTTPClient 流送器,以确保 content-length 为预备处事器的苦求正确建立标头。 备择有预备咱们不错使用 HummingbirdCore 代替 Hummingbird 算作代理处事器。这将提供一些稀奇的性能,因为它会删除稀奇的代码层,但会松手活泼性。添加任何稀奇的路由或中间件需要作念更多的使命。我有只使用HummingbirdCore代理处事器的示例代码在这里[7]。
虽然,另一种选拔是使用 Vapor。我思在 Vapor 中的竣事看起来与上头描摹的相配相似,应该不会太难。不外我会把它留给别东谈主。
参考贵寓[1]Hummingbird: https://github.com/hummingbird-project/hummingbird
[2]AsyncHTTPClient: https://github.com/swift-server/async-http-client
[3]Hummingbird模板时势: https://github.com/hummingbird-project/template
[4]here: https://opticalaberration.com/2021/12/hummingbird-template.html
[5]示例代码中: https://github.com/hummingbird-project/hummingbird-examples/blob/main/proxy-server/Sources/App/Middleware/StreamingResponseDelegate.swift
[6]示例代码: https://github.com/hummingbird-project/hummingbird-examples/tree/main/proxy-server
[7]在这里: https://github.com/hummingbird-project/hummingbird-examples/tree/main/proxy-server-core