Vapor奇幻之旅(04Routing)

Routing是web服务中重要的组成部分,用于调度请求和返回.

Vapor的Routing提供了RouteBuilder和RouteCollection

其中RouteBuilder提供了基本的路由和路由集

路由基本方法

我们先看看部分源码,看看到底能干些什么:

extension RouteBuilder {

    public func add(_ method: HTTP.Method, _ path: String..., value: @escaping Routing.RouteHandler)

    public func socket(_ segments: String..., handler: @escaping Routing.WebSocketRouteHandler)

    public func all(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func get(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func post(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func put(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func patch(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func delete(_ segments: String..., handler: @escaping Routing.RouteHandler)

    public func options(_ segments: String..., handler: @escaping Routing.RouteHandler)
}

从源码可以看到基本的网络请求RouteBuilder都可以提供,包括HTTP请求 POST, GET, PUT, PATCH, DELETE,以及socket请求和all, add, patch, options

下面我来一一介绍他们的用法:

创建一个Routes+Test.swift的文件,并加入以下测试代码

import Vapor

extension Droplet {
    
    func setupTestRoutes() throws {
        
        post("testPost") { req in
            return "testPost result"
        }
        
        get("testGet") { req in
            return "testGet result"
        }
        
        put("testPut") { req in
            return "testPut result"
        }
        
        patch("testPatch") { req in
            return "testPatch result"
        }
        
        delete("testDelete") { req in
            return "testDelete result"
        }
        
        options("testOptions") { req in
            return "testOptions result"
        }
    }
}

接着在Droplet+Setup.swift里面加入try setupTestRoutes(),整体代码如下

@_exported import Vapor

extension Droplet {
    public func setup() throws {
        try setupRoutes()
        try setupTestRoutes()
    }
}

运行程序,就可以测试这些接口了,请注意,只有get请求才能直接在浏览器输入http://0.0.0.0:8080/textGet 输出 testGet result

对于其他接口可以通过接口测试工具来测试,这里我推荐使用cocoa rest client

可以很快测试接口并查看返回的结果


cocoa rest client界面
  • 请求参数的添加
    通过前面的源码我们可以看到基本请求的方法第一个参数是 segments: String... ,也就是可以传入多个参数, 通常我们的get请求是需要带有参数的,如传入名字,年龄。

参数有两种写法:
一种是 :[类型.parameter]
另一种是: [:参数名称]

我们写一个测试的请求:

get("age", Int.parameter) { req in
    let age = try req.parameters.next(Int.self)
    return "Age is \(age)"
}

get("call", ":name") { req in
    guard let name = req.parameters["name"]?.string else {
        throw Abort.badRequest
    }
    return "You requested User #\(name)"
}

那么
请求 http://0.0.0.0:8080/age/18 则会返回 Age is 18
请求http://0.0.0.0:8080/call/Leakey则会返回Calling Leakey

如果参数是一个对象,则需要对象适配Parameterizable协议

Parameterizable的源码如下:

public protocol Parameterizable {
    /// the unique key to use as a slug in route building
    static var uniqueSlug: String { get }
    
    // returns the found model for the resolved url parameter
    static func make(for parameter: String) throws -> Self
}

我们可以写一个User对象:

extension Type: Parameterizable {
   
    static var uniqueSlug: String {
        return "type"
    }

    static func make(for parameter: String) throws -> Type {
        
    }
}

那么请求中就可以将Type作为参数了

drop.get("users", "nickname", Type.parameter) { req in
    let foo = try req.parameters.next(Type.self)
    ...
}

路由集

看看路由集的源码

extension RouteBuilder {

    /// Group all subsequent routes built with this builder
    /// under this specified host
    /// 
    /// the last host in the chain will take precedence, for example:
    ///
    /// if using:
    /// grouped(host: "0.0.0.0").grouped(host: "196.08.0.1")
    ///
    /// will bind subsequent additions to '196.08.0.1'
    public func grouped(host: String) -> RouteBuilder

    /// Group all subsequent routes behind a specified path prefix
    /// use `,` separated list or `/` separated string
    /// for example, the following are all equal
    ///
    /// "a/path/to/foo"
    /// "a", "path", "to", "foo"
    /// "a/path", "to/foo"
    public func grouped(_ path: String...) -> RouteBuilder

    /// - see grouped(_ path: String...)
    public func grouped(_ path: [String]) -> RouteBuilder

    /// Group all subsequent routes to pass through specified middleware
    /// use `,` separated list for multiple middleware
    public func grouped(_ middleware: Middleware...) -> RouteBuilder

    /// - see grouped(middleware: Middleware...)
    public func grouped(_ middleware: [Middleware]) -> RouteBuilder
}

extension RouteBuilder {

    /// Closure based variant of grouped(host: String)
    public func group(host: String, handler: (RouteBuilder) -> ())

    /// Closure based variant of grouped(_ path: String...)
    public func group(_ path: String..., handler: (RouteBuilder) -> ())

    /// Closure based variant of grouped(_ path: [String])
    public func group(path: [String], handler: (RouteBuilder) -> ())

    /// Closure based variant of grouped(middleware: Middleware...)
    public func group(_ middleware: Middleware..., handler: (RouteBuilder) -> ())

    /// Closure based variant of grouped(middleware: [Middleware])
    public func group(middleware: [Middleware], handler: (RouteBuilder) -> ())
}

可以看到路由组包含一系列名为grouped和名为group的方法

那么,group有什么作用呢?
官方文档给出的解释是:

Grouping routes together makes it easy to add common prefixes, middleware, or hosts to multiple routes.

这里我详细解释一下,路由集的作用是将许多的路由集合在一起,比如统一前缀的不同请求集合在一起,中间件的集合,以及主机的集合。

  • Group

同前面一样,我添加了一个测试方法

func setupGroupRoutes() throws {
        
        group("testGroup") { testGroup in
            testGroup.post("testGroup_post") { request in
                return "testGroup testGroup_post result"
            }
            testGroup.get("testGroup_get") { request in
                return "testGroup testGroup_get result"
            }
            testGroup.put("testGroup_put") { req in
                return "testGroup testGroup_put result"
            }
            
            testGroup.patch("testGroup_patch") { req in
                return "testGroup testGroup_patch result"
            }
            
            testGroup.delete("testGroup_delete") { req in
                return "testGroup testGroup_delete result"
            }
            
            testGroup.options("testGroup_options") { req in
                return "testGroup testGroup_options result"
            }
        }
    
    }

接着在Droplet+Setup.swift里面加入try setupGroupRoutes(),整体代码如下

@_exported import Vapor

extension Droplet {
    public func setup() throws {
        try setupRoutes()
        try setupTestRoutes()
        try setupGroupRoutes()
    }
}
对group的测试

这里的group和java的spring的package有些类似。

  • Grouped

上面我们创建了名为testGroup的group,要获得这个group的RouteBuilder,就可以用grouped方法了,这样就可以继续给group增加子请求了。

如下面的代码:

let testGroup = grouped("testGroup")
testGroup.get("extra") { request in
    return "testGroup extra result"
}
  • Middleware

中间件的请求集,官方给出的例子是auth,引入AuthProvider到项目即可使用auth中间件

drop.group(AuthMiddleware()) { authorized in 
    authorized.get("token") { request in
        // has been authorized
    }
}

这个例子告诉我们,我们可以使用中间件来处理请求,如使用auth中间件来校验token,验证数据等。

  • Host
    使用host可以指定某个主机的请求,做分布式的同学你懂的。
    官方给出的例子:
drop.group(host: "vapor.codes") { vapor in
    vapor.get { request in
        // only responds to requests to vapor.codes
    }
}
  • Chaining
    因为grouped方法返回的是RouteBuilder,意味着可以连续调用grouped,可以将一系列的group串联起来
    官方的例子:
drop.grouped(host: "vapor.codes").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in
   // add routes here
}

RouteCollection

RouteCollection是一个协议, 代码很简单,只有一个方法需要实现:

public protocol RouteCollection {
    func build(_ builder: RouteBuilder) throws
}

如果对上面的group已经理解的同学这里就不难理解了,这里提供了一个RouteBuilder,可以通过这个builder获得group,并增加新的请求到group中,下面是我的demo:

class TestCollection: RouteCollection {
    func build(_ builder: RouteBuilder) throws {
        let testGroup = builder.grouped("testGroup")
        testGroup.get("anotherExtra") { req in
            return "testGroup extra result"
        }
    }
}

需要在Droplet+Setup.swift中添加这个collection的实例才能有效

@_exported import Vapor

extension Droplet {
    public func setup() throws {
        try setupRoutes()
        try setupTestRoutes()
        try setupGroupRoutes()
        
        let testCollection = TestCollection()
        try collection(testCollection)
    }
}

如果collection同时实现了RouteCollection 和 EmptyInitializable协议,并添加了空的init方法,就可以用XXXCollection.self添加到droplet中了,我们可以改造一下前面的demo:

class TestCollection: RouteCollection, EmptyInitializable {
    
    required init() {
        
    }
    
    func build(_ builder: RouteBuilder) throws {
        let testGroup = builder.grouped("testGroup")
        testGroup.get("anotherExtra") { req in
            return "testGroup extra result"
        }
    }
}

Droplet+Setup.swift

@_exported import Vapor

extension Droplet {
    public func setup() throws {
        try setupRoutes()
        try setupTestRoutes()
        try setupGroupRoutes()
        
        try collection(TestCollection.self)
    }
}

代码是不是又简洁了许多?

路由的基本使用就介绍到这里,如果有遗漏或者不清楚的地方请提醒我补充,希望能对你有所帮助。

国际惯例,Demo请见HelloVapor

关于Vapor其他知识,可以参考以下文章:

Vapor奇幻之旅(01开始)
Vapor奇幻之旅(02部署)
Vapor奇幻之旅(03上手)
Vapor奇幻之旅(04Routing)
Vapor奇幻之旅(05 Fluent)
Vapor奇幻之旅(06 PostgreSQL)
Vapor奇幻之旅(07 连接服务端PostgreSQL)
Vapor奇幻之旅(08 连接服务端MongoDB)
Vapor奇幻之旅(09 连接MySQL)

希望你对我的教程能够喜欢,你们的赞是我持续的动力,欢迎加入QQ群参与互动:431296189

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,685评论 18 139
  • 上篇文章带大家基本了解了一下开始一个 Vapor 项目的流程,本篇紧接着来说说在所有 Web 框架中都最关键的 “...
    isaced阅读 1,239评论 0 10
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,117评论 4 61
  • 嗯哼嗯哼蹦擦擦~~~ 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 ...
    philiha阅读 4,906评论 0 6
  • 参观厕所 等待周五到来的这几天,班里出奇地平静。平时的疯疯癫癫、追逐奔跑、男拉女拽的现象好像一下子少了许多。每次我...
    立邦柒阅读 465评论 12 10