轉(zhuǎn)載請(qǐng)附原文鏈接:http://blog.fandong.me/2017/08/18/iOS-SwiftVaporWeb22/
前言
之前一直有做Java后臺(tái)開發(fā)的興趣髓抑,可是想到要看好多的Java教程焰扳,作為一個(gè)iOS開發(fā)者,我放棄了使套,
后來從朋友韓云智VL那里知道了這個(gè)框架,竟是用Swift寫的,不得不說罩锐,它燃起了我的興趣。
Vapor是一個(gè)基于Swift開發(fā)的服務(wù)端框架卤唉,可以工作于iOS涩惑,Mac OS,Ubuntu搬味。
為了配合Swift部署到服務(wù)器,我把ECS的服務(wù)器系統(tǒng)改為Ubuntu16.04境氢。
中間件
中間件是任何現(xiàn)代Web框架的重要組成部分,它允許你在客戶端和服務(wù)器之間經(jīng)過時(shí)修改請(qǐng)求和響應(yīng).
您可以將中間件看作連接您的服務(wù)器和請(qǐng)求您的網(wǎng)絡(luò)應(yīng)用程序的客戶端的邏輯.
版本中間件
例如,讓我們創(chuàng)建一個(gè)中間件,對(duì)每個(gè)響應(yīng)添加我們的API版本,中間件看起來會(huì)是這樣的:
import HTTP
final class VersionMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) throws -> Response {
let response = try next.respond(to: request)
response.headers["Version"] = "API v1.0"
return response
}
}
我們將這個(gè)中間件提供給我們的Droplet
容器
import Vapor
let config = try Config()
config.addConfigurable(middleware: VersionMiddleware(), name: "version")
let drop = try Droplet(config)
你現(xiàn)在可以再配置文件中啟用和禁用此中間件,只需要添加
version
到您的droplet.json
中的middleware
數(shù)組,請(qǐng)查看配置文件章節(jié)
你可以想見,我們的版本中間件就在連接客戶端和我們的服務(wù)器中間,訪問我們的服務(wù)器每一個(gè)請(qǐng)求和響應(yīng)都必須經(jīng)過這個(gè)中間件鏈.
分解
我們一行一行的分解中間件
let response = try next.respond(to: request)
由于VersionMiddleware
在這個(gè)例子中沒有修改請(qǐng)求,所以我們要求下一個(gè)中間件來響應(yīng)該請(qǐng)求,鏈條一直下降到Droplet
,然后回到發(fā)送給客戶端的響應(yīng).
response.headers["Version"] = "API v1.0"
然后我們定義一個(gè)包含版本的請(qǐng)求頭的響應(yīng).
return response
返回響應(yīng),并將備份任何剩余的中間件,返回給客戶端.
請(qǐng)求
中間件也可以被修改或與請(qǐng)求交互
func respond(to request: Request, chainingTo next: Responder) throws -> Response {
guard request.cookies["token"] == "secret" else {
throw Abort(.badRequest)
}
return try next.respond(to: request)
}
這個(gè)中間件將要求該請(qǐng)求的cookie具有一個(gè)token
鍵值等于secret
或其他的,請(qǐng)求將被終止.
錯(cuò)誤
中間件是捕獲程序中任意位置錯(cuò)誤的完美地方,當(dāng)您讓中間件捕獲錯(cuò)誤時(shí),您可以從路由閉包中刪除大量重復(fù)的邏輯,看看下面的例子:
enum FooError: Error {
case fooServiceUnavailable
}
假設(shè)您定義了自定義錯(cuò)誤或您正在使用的的其中一個(gè)APIthrows
,拋出的錯(cuò)誤必須被捕獲,否則最終將作為用戶意外的內(nèi)部服務(wù)器錯(cuò)誤(500),最明顯的解決方案就是在路由閉包中捕獲錯(cuò)誤
app.get("foo") { request in
let foo: Foo
do {
foo = try getFooFromService()
} catch {
throw Abort(.badRequest)
}
// continue with Foo object
}
這個(gè)解決方案是有效的,但是如果有多個(gè)路由需要處理這個(gè)錯(cuò)誤,他將會(huì)產(chǎn)生重復(fù)代碼,幸運(yùn)的是,這個(gè)錯(cuò)誤可以在中間件中捕獲
final class FooErrorMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) throws -> Response {
do {
return try next.respond(to: request)
} catch FooError.fooServiceUnavailable {
throw Abort(
.badRequest,
reason: "Sorry, we were unable to query the Foo service."
)
}
}
}
我們只需要添加這個(gè)中間件到我們的Droplet的配置文件中.
config.addConfigurable(middleware: FooErrorMiddleware(), name: "foo-error")
提示
不要忘記在droplet.json
文件中啟用中間件
現(xiàn)在我們的路由閉包看起來好多了,我們也不必?fù)?dān)心代碼的重復(fù)了
app.get("foo") { request in
let foo = try getFooFromService()
// continue with Foo object
}
路由組
更細(xì)致的來說,中間件可以應(yīng)用于特定的路由組.
let authed = drop.grouped(AuthMiddleware())
authed.get("secure") { req in
return Secrets.all().makeJSON()
}
添加到authed
組的任何內(nèi)容都必須通過AuthMiddleware
.因此,我們可以假定所有訪問/secure
的流量已經(jīng)被授權(quán)了,了解更多請(qǐng)查看路由
配置
你可以使用配置文件來啟用或禁用中間件,如果你有中間件,例如,僅在生產(chǎn)環(huán)境中運(yùn)行,這將非常有用.
添加可配置的中間件,像下面這樣
let config = try Config()
config.addConfigurable(middleware: myMiddleware, name: "my-middleware")
let drop = Droplet(config)
然后,在Config/droplet.json
文件中,添加my-middleware
到middleware
數(shù)組中.
{
...
"middleware": {
...
"my-middleware",
...
},
...
}
如果添加的中間件的名稱出現(xiàn)在中間件陣列中,那么當(dāng)應(yīng)用程序啟動(dòng)時(shí),它將被添加到服務(wù)器的中間件.
按照中間件中的順序
手動(dòng)
如果你不想使用配置文件,你也可以對(duì)中間件進(jìn)行硬編碼.
import Vapor
let versionMiddleware = VersionMiddleware()
let drop = try Droplet(middleware: [versionMiddleware])
高級(jí)
擴(kuò)展
中間件需要與請(qǐng)求/響應(yīng)的擴(kuò)展和存儲(chǔ)有很好的配對(duì)關(guān)系,這個(gè)例子給你還在那時(shí)了如何根據(jù)客戶端的類型為模型動(dòng)態(tài)的返回HTML或JSON響應(yīng)
中間件
final class PokemonMiddleware: Middleware {
let view: ViewProtocol
init(_ view: ViewProtocol) {
self.view = view
}
func respond(to request: Request, chainingTo next: Responder) throws -> Response {
let response = try next.respond(to: request)
if let pokemon = response.pokemon {
if request.accept.prefers("html") {
response.view = try view.make("pokemon.mustache", pokemon)
} else {
response.json = try pokemon.makeJSON()
}
}
return response
}
}
extension PokemonMiddleware: ConfigInitializable {
convenience init(config: Config) throws {
let view = try config.resolveView()
self.init(view)
}
}
響應(yīng)
延伸到Response
.
extension Response {
var pokemon: Pokemon? {
get { return storage["pokemon"] as? Pokemon }
set { storage["pokemon"] = newValue }
}
}
在這個(gè)例子中,我們給響應(yīng)添加一個(gè)新的屬性來持有一個(gè)口袋對(duì)象,如果中間件發(fā)現(xiàn)了一個(gè)包含口袋對(duì)象的響應(yīng),它將動(dòng)態(tài)的檢查客戶端是否是支持HTML的,如果客戶端是一個(gè)像Safari的瀏覽器,支持HTML,它將會(huì)發(fā)揮一個(gè)Mustache視圖,,如果客戶端不支持HTML,它將會(huì)返回JSON
使用方法
你的閉包現(xiàn)在應(yīng)該看起來這樣
import Vapor
let config = try Config()
config.addConfigurable(middleware: PokemonMiddleware.init, name: "pokemon")
let drop = try Droplet(config)
drop.get("pokemon", Pokemon.self) { request, pokemon in
let response = Response()
response.pokemon = pokemon
return response
}
提示
別忘記添加pokemon
到你的droplet.json
的中間件數(shù)組
Response Representable
如果你想更進(jìn)一步,你可以使Pokemon
遵循ResponseRepresentable
import HTTP
extension Pokemon: ResponseRepresentable {
func makeResponse() throws -> Response {
let response = Response()
response.pokemon = self
return response
}
}
現(xiàn)在你的閉包就大大簡化了
drop.get("pokemon", Pokemon.self) { request, pokemon in
return pokemon
}
中間件是非常強(qiáng)大的.結(jié)合擴(kuò)展,它允許您添加對(duì)框架本身的功能.