Embassy

Build Status Carthage compatible SwiftPM compatible CocoaPods Swift Version Plaform GitHub license

用纯 Swift 编写的超轻量级异步 HTTP 服务器。

请阅读: 用于 iOS UI 测试的嵌入式 Web 服务器

另请参阅: 我们基于 Embassy 的轻量级 Web 框架 Ambassador

特性

示例

这是一个简单的例子,展示了 Embassy 的工作方式。

let loop = try! SelectorEventLoop(selector: try! KqueueSelector())
let server = DefaultHTTPServer(eventLoop: loop, port: 8080) {
    (
        environ: [String: Any],
        startResponse: ((String, [(String, String)]) -> Void),
        sendBody: ((Data) -> Void)
    ) in
    // Start HTTP response
    startResponse("200 OK", [])
    let pathInfo = environ["PATH_INFO"]! as! String
    sendBody(Data("the path you're visiting is \(pathInfo.debugDescription)".utf8))
    // send EOF
    sendBody(Data())
}

// Start HTTP server to listen on the port
try! server.start()

// Run event loop
loop.runForever()

然后您可以在浏览器中访问 http://[::1]:8080/foo-bar 并看到

the path you're visiting is "/foo-bar"

默认情况下,出于安全原因,服务器将仅绑定到 localhost 接口 (::1)。 如果您想通过外部网络访问您的服务器,您需要将绑定接口更改为所有地址

let server = DefaultHTTPServer(eventLoop: loop, interface: "::", port: 8080)

异步事件循环

要使用异步事件循环,您可以通过 environ 字典中的键 embassy.event_loop 获取它,并将其强制转换为 EventLoop。 例如,您可以创建一个 SWSGI 应用程序,该应用程序延迟 sendBody 调用,如下所示

let app = { (
    environ: [String: Any],
    startResponse: ((String, [(String, String)]) -> Void),
    sendBody: @escaping ((Data) -> Void)
) in
    startResponse("200 OK", [])

    let loop = environ["embassy.event_loop"] as! EventLoop

    loop.call(withDelay: 1) {
        sendBody(Data("hello ".utf8))
    }
    loop.call(withDelay: 2) {
        sendBody(Data("baby ".utf8))
    }
    loop.call(withDelay: 3) {
        sendBody(Data("fin".utf8))
        sendBody(Data())
    }
}

请注意,传递到 SWSGI 的函数只能在运行 EventLoop 的同一个线程中调用,它们都不是线程安全的,因此,您不应该使用 GCD 来延迟任何调用。 相反,您可以从 EventLoop 中使用一些方法,它们都是线程安全的

call(callback: (Void) -> Void)

尽快在事件循环中调用给定的回调

call(withDelay: TimeInterval, callback: (Void) -> Void)

安排给定的回调在 withDelay 秒后调用,然后在事件循环中调用它。

call(atTime: Date, callback: (Void) -> Void)

安排给定的回调在 atTime 在事件循环中调用。 如果给定的时间是过去或零,则此方法的工作方式与仅带有回调参数的 call 完全相同。

什么是 SWSGI (Swift Web Server Gateway Interface)?

SWSGI 是对 Python 的 WSGI (Web Server Gateway Interface) 的致敬。 它是一个网关接口,使 Web 应用程序能够与 HTTP 客户端通信,而无需了解 HTTP 服务器的实现细节。

它定义为

public typealias SWSGI = (
    [String: Any],
    @escaping ((String, [(String, String)]) -> Void),
    @escaping ((Data) -> Void)
) -> Void

environ

它是一个字典,包含有关请求的所有必要信息。 它基本上遵循 WSGI 标准,除了 wsgi.* 键将改为 swsgi.*。 例如

[
  "SERVER_NAME": "[::1]",
  "SERVER_PROTOCOL" : "HTTP/1.1",
  "SERVER_PORT" : "53479",
  "REQUEST_METHOD": "GET",
  "SCRIPT_NAME" : "",
  "PATH_INFO" : "/",
  "HTTP_HOST": "[::1]:8889",
  "HTTP_USER_AGENT" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
  "HTTP_ACCEPT_LANGUAGE" : "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4,zh-CN;q=0.2",
  "HTTP_CONNECTION" : "keep-alive",
  "HTTP_ACCEPT" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  "HTTP_ACCEPT_ENCODING" : "gzip, deflate, sdch",
  "swsgi.version" : "0.1",
  "swsgi.input" : (Function),
  "swsgi.error" : "",
  "swsgi.multiprocess" : false,
  "swsgi.multithread" : false,
  "swsgi.url_scheme" : "http",
  "swsgi.run_once" : false
]

要从正文中读取请求,您可以使用 swsgi.input,例如

let input = environ["swsgi.input"] as! SWSGIInput
input { data in
    // handle the body data here
}

当到达 EOF 时,会将一个空的 Data 传递到输入数据处理程序中。 另外请注意,如果未设置 swsgi.input 或将其设置为 nil,则不会读取请求正文。 您可以使用 swsgi.input 作为带宽控制,当您不想从客户端接收任何数据时,将其设置为 nil。

一些额外的 Embassy 服务器特定键是

startResponse

用于开始向客户端发送 HTTP 响应头的函数,第一个参数是带有消息的状态代码,例如 “200 OK”。 第二个参数是标头,作为键值元组的列表。

要响应 HTTP 标头,您可以这样做

startResponse("200 OK", [("Set-Cookie", "foo=bar")])

每个请求只能调用一次 startResponse,额外的调用将被简单地忽略。

sendBody

用于将正文数据发送到客户端的函数。 您需要首先调用 startResponse 才能调用 sendBody。 如果您没有首先调用 startResponse,则所有对 sendBody 的调用将被忽略。 要发送数据,您可以在这里这样做

sendBody(Data("hello".utf8))

要结束响应数据流,只需使用一个空的 Data 调用 sendBody 即可。

sendBody(Data())

安装

CocoaPods

要使用 CocoaPod 安装,请将 Embassy 添加到您的 Podfile 中

pod 'Embassy', '~> 4.1'

Carthage

要使用 Carthage 安装,请将 Embassy 添加到您的 Cartfile 中

github "envoy/Embassy" ~> 4.1

Package Manager

Package.swift 中添加此 Embassy repo,如下所示

import PackageDescription

let package = Package(
    name: "EmbassyExample",
    dependencies: [
        .package(url: "https://github.com/envoy/Embassy.git",
                 from: "4.1.4"),
    ]
)

您可以在此处阅读此示例项目