Vapor奇幻之旅(04Routing)

Routing是web服務中重要的組成部分红竭,用于調(diào)度請求和返回.

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

從源碼可以看到基本的網(wǎng)絡請求RouteBuilder都可以提供喘落,包括HTTP請求 POST, GET, PUT, PATCH, DELETE,以及socket請求和all, add, patch, options

下面我來一一介紹他們的用法:

創(chuàng)建一個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界面
  • 請求參數(shù)的添加
    通過前面的源碼我們可以看到基本請求的方法第一個參數(shù)是 segments: String... 鹿蜀,也就是可以傳入多個參數(shù)箕慧, 通常我們的get請求是需要帶有參數(shù)的,如傳入名字茴恰,年齡颠焦。

參數(shù)有兩種寫法:
一種是 :[類型.parameter]
另一種是: [:參數(shù)名稱]

我們寫一個測試的請求:

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

如果參數(shù)是一個對象,則需要對象適配Parameterizable協(xié)議

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作為參數(shù)了

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.

這里我詳細解釋一下,路由集的作用是將許多的路由集合在一起分冈,比如統(tǒng)一前綴的不同請求集合在一起圾另,中間件的集合,以及主機的集合雕沉。

  • 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

上面我們創(chuàng)建了名為testGroup的group扰路,要獲得這個group的RouteBuilder,就可以用grouped方法了倔叼,這樣就可以繼續(xù)給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巡验,驗證數(shù)據(jù)等识椰。

  • Host
    使用host可以指定某個主機的請求,做分布式的同學你懂的深碱。
    官方給出的例子:
drop.group(host: "vapor.codes") { vapor in
    vapor.get { request in
        // only responds to requests to vapor.codes
    }
}
  • Chaining
    因為grouped方法返回的是RouteBuilder,意味著可以連續(xù)調(diào)用grouped,可以將一系列的group串聯(lián)起來
    官方的例子:
drop.grouped(host: "vapor.codes").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in
   // add routes here
}

RouteCollection

RouteCollection是一個協(xié)議, 代碼很簡單藏畅,只有一個方法需要實現(xiàn):

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

如果對上面的group已經(jīng)理解的同學這里就不難理解了敷硅,這里提供了一個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同時實現(xiàn)了RouteCollection 和 EmptyInitializable協(xié)議,并添加了空的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)
    }
}

代碼是不是又簡潔了許多幽七?

路由的基本使用就介紹到這里景殷,如果有遺漏或者不清楚的地方請?zhí)嵝盐已a充,希望能對你有所幫助澡屡。

國際慣例猿挚,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)

希望你對我的教程能夠喜歡驶鹉,你們的贊是我持續(xù)的動力绩蜻,歡迎加入QQ群參與互動:431296189

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市室埋,隨后出現(xiàn)的幾起案子办绝,更是在濱河造成了極大的恐慌,老刑警劉巖姚淆,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孕蝉,死亡現(xiàn)場離奇詭異,居然都是意外死亡腌逢,警方通過查閱死者的電腦和手機降淮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來上忍,“玉大人骤肛,你說我怎么就攤上這事∏侠叮” “怎么了腋颠?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吓笙。 經(jīng)常有香客問我淑玫,道長,這世上最難降的妖魔是什么面睛? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任絮蒿,我火速辦了婚禮,結果婚禮上叁鉴,老公的妹妹穿的比我還像新娘土涝。我一直安慰自己,他們只是感情好幌墓,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布但壮。 她就那樣靜靜地躺著冀泻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜡饵。 梳的紋絲不亂的頭發(fā)上弹渔,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機與錄音溯祸,去河邊找鬼肢专。 笑死,一個胖子當著我的面吹牛焦辅,可吹牛的內(nèi)容都是我干的博杖。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼氨鹏,長吁一口氣:“原來是場噩夢啊……” “哼欧募!你這毒婦竟也來了?” 一聲冷哼從身側響起仆抵,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤跟继,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镣丑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舔糖,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年莺匠,在試婚紗的時候發(fā)現(xiàn)自己被綠了金吗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡趣竣,死狀恐怖摇庙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遥缕,我是刑警寧澤卫袒,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站单匣,受9級特大地震影響夕凝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜户秤,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一码秉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸡号,春花似錦转砖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莉兰。三九已至,卻和暖如春礁竞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杉辙。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工模捂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜘矢。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓狂男,卻偏偏與公主長得像,于是被迫代替她去往敵國和親品腹。 傳聞我的和親對象是個殘疾皇子岖食,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)舞吭,斷路器泡垃,智...
    卡卡羅2017閱讀 134,717評論 18 139
  • 上篇文章帶大家基本了解了一下開始一個 Vapor 項目的流程,本篇緊接著來說說在所有 Web 框架中都最關鍵的 “...
    isaced閱讀 1,241評論 0 10
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫羡鸥、插件蔑穴、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,125評論 4 61
  • 嗯哼嗯哼蹦擦擦~~~ 轉(zhuǎn)載自:https://github.com/Tim9Liu9/TimLiu-iOS 目錄 ...
    philiha閱讀 4,911評論 0 6
  • 參觀廁所 等待周五到來的這幾天,班里出奇地平靜惧浴。平時的瘋瘋癲癲存和、追逐奔跑、男拉女拽的現(xiàn)象好像一下子少了許多衷旅。每次我...
    立邦柒閱讀 467評論 12 10