此仓库是 Perfect HTTP/1、HTTP/2 和 FastCGI 服务器的依赖项。 请查看 Perfect HTTPServer 了解更多详情。
HTTP 库提供了一组枚举、结构体、协议和函数来处理与 HTTP 客户端的交互。 它为 URL 路由机制提供了具体的实现。 设置 HTTPServer 时,您需要导入此库才能使用路由函数。 通常
import PerfectHTTP
import PerfectHTTPServer
这是一个使用闭包块作为处理程序的路由声明示例
var routes = Routes()
routes.add(method: .get, uri: "/") {
request, response in
response.appendBody(string: "<html><title>Hello, world!</title><body>Hello, world!</body></html>")
response.completed()
}
处理程序可以是一个单独的函数,它接受一个 HTTPRequest、一个 HTTPResponse,并完成响应,或者交给另一个函数来完成。
func helloWorld(request: HTTPRequest, response: HTTPResponse) {
response.appendBody(string: "<html><title>Hello, world!</title><body>Hello, world!</body></html>")
.completed()
}
routes.add(method: .get, uri: "/hello", handler: helloWorld)
路由必须在服务器实例启动之前添加到该实例中。
try HTTPServer.launch(name: "my.server.ca", port: port, routes: routes)
在服务器开始侦听请求后,无法添加或修改路由。
在您的处理函数中,HTTPRequest 对象提供对所有客户端请求信息的访问。 这包括所有客户端标头、查询参数、POST 正文数据以及其他相关信息,例如客户端 IP 地址和 URL 变量。
HTTPRequest 将处理解析和解码所有 "application/x-www-form-urlencoded" 以及 "multipart/form-data" 内容类型的请求。 它将以原始、未解析的形式提供任何其他内容类型的数据。 在处理 multipart 表单数据时,HTTPRequest 将自动解码数据并为其中包含的任何文件上传创建临时文件。 这些文件将存在直到请求结束,之后它们将被自动删除。
在构建返回值时,HTTPResponse 对象包含所有传出的响应数据。 这包括 HTTP 状态代码和消息、HTTP 标头以及任何响应正文数据。 HTTPResponse 还包含将响应数据流式传输或推送块到客户端以及完成或终止请求的能力。
除了接受请求和响应对象的原始处理程序之外,Perfect-HTTP 路由还支持强类型处理程序,这些处理程序可以解码、接受或返回 Codable Swift 对象。
用于处理类型化路由的 API 与用于处理非类型化路由的 API 非常相似。 用于生成类型化路由的对象名为 TRoutes
和 TRoute
。 这些对象的含义和用法分别与 Routes
和 Route
对象密切对应。
这些对象的接口如下所示
/// A typed intermediate route handler parameterized on the input and output types.
public struct TRoutes<I, O> {
/// Input type alias
public typealias InputType = I
/// Output type alias
public typealias OutputType = O
/// Init with a base URI and handler.
public init(
baseUri u: String,
handler t: @escaping (InputType) throws -> OutputType)
/// Add a typed route to this base URI.
@discardableResult
public mutating func add<N>(_ route: TRoute<OutputType, N>) -> TRoutes
/// Add other intermediate routes to this base URI.
@discardableResult
public mutating func add<N>(_ route: TRoutes<OutputType, N>) -> TRoutes
/// Add a route to this object. The new route will take the output of this route as its input.
@discardableResult
public mutating func add<N: Codable>(
method m: HTTPMethod,
uri u: String,
handler t: @escaping (OutputType) throws -> N) -> TRoutes
}
/// A typed route handler.
public struct TRoute<I, O: Codable> {
/// Input type alias.
public typealias InputType = I
/// Output type alias.
public typealias OutputType = O
// Init with a method, uri, and handler.
public init(
method m: HTTPMethod,
uri u: String,
handler t: @escaping (InputType) throws -> OutputType)
/// Init with zero or more methods, a uri, and handler.
public init(
methods m: [HTTPMethod] = [.get, .post],
uri u: String,
handler t: @escaping (InputType) throws -> OutputType)
}
与 Routes
和 Route
对象一样,TRoutes
是一个中间处理程序,而 TRoute
是一个终端处理程序。
可以创建一个 TRoutes
处理程序,它接受 HTTPRequest 对象或任何其他类型的对象,这些对象可以从之前的处理程序传递下来。 通常创建的第一个 TRoutes
处理程序是接受 HTTPRequest 对象。 此处理程序反过来处理其输入,并返回提供给后续处理程序的某个对象。
一个 TRoute
处理程序接受某种类型的输入,并返回一个 Codable 对象。 此 codable 对象被序列化为 JSON 并返回给客户端。
这两种类型的处理程序的输入可以是 HTTPRequest、将请求正文解码为某个 Decodable 对象的结果,或者紧接之前的中间处理程序的返回值。 当将处理程序定义为接收 Decodable 对象时,HTTPRequest 正文将自动解码为该类型。 如果无法解码正文,则会引发错误,并将错误响应返回给客户端。 或者,可以将处理程序定义为接受 HTTPRequest 对象,但可以使用 HTTPRequest.decode
函数(如下所述)自行解码正文。
HTTPRequest 对象上的两个扩展有助于解码请求正文。
/// Extensions on HTTPRequest which permit the request body to be decoded to a Codable type.
public extension HTTPRequest {
/// Decode the request as the desired object.
func decode<A: Codable>() throws -> A
/// Identity decode. Used to permit generic code to operate with the HTTPRequest
func decode() throws -> Self
}
第一个函数会将正文解码为所需的 Codable 对象。 如果请求的 content-type 是 application/json,则正文将从该 JSON 解码。 否则,请求的 URL 编码的 GET 或 POST 参数将用于解码。 此外,URL 变量(稍后在本文档中描述)也将用于解码。 这允许将 GET/POST 参数和 URL 变量混合在一起,以便在解码对象时使用。
请注意,当从非 JSON 请求数据解码对象时,不支持嵌套的非整型对象。 在这种情况下,也不支持具有数组属性的对象。
如果在处理过程中,中间或终端类型化处理程序遇到错误,它们可以抛出 HTTPResponseError
。 初始化这些对象之一需要 HTTPResponseStatus
和问题的字符串描述。
/// A codable response type indicating an error.
public struct HTTPResponseError: Error, Codable, CustomStringConvertible {
/// The HTTP status for the response.
public let status: HTTPResponseStatus
/// Textual description of the error.
public let description: String
/// Init with status and description.
public init(
status s: HTTPResponseStatus,
description d: String)
}
Routes
上的扩展允许添加 TRoutes
或 TRoute
对象。
public extension Routes {
/// Add routes to this object.
mutating func add<I, O>(_ route: TRoutes<I, O>)
/// Add a route to this object.
mutating func add<I, O>(_ route: TRoute<I, O>)
}
以下示例显示了如何定义路由的 Codable 对象,以及如何将类型化路由添加到 Routes
对象。
在这个简短的示例中,"/api" 的中间处理程序将对请求执行一些筛选,以确保客户端已通过身份验证。 然后,它将向下一个处理程序(在本例中为终端)返回一个元组,该元组包含原始 HTTPRequest 对象以及包含从请求中提取的任何客户端 ID 的 SessionInfo
对象。 然后,终端处理程序 "/api/info/{id}" 将使用此信息来完成请求并返回响应。
struct SessionInfo: Codable {
//...could be an authentication token, etc.
let id: String
}
struct RequestResponse: Codable {
struct Address: Codable {
let street: String
let city: String
let province: String
let country: String
let postalCode: String
}
let fullName: String
let address: Address
}
// when handlers further down need the request you can pass it along. this is not necessary though
typealias RequestSession = (request: HTTPRequest, session: SessionInfo)
// intermediate handler for /api
func checkSession(request: HTTPRequest) throws -> RequestSession {
// one would check the request to make sure it's authorized
let sessionInfo: SessionInfo = try request.decode() // will throw if request does not include id
return (request, sessionInfo)
}
// terminal handler for /api/info/{id}
func userInfo(session: RequestSession) throws -> RequestResponse {
// return the response for this request
return .init(fullName: "Justin Trudeau",
address: .init(
street: "111 Wellington St",
city: "Ottawa",
province: "Ontario",
country: "Canada",
postalCode: "K1A 0A6"))
}
// root Routes object holding all other routes for this server
var routes = Routes()
// types routes object for the /api URI
var apiRoutes = TRoutes(baseUri: "/api", handler: checkSession)
// add terminal handler for the /info/{id} URI suffix
apiRoutes.add(method: .get, uri: "/info/{id}", handler: userInfo)
// add the typed routes to the root
routes.add(apiRoutes)
// add routes to server and launch
try HTTPServer.launch(name: "my.server.ca", port: port, routes: routes)
以下文档包含相关信息