一个基于 SWSGI 的超轻量级 Swift Web 框架
这是一个如何使用 Ambassador 和 Embassy 作为 HTTP 服务器来模拟 API 端点的示例。
import Embassy
import EnvoyAmbassador
let loop = try! SelectorEventLoop(selector: try! KqueueSelector())
let router = Router()
let server = DefaultHTTPServer(eventLoop: loop, port: 8080, app: router.app)
router["/api/v2/users"] = DelayResponse(JSONResponse(handler: { _ -> Any in
return [
["id": "01", "name": "john"],
["id": "02", "name": "tom"]
]
}))
// Start HTTP server to listen on the port
try! server.start()
// Run event loop
loop.runForever()
然后你可以访问 http://[::1]:8080/api/v2/users 在浏览器中,或者使用 HTTP 客户端 GET 该 URL 并查看
[
{
"id" : "01",
"name" : "john"
},
{
"id" : "02",
"name" : "tom"
}
]
Router
允许你将不同的路径映射到不同的 WebApp
。 就像你在前面的例子中看到的那样,要将路径 /api/v2/users
路由到我们的响应处理程序,你只需将所需的路径设置为 WebApp
的值
let router = Router()
router["/api/v2/users"] = DelayResponse(JSONResponse(handler: { _ -> Any in
return [
["id": "01", "name": "john"],
["id": "02", "name": "tom"]
]
}))
并将 router.app
作为 SWSGI 接口传递给 HTTP 服务器。 当访问的路径未找到时,将使用 router.notFoundResponse
,它只返回 404。 你可以覆盖 notFoundResponse
以自定义未找到时的行为。
你还可以使用正则表达式映射 URL。 例如,你可以这样写
let router = Router()
router["/api/v2/users/([0-9]+)"] = DelayResponse(JSONResponse(handler: { environ -> Any in
let captures = environ["ambassador.router_captures"] as! [String]
return ["id": captures[0], "name": "john"]
}))
然后,所有 URL 匹配 /api/v2/users/([0-9]+)
正则表达式的请求都将被路由到这里。 对于所有匹配组,它们将作为字符串数组传递到具有键 ambassador.router_captures
的环境中。
DataResponse
是一个用于发送数据的助手。 例如,假设你想创建一个端点来返回状态代码 500,你可以这样做
router["/api/v2/return-error"] = DataResponse(statusCode: 500, statusMessage: "server error")
状态默认为 200 OK
,内容类型默认为 application/octet-stream
,它们都可以通过 init 参数覆盖。 你还可以提供自定义标头和一个用于返回数据的处理程序。 例如
router["/api/v2/xml"] = DataResponse(
statusCode: 201,
statusMessage: "created",
contentType: "application/xml",
headers: [("X-Foo-Bar", "My header")]
) { environ -> Data in
return Data("<xml>who uses xml nowadays?</xml>".utf8)
}
如果你希望以异步方式发送 body,你也可以使用另一个带有额外 sendData
函数作为参数的 init
router["/api/v2/xml"] = DataResponse(
statusCode: 201,
statusMessage: "created",
contentType: "application/xml",
headers: [("X-Foo-Bar", "My header")]
) { (environ, sendData) in
sendData(Data("<xml>who uses xml nowadays?</xml>".utf8))
}
请注意,与 SWSGI 的 sendBody
不同,sendData
只需要用整个数据块调用一次。
与 DataResponse
几乎相同,只是它接受 Any
而不是字节,并将该对象转储为 JSON 格式并响应你。 例如
router["/api/v2/users"] = JSONResponse() { _ -> Any in
return [
["id": "01", "name": "john"],
["id": "02", "name": "tom"]
]
}
DelayResponse
是一个 **装饰器** 响应,它会延迟给定的响应一段时间。 在现实世界中,总是会有网络延迟,为了模拟延迟,DelayResponse
非常有帮助。 要延迟响应,只需执行
router["/api/v2/users"] = DelayResponse(JSONResponse(handler: { _ -> Any in
return [
["id": "01", "name": "john"],
["id": "02", "name": "tom"]
]
}))
默认情况下,它会随机延迟响应。 你可以通过传递 delay
参数来修改它。 比如,如果你想延迟 10 秒,你可以这样做
router["/api/v2/users"] = DelayResponse(JSONResponse(handler: { _ -> Any in
return [
["id": "01", "name": "john"],
["id": "02", "name": "tom"]
]
}), delay: .delay(10))
可用的延迟选项有
要从请求中读取 POST body 或任何其他 HTTP body,你需要使用 SWSGI 的 environ
参数中提供的 swsgi.input
函数。 例如,你可以这样做
router["/api/v2/users"] = JSONResponse() { environ -> Any in
let input = environ["swsgi.input"] as! SWSGIInput
input { data in
// handle the data stream here
}
}
这样做并不难,但是,数据以流的形式传入,例如
在大多数情况下,你不会喜欢手动处理数据流。 要等待所有数据接收完毕并一次性处理它们,你可以使用 DataReader
。 例如
router["/api/v2/users"] = JSONResponse() { environ -> Any in
let input = environ["swsgi.input"] as! SWSGIInput
DataReader.read(input) { data in
// handle the whole data here
}
}
与 DataReader
类似,除了读取整个数据块之外,JSONReader
还会将其解析为 JSON 格式。 这是你如何做的
router["/api/v2/users"] = JSONResponse() { environ -> Any in
let input = environ["swsgi.input"] as! SWSGIInput
JSONReader.read(input) { json in
// handle the json object here
}
}
URLParametersReader
等待接收所有数据,并将它们一次性解析为 URL 编码参数,如 foo=bar&eggs=spam
。 这些参数将作为键值对数组 (String, String)
传递。
router["/api/v2/users"] = JSONResponse() { environ -> Any in
let input = environ["swsgi.input"] as! SWSGIInput
URLParametersReader.read(input) { params in
// handle the params object here
}
}
如果你想,你也可以使用 URLParametersReader.parseURLParameters
来解析 URL 编码的参数字符串。 像这样
let params = URLParametersReader.parseURLParameters("foo=bar&eggs=spam")
要使用 CocoaPod 安装,请将 Embassy 添加到你的 Podfile
pod 'EnvoyAmbassador', '~> 4.0'
要使用 Carthage 安装,请将 Ambassador 添加到你的 Cartfile
github "envoy/Ambassador" ~> 4.0
请注意,你应该导入 Ambassador
而不是 EnvoyAmbassador
。 我们使用 EnvoyAmbassador
来进行 Cocoapods,仅仅是因为名称 Ambassador
已经被占用。
要做到这一点,请将 repo 添加到 Package.swift
,像这样
import PackageDescription
let package = Package(
name: "AmbassadorExample",
dependencies: [
.package(url: "https://github.com/envoy/Ambassador.git",
from: "4.0.0"),
]
)