美文网首页Vapor奇幻之旅
Vapor奇幻之旅(04Routing)

Vapor奇幻之旅(04Routing)

作者: leacode | 来源:发表于2018-01-21 01:57 被阅读216次

    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

    相关文章

      网友评论

        本文标题:Vapor奇幻之旅(04Routing)

        本文链接:https://www.haomeiwen.com/subject/qccxaxtx.html