Vapor文檔學(xué)習(xí)八:Middleware

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鏈。

middleware.png

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)化程序功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容