Middleware是任何現(xiàn)代Web框架的重要組成部分。它允許你修改在客戶(hù)端和服務(wù)器之間傳遞的請(qǐng)求(Request)和響應(yīng)( Response)。
你可以將Middleware視為連接服務(wù)器與請(qǐng)求網(wǎng)絡(luò)的應(yīng)用客戶(hù)端的邏輯鏈。
Basic
舉例說(shuō)明:我們來(lái)創(chuàng)建一個(gè)middleware,它會(huì)在每次響應(yīng)時(shí)添加我們API的版本號(hào):
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è)middleware應(yīng)用到Droplet中:
let drop = Droplet()
drop.middleware.append(VersionMiddleware())
您可以想象我們的VersionMiddleware位于連接客戶(hù)端和服務(wù)器的鏈中間。訪(fǎng)問(wèn)服務(wù)器的每一個(gè)請(qǐng)求和響應(yīng)都必須經(jīng)過(guò)這個(gè)middleware鏈。

BreakDown
來(lái)一行一行的分析一下上面的代碼:
let response = try next.respond(to: request)
例子中的VersionMiddleware并不想修改request,所以我們立即將request傳給下一個(gè)middleware去響應(yīng),直到傳給Droplet,Droplet得到請(qǐng)求后會(huì)將response返回給客戶(hù)端。
response.headers["Version"] = "API v1.0"
然后修改response,添加Version信息到headers里。
return response
response中修改過(guò)的內(nèi)容會(huì)在后面的middleware中保留,最后傳給客戶(hù)端。
Request
middleware也可以修改或作用于request:
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è)middleware要求cookies中存儲(chǔ)的“token”值是“secret”,否則請(qǐng)求就會(huì)終止。
Errors
Middleware是捕獲應(yīng)用程序中任何地方發(fā)生的異常的完美場(chǎng)所。當(dāng)你使用middleware捕獲異常時(shí),你可以刪除路由閉包中大量重復(fù)的邏輯。例子如下:
enum FooError: Error {
case fooServiceUnavailable
}
有這樣一個(gè)你自定義的異常或者是某個(gè)API的異常,你必須對(duì)它進(jìn)行捕獲,否則會(huì)引起服務(wù)器異常。最明顯的解決辦法是在路由閉包中進(jìn)行捕獲:
drop.get("foo") { request in
let foo: Foo
do {
foo = try getFooFromService()
} catch {
throw Abort.badRequest
}
// continue with Foo object
}
這樣可以捕獲異常,但是如果在多個(gè)路由中都需要捕獲這個(gè)異常呢?復(fù)制代碼沒(méi)問(wèn)題,但是更高大上的辦法就是在middleware中捕獲。
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.custom(
status: .badRequest,
message: "Sorry, we were unable to query the Foo service."
)
}
}
}
我們只需要將這個(gè)middleware拼到drop中。
drop.middleware.append(FooErrorMiddleware())
路由閉包中則簡(jiǎn)單得多了,并且不用擔(dān)心代碼重復(fù)的問(wèn)題。
app.get("foo") { request in
let foo = try getFooFromService()
// continue with Foo object
}
有趣的是,這就是Abort類(lèi)在Vapor中實(shí)現(xiàn)的原理。 AbortMiddleware可以捕獲所有的Abort異常,并返回JSON響應(yīng)數(shù)據(jù)。如果您想自定義Abort錯(cuò)誤的顯示方式,可以刪除這個(gè)AbortMiddleware并添加自己的middleware。
Configration
將middleware添加到drop.middleware數(shù)組中,是從應(yīng)用開(kāi)始就一直在用的最簡(jiǎn)單的添加middleware的方法。
你也可以通過(guò)configration來(lái)對(duì)middleware進(jìn)行添加、刪除或更多操作。尤其是你在不同的開(kāi)發(fā)環(huán)境下進(jìn)行開(kāi)發(fā)時(shí)這樣做就很有用了。
configuration添加middleware的方法如下:
let drop = Droplet()
drop.addConfigurable(middleware: myMiddleware, name: "my-middleware")
然后在Config/droplet.json文件內(nèi)添加middleware數(shù)組以及my-middleware字段。
{
...
"middleware": {
"server": [
...
"my-middleware",
...
],
"client": [
...
]
},
...
}
my-middleware出現(xiàn)在server中就會(huì)添加到server的middleware數(shù)組中,如果添加在client中,就會(huì)添加到client的middleware數(shù)組中。當(dāng)然了,一個(gè)middleware可以添加到多個(gè)地方,也可以重復(fù)添加,并且會(huì)安添加順序依次執(zhí)行。
Extensions(高級(jí)用法)
Middleware 與request/response的擴(kuò)展和存儲(chǔ)配合的很好:
final class PokemonMiddleware: Middleware {
let drop: Droplet
init(drop: Droplet) {
self.drop = drop
}
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 drop.view("pokemon.mustache", context: pokemon)
} else {
response.json = try pokemon.makeJSON()
}
}
return response
}
}
Response的擴(kuò)展:
extension Response {
var pokemon: Pokemon? {
get { return storage["pokemon"] as? Pokemon }
set { storage["pokemon"] = newValue }
}
}
在這個(gè)例子中,我們給Response添加了一個(gè)新的屬性,讓它持有一個(gè)Pokémon對(duì)象。 如果middleware發(fā)現(xiàn)了某個(gè)或多個(gè)Response中持有Pokémon對(duì)象,它將動(dòng)態(tài)地檢查客戶(hù)端是否支持HTML。 如果客戶(hù)端是Safari瀏覽器,并且支持HTML,它將返回一個(gè)Mustache視圖。 如果客戶(hù)端不支持HTML,它將返回JSON。
路由閉包中這么寫(xiě):
import HTTP
drop.get("pokemon", Pokemon.self) { request, pokemon in
let response = Response()
response.pokemon = pokemon
return response
}
或者更進(jìn)一步,讓Pokemon遵守ResponseRepresentable協(xié)議:
import HTTP
extension Pokemon: ResponseRepresentable {
func makeResponse() throws -> Response {
let response = Response()
response.pokemon = self
return response
}
}
然后路由閉包中可以十分簡(jiǎn)潔,并且不用import HTTP:
drop.get("pokemon", Pokemon.self) { request, pokemon in
return pokemon
}
Middleware是非常強(qiáng)大的,結(jié)合擴(kuò)展,你可以向原生框架添加功能。
對(duì)于那些好奇的人來(lái)說(shuō),這就是Vapor如何在內(nèi)部管理JSON的。 無(wú)論何時(shí)在閉包中返回JSON,它會(huì)給Response設(shè)置json:JSON?屬性。 JSONMiddleware會(huì)檢測(cè)這個(gè)屬性并將其JSON序列化添加到response的正文中。
<b>總結(jié):</b>Middleware在客戶(hù)端和服務(wù)端中間鏈中對(duì)Request/Response進(jìn)行“偷梁換柱”,能夠極大的減少代碼冗余、簡(jiǎn)化路由閉包。結(jié)合類(lèi)的擴(kuò)展等,又可以強(qiáng)化程序功能。