加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_丽江站长网 (http://www.0888zz.com/)- 科技、建站、数据工具、云上网络、机器学习!
当前位置: 首页 > 服务器 > 系统 > 正文

借助Swift搭建一个HTTP代理

发布时间:2022-06-20 16:10:01 所属栏目:系统 来源:互联网
导读:前言 我将通过这篇文章详述一下如何用Swift搭建一个HTTP代理服务器。本文将使用Hummingbird[1]作为服务端的基本HTTP框架,以及使用AsyncHTTPClient[2]作为Swift的HTTP客户端来请求目标服务。 增加 AsyncHTTPClient 我们将把AsyncHTTPClient作为依赖加入Packa
  前言
  我将通过这篇文章详述一下如何用Swift搭建一个HTTP代理服务器。本文将使用Hummingbird[1]作为服务端的基本HTTP框架,以及使用AsyncHTTPClient[2]作为Swift的HTTP客户端来请求目标服务。
 
 
  增加 AsyncHTTPClient
  我们将把AsyncHTTPClient作为依赖加入Package.swift以便我们后面来使用
 
  复制
  dependencies: [
      ...
      .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.6.0"),
  ],
  1.
  2.
  3.
  4.
  然后在目标依赖也添加一下
 
  复制
  targets: [
      .executableTarget(name: "App",
          dependencies: [
              ...
              .product(name: "AsyncHTTPClient", package: "async-http-client"),
          ],
  1.
  2.
  3.
  4.
  5.
  6.
  我们将把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()
          }}
      }
  }
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  当HBApplication关闭时候会调用set里面的闭包。这意味着我们当我们引用了HBApplication,即使不使用HTTPClient,我们也有权限去调用它
 
  增加 middleware[中间件]
  我们将把我们的代理服务器作为中间件。中间件将获取一个请求,然后将它发送到目标服务器并且从目标服务器获取响应信息。下面使我们初始版本的中间件,它需要HTTPClient和目标服务器的URL两个参数。
 
 
  复制
  struct HBProxyServerMiddleware: HBMiddleware {
      let httpClient: HTTPClient
      let target: String
   
 
  现在我们有了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"))
  }
  1.
  2.
  3.
  4.
  转换类型
  当我们完成上面的步骤,构建会显示失败。因为我们还需要转换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`.
 
  响应信息装换
  从HTTPClient.Response到HBResponse的转换相当简单
 
  复制
  extension HTTPClient.Response {
      var hbResponse: HBResponse {
   我们现在将这两个转换步骤加入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
   现在应该可以正常编译了。中间件将整理HBRequest的请求体,将它转化为HTTPRequest.Request,然后使用HTTPClient将请求转发给目标服务器。获取的响应信息会转化为HBResponse返回给应用。
 
  运行应用,打开网页打开localhost:8080。我们应该能看到我们之前设置代理的httpbin.org网页信息
 
  Streaming[流]
  上面的设置不是非常理想。它会等待请求完全加载,然后才将请求转发给目标服务端。同理响应转发也是需要等待响应完全加载后才会转发。这降低了消息发送的效率,同样会导致请求占用大量内存或者响应信息很大。
 
  复制
  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))
     
  流式响应
  流式响应需要一个遵循 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(
 
  你会注意到在上面的代码中我们不等待httpClient.execute. 这是因为如果我们这样做了,该函数将在继续之前等待整个响应主体在内存中。我们希望立即处理响应,因此我们向委托添加了一个promise: 一旦我们收到头部信息,就会通过保存头部详情和流到HBResponse来实现。EventLoopFuture这个 promise的是我们从apply函数传回的。
 
  我没有在StreamingResponseDelegate这里包含代码,但您可以在完整的示例代码中[5]找到它。
 
  示例代码添加
  该示例代码[6]可能在上面的基础上做了部分修改。
 
  默认绑定地址端口是 8081 而不是 8080。大多数 Hummingbird 示例在 8080 上运行,因此要在这些示例旁边使用代理,它需要绑定到不同的端口。
  我添加了一个位置选项,它允许我们只转发来自特定基本 URL 的请求
  我为目标和位置添加了命令行选项,因此可以在不重建应用程序的情况下更改这些选项
  我删除了 host 标题或请求,以便可以用正确的值填写
  如果提供了 content-length 标头,则在转换流请求时,我将其传递给 HTTPClient 流送器,以确保 content-length 为目标服务器的请求正确设置标头。

(编辑:应用网_丽江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读