基于Swift的Web框架Vapor2.0文档(翻译)HTTP-Middleware

转载请附原文链接:http://blog.fandong.me/2017/08/18/iOS-SwiftVaporWeb22/

前言

之前一直有做Java后台开发的兴趣,可是想到要看好多的Java教程,作为一个iOS开发者,我放弃了,
后来从朋友韩云智VL那里知道了这个框架,竟是用Swift写的,不得不说,它燃起了我的兴趣。
Vapor是一个基于Swift开发的服务端框架,可以工作于iOS,Mac OS,Ubuntu。
为了配合Swift部署到服务器,我把ECS的服务器系统改为Ubuntu16.04。

Vapor 2.0 - 文档目录
以下文字翻译自Vapor Docs/HTTP/Middle

中间件

中间件是任何现代Web框架的重要组成部分,它允许你在客户端和服务器之间经过时修改请求和响应.
您可以将中间件看作连接您的服务器和请求您的网络应用程序的客户端的逻辑.

版本中间件

例如,让我们创建一个中间件,对每个响应添加我们的API版本,中间件看起来会是这样的:

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
    }
}

我们将这个中间件提供给我们的Droplet容器

import Vapor

let config = try Config()

config.addConfigurable(middleware: VersionMiddleware(), name: "version")

let drop = try Droplet(config)

你现在可以再配置文件中启用和禁用此中间件,只需要添加version到您的droplet.json中的middleware数组,请查看配置文件章节

你可以想见,我们的版本中间件就在连接客户端和我们的服务器中间,访问我们的服务器每一个请求和响应都必须经过这个中间件链.


分解

我们一行一行的分解中间件

let response = try next.respond(to: request)

由于VersionMiddleware在这个例子中没有修改请求,所以我们要求下一个中间件来响应该请求,链条一直下降到Droplet,然后回到发送给客户端的响应.

response.headers["Version"] = "API v1.0"

然后我们定义一个包含版本的请求头的响应.

return response

返回响应,并将备份任何剩余的中间件,返回给客户端.

请求

中间件也可以被修改或与请求交互

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)
}

这个中间件将要求该请求的cookie具有一个token键值等于secret或其他的,请求将被终止.

错误

中间件是捕获程序中任意位置错误的完美地方,当您让中间件捕获错误时,您可以从路由闭包中删除大量重复的逻辑,看看下面的例子:

enum FooError: Error {
    case fooServiceUnavailable
}

假设您定义了自定义错误或您正在使用的的其中一个APIthrows,抛出的错误必须被捕获,否则最终将作为用户意外的内部服务器错误(500),最明显的解决方案就是在路由闭包中捕获错误

app.get("foo") { request in
    let foo: Foo
    do {
        foo = try getFooFromService()
    } catch {
        throw Abort(.badRequest)
    }

    // continue with Foo object
}

这个解决方案是有效的,但是如果有多个路由需要处理这个错误,他将会产生重复代码,幸运的是,这个错误可以在中间件中捕获

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."
            )
        }
    }
}

我们只需要添加这个中间件到我们的Droplet的配置文件中.

config.addConfigurable(middleware: FooErrorMiddleware(), name: "foo-error")

提示
不要忘记在droplet.json文件中启用中间件

现在我们的路由闭包看起来好多了,我们也不必担心代码的重复了

app.get("foo") { request in
    let foo = try getFooFromService()

    // continue with Foo object
}

路由组

更细致的来说,中间件可以应用于特定的路由组.

let authed = drop.grouped(AuthMiddleware())
authed.get("secure") { req in
    return Secrets.all().makeJSON()
}

添加到authed组的任何内容都必须通过AuthMiddleware.因此,我们可以假定所有访问/secure的流量已经被授权了,了解更多请查看路由

配置

你可以使用配置文件来启用或禁用中间件,如果你有中间件,例如,仅在生产环境中运行,这将非常有用.
添加可配置的中间件,像下面这样

let config = try Config()

config.addConfigurable(middleware: myMiddleware, name: "my-middleware")

let drop = Droplet(config)

然后,在Config/droplet.json文件中,添加my-middlewaremiddleware数组中.

{
    ...
    "middleware": {
        ...
        "my-middleware",
        ...
    },
    ...
}

如果添加的中间件的名称出现在中间件阵列中,那么当应用程序启动时,它将被添加到服务器的中间件.
按照中间件中的顺序

手动

如果你不想使用配置文件,你也可以对中间件进行硬编码.

import Vapor

let versionMiddleware = VersionMiddleware()
let drop = try Droplet(middleware: [versionMiddleware])

高级

扩展

中间件需要与请求/响应的扩展和存储有很好的配对关系,这个例子给你还在那时了如何根据客户端的类型为模型动态的返回HTML或JSON响应

中间件
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)
    }
}
响应

延伸到Response.

extension Response {
    var pokemon: Pokemon? {
        get { return storage["pokemon"] as? Pokemon }
        set { storage["pokemon"] = newValue }
    }
}

在这个例子中,我们给响应添加一个新的属性来持有一个口袋对象,如果中间件发现了一个包含口袋对象的响应,它将动态的检查客户端是否是支持HTML的,如果客户端是一个像Safari的浏览器,支持HTML,它将会发挥一个Mustache视图,,如果客户端不支持HTML,它将会返回JSON

使用方法

你的闭包现在应该看起来这样


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的中间件数组

Response Representable

如果你想更进一步,你可以使Pokemon遵循ResponseRepresentable

import HTTP

extension Pokemon: ResponseRepresentable {
    func makeResponse() throws -> Response {
        let response = Response()
        response.pokemon = self
        return response
    }
}

现在你的闭包就大大简化了

drop.get("pokemon", Pokemon.self) { request, pokemon in
    return pokemon
}

中间件是非常强大的.结合扩展,它允许您添加对框架本身的功能.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容