用纯 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
中使用一些方法,它们都是线程安全的
尽快在事件循环中调用给定的回调
安排给定的回调在 withDelay
秒后调用,然后在事件循环中调用它。
安排给定的回调在 atTime
在事件循环中调用。 如果给定的时间是过去或零,则此方法的工作方式与仅带有回调参数的 call
完全相同。
SWSGI 是对 Python 的 WSGI (Web Server Gateway Interface) 的致敬。 它是一个网关接口,使 Web 应用程序能够与 HTTP 客户端通信,而无需了解 HTTP 服务器的实现细节。
它定义为
public typealias SWSGI = (
[String: Any],
@escaping ((String, [(String, String)]) -> Void),
@escaping ((Data) -> Void)
) -> Void
它是一个字典,包含有关请求的所有必要信息。 它基本上遵循 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 服务器特定键是
embassy.connection
- 请求的 HTTPConnection
对象embassy.event_loop
- EventLoop
对象embassy.version
- Embassy 的版本,例如 3.0.0
用于开始向客户端发送 HTTP 响应头的函数,第一个参数是带有消息的状态代码,例如 “200 OK”。 第二个参数是标头,作为键值元组的列表。
要响应 HTTP 标头,您可以这样做
startResponse("200 OK", [("Set-Cookie", "foo=bar")])
每个请求只能调用一次 startResponse
,额外的调用将被简单地忽略。
用于将正文数据发送到客户端的函数。 您需要首先调用 startResponse
才能调用 sendBody
。 如果您没有首先调用 startResponse
,则所有对 sendBody
的调用将被忽略。 要发送数据,您可以在这里这样做
sendBody(Data("hello".utf8))
要结束响应数据流,只需使用一个空的 Data 调用 sendBody
即可。
sendBody(Data())
要使用 CocoaPod 安装,请将 Embassy 添加到您的 Podfile 中
pod 'Embassy', '~> 4.1'
要使用 Carthage 安装,请将 Embassy 添加到您的 Cartfile 中
github "envoy/Embassy" ~> 4.1
在 Package.swift
中添加此 Embassy repo,如下所示
import PackageDescription
let package = Package(
name: "EmbassyExample",
dependencies: [
.package(url: "https://github.com/envoy/Embassy.git",
from: "4.1.4"),
]
)
您可以在此处阅读此示例项目。