美文网首页
全栈Swifter:一、Perfect框架初识

全栈Swifter:一、Perfect框架初识

作者: 顺手给我new一个对象 | 来源:发表于2017-06-28 17:38 被阅读214次

    因为一直是在做移动端的开发,在工作中总会遇到比较坑的后端。
    之前就遇到过一个让人抓狂的后端开发人员,本来只需一句代码解析的response,在对接他的API时发现需要上百行代码去解析,那时我心里就埋下了走向后端的种子,我要写出最美的API。
    自从Swift开源之后一直想尝试用Swift写服务端,在官方Swift3.0发布和经过许多优秀团队对Swift的贡献,Swift逐渐稳定。在今天成熟的条件下,我要做的就是用由加拿大团队开发的Perfect服务器框架,写一个简单的API。

    开发环境

    本demo环境:

    • macOS Sierra 10.12.5
    • Xcode 8.3.2
    • paw 3.1

    项目初始化

    我们可以通过SwiftPackageManager来初始化一个项目。打开终端:

    HO-2:~ HO$ mkdir SwiftServerDemo
    HO-2:~ HO$ cd SwiftServerDemo
    HO-2:SwiftServerDemo HO$ vi Package.swift
    

    以上,新建一个SwiftServerDemo文件夹,用Vim新建一个Package.swift文件,这个文件你可以理解为CocoaPod中的Podfile

    Package.swift中输入以下内容。

    import PackageDescription
    
    let package = Package(
        name: "SwiftServerDemo",
        dependencies: [
            .Package(
            url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git",
            majorVersion: 2, minor: 0
            )
        ]
    )
    

    语法是不是挺熟悉,这段Swift代码是作为项目的配置文件,表明了我们的项目名、所需依赖和依赖的版本。
    保存好该文件,回到终端,执行swift build

    HO-2:SwiftServerDemo HO$ swift build
    

    第一次编译会从仓库clone所有的dependencies到本地,速度可能有点慢,好好等待就可以了,最终终端如下:

    2017-06-28 10:41:53.408 xcodebuild[1991:95616] [MT] DVTPlugInManager: Required plug-in compatibility UUID DFFB3951-EB0A-4C09-9DAC-5F2D28CC839C for KSImageNamed.ideplugin (com.ksuther.KSImageNamed) not present
    2017-06-28 10:41:53.517 xcodebuild[1991:95616] [MT] PluginLoading: Required plug-in compatibility UUID DFFB3951-EB0A-4C09-9DAC-5F2D28CC839C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/VVDocumenter-Xcode.xcplugin' not present in DVTPlugInCompatibilityUUIDs
    2017-06-28 10:41:53.517 xcodebuild[1991:95616] [MT] PluginLoading: Required plug-in compatibility UUID DFFB3951-EB0A-4C09-9DAC-5F2D28CC839C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/KSImageNamed.xcplugin' not present in DVTPlugInCompatibilityUUIDs
    2017-06-28 10:41:53.518 xcodebuild[1991:95616] [MT] PluginLoading: Required plug-in compatibility UUID DFFB3951-EB0A-4C09-9DAC-5F2D28CC839C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/ActivatePowerMode.xcplugin' not present in DVTPlugInCompatibilityUUIDs
    Fetching https://github.com/PerfectlySoft/Perfect-HTTPServer.git
    Fetching https://github.com/PerfectlySoft/Perfect-HTTP.git
    Fetching https://github.com/PerfectlySoft/PerfectLib.git
    Fetching https://github.com/PerfectlySoft/Perfect-Net.git
    Fetching https://github.com/PerfectlySoft/Perfect-COpenSSL.git
    Fetching https://github.com/PerfectlySoft/Perfect-Thread.git
    Cloning https://github.com/PerfectlySoft/Perfect-HTTP.git
    Resolving https://github.com/PerfectlySoft/Perfect-HTTP.git at 2.0.7
    Cloning https://github.com/PerfectlySoft/Perfect-COpenSSL.git
    Resolving https://github.com/PerfectlySoft/Perfect-COpenSSL.git at 2.0.4
    Cloning https://github.com/PerfectlySoft/Perfect-HTTPServer.git
    Resolving https://github.com/PerfectlySoft/Perfect-HTTPServer.git at 2.0.9
    Cloning https://github.com/PerfectlySoft/Perfect-Net.git
    Resolving https://github.com/PerfectlySoft/Perfect-Net.git at 2.0.5
    Cloning https://github.com/PerfectlySoft/Perfect-Thread.git
    Resolving https://github.com/PerfectlySoft/Perfect-Thread.git at 2.0.12
    Cloning https://github.com/PerfectlySoft/PerfectLib.git
    Resolving https://github.com/PerfectlySoft/PerfectLib.git at 2.0.10
    warning: module 'SwiftServerDemo' does not contain any sources.
    warning: module 'SwiftServerDemo' does not contain any sources.
    

    当所有module编译完成后会提示我们warning: module 'SwiftServerDemo' does not contain any sources.,意思是我们还没有源代码。
    我们可以在项目目录下新建一个文件夹,名为Sources,用来保存源文件。

    HO-2:SwiftServerDemo HO$ mkdir Sources
    

    Sources目录中新建一个main.swift文件,作为程序入口,代码如下:

    HO-2:SwiftServerDemo HO$ cd Sources
    HO-2:Sources HO$ vi main.swift
    
    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    let server = HTTPServer()
    var routes = Routes()
    
    routes.add(method: .get, uri: "/", handler: {
            request, response in
            response.setHeader(.contentType, value: "text/html")
            response.appendBody(string: "<html><title>Hello</title><body>Hello Swift</body></html>")
            response.completed()
        }
    )
    
    server.addRoutes(routes)
    server.serverPort = 8080
     
    do {
        try server.start()
    } catch PerfectError.networkError(let err, let msg) {
        print("Error Message: \(err) \(msg)")
    }
    

    这段代码首先创建了一个路由,是get方法,路径是根路径,并且返回了一段html代码,设置服务器端口为8080,然后是用一个do循环来驱动了服务器。

    重新执行swift build

    HO-2:Sources HO$ swift build
    

    巴拉巴拉一堆日志输出....像下面这样子就可以了

    上面还有很多很多...
    efault value#>
    Compile Swift Module 'SwiftServerDemo' (1 sources)
    Linking /Users/HO/SwiftServerDemo/.build/debug/SwiftServerDemo
    HO-2:Sources HO$ 
    

    完成编译后,我们可以执行.build/debug/SwiftServerDemo来运行我们的程序,服务器会监听8080端口。

    HO-2:Sources HO$ cd
    HO-2:~ HO$ cd SwiftServerDemo
    HO-2:SwiftServerDemo HO$ .build/debug/SwiftServerDemo
    
    [INFO] Starting HTTP server  on 0.0.0.0:8080
    

    打开浏览器,输入http://localhost:8080/
    我们可以看到浏览器页面中显示:Hello Swift

    项目配置

    我们可以利用SPM来生成xcodeproj,执行swift package generate-xcodeproj,当提示generated: ./SwiftServerDemo.xcodeproj后,即可用Xcode打开项目目录下的SwiftServerDemo.xcodeproj文件。

    HO-2:~ HO$ cd SwiftServerDemo
    HO-2:SwiftServerDemo HO$ swift package generate-xcodeproj
    generated: ./SwiftServerDemo.xcodeproj
    

    在Xcode左侧navigator中,选择Project-MySwiftServer-Build Settings-Library Search Paths,添加"$(PROJECT_DIR)/**",注意要包含前后引号。

    配置

    配置完成后,就可以用Xcode来写代码、Build、Run项目。

    运行服务器

    尝试⌘CMD+R,运行项目,console中会提示服务器已经在8080端口跑起来了。打开浏览器,输入地址http://localhost:8080/ ,马上可以看到页面上显示我们配置好的Hello Swift页面。

    分离路由

    PerfectHTTP中,有一个struct名为Routes,我们可以通过它来构建服务器的路由。

    Sources目录中,创建一个名为routeHandlers.swift的文件

    HO-2:~ HO$ cd /Users/HO/SwiftServerDemo/Sources 
    HO-2:Sources HO$ vi routeHandlers..swift
    

    routeHandlers.swift 内容如下:

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    

    回到终端build一下:

    HO-2:Sources HO$ swift build
    

    日志如下表示成功:

    Compile Swift Module 'SwiftServerDemo' (2 sources)
    Linking /Users/HO/SwiftServerDemo/.build/debug/SwiftServerDemo
    

    用SPM来生成新的xcodeproj,执行swift package generate-xcodeproj,当提示generated: ./SwiftServerDemo.xcodeproj后即可用Xcode打开项目目录下的SwiftServerDemo.xcodeproj文件,此时工程结构如下:

    HO-2:SwiftServerDemo HO$ swift package generate-xcodeproj
    generated: ./SwiftServerDemo.xcodeproj
    
    
    工程结构

    用xcode打开工程,删除main.swift中的有关路由部分的代码,删除后的main.swift文件内容如下:

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    let server = HTTPServer()
    
    server.addRoutes(signupRoutes())
    server.serverPort = 8080
     
    do {
        try server.start()
    } catch PerfectError.networkError(let err, let msg) {
        print("Error Message: \(err) \(msg)")
    }
    
    

    将刚刚我们删掉的那部分代码,粘贴到刚刚创建的routeHandlers.swift文件中,然后适当的修改。routeHandlers.swift文件内容如下:

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    public func signupRoutes() -> Routes{
        
        return addURLRoutes()
    }
    
    func addURLRoutes() -> Routes {
        var routes = Routes()
        routes.add(method: .get, uri: "/", handler: {
            request, response in
            response.setHeader(.contentType, value: "text/html")
            response.appendBody(string: "<html><title>Hello</title><body>Hello Swift</body></html>")
            response.completed()
        })
        return routes
    }
    

    这段代码,将刚刚添加的“Hello Swift”页路由放到了统一文件中进行管理。编译运行,没毛病。

    上面代码中add方法最后一个参数handler是传入一个闭包,该闭包定义为public typealias RequestHandler = (HTTPRequest, HTTPResponse) -> (),所以我们可以将一个符合该类型的参数传入add方法中。修改routeHandlers.swift文件如下:

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    public func signupRoutes() -> Routes{
        
        return addURLRoutes()
    }
    
    func addURLRoutes() -> Routes {
        var routes = Routes()
        routes.add(method: .get, uri: "/", handler: helloHandler)
        return routes
    }
    
    
    func helloHandler(request: HTTPRequest, _ response: HTTPResponse) {
        response.setHeader(.contentType, value: "text/html")
        response.appendBody(string: "<html><title>Hello</title><body>Hello Swift</body></html>")
        response.completed()
    }
    

    重新运行编译,完全没毛病。

    MongoDB数据库

    MongoDB是一种非关系型数据库,可以存储类JSON格式的BSON数据,所以深受广大开发者的喜爱,我们在此使用MongoDB举例。

    对于已经使用过MongoDB的同学,可以不用看安装和配置部分。

    安装Homebrew:

    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    

    安装mongoldb:

        
    brew install mongodb
    
    

    要安装mongo-c:

    brew install mongo-c-driver
    

    到这里我们我们完成了mongodb 的安装,如遇到权限问题,具体不表,可以自行谷歌或者查看我之前python文集中有关mongodb的安装传送门

    数据库连接

    使用vim打开Package.swift:

    HO-2:SwiftServerDemo HO$ vi Package.swift
    

    在Package.swift中,添加MongoDB依赖如下:

    import PackageDescription
    let package = Package(
        name: "SwiftServerDemo",
        dependencies: [
            .Package(
            url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git",
            majorVersion: 2, minor: 0
            ),
        .Package(
            url: "https://github.com/PerfectlySoft/Perfect-MongoDB.git",
            majorVersion: 2
            )
        ]
    )
    

    回到终端build:

    HO-2:SwiftServerDemo HO$ swift build
    

    巴拉巴拉一堆日志输出....像下面这样子就表示mongodb依赖完成了。

    Fetching https://github.com/PerfectlySoft/Perfect-MongoDB.git
    Fetching https://github.com/PerfectlySoft/Perfect-mongo-c.git
    Cloning https://github.com/PerfectlySoft/Perfect-MongoDB.git
    Resolving https://github.com/PerfectlySoft/Perfect-MongoDB.git at 2.0.8
    Cloning https://github.com/PerfectlySoft/Perfect-mongo-c.git
    Resolving https://github.com/PerfectlySoft/Perfect-mongo-c.git at 2.0.0
    Compile Swift Module 'MongoDB' (7 sources)
    /Users/HO/SwiftServerDemo/.build/checkouts/Perfect-MongoDB.git--8154448075697536604/Sources/MongoDB/MongoCollection.swift:457:13: warning: 'mongoc_collection_save' is deprecated
                    let res = mongoc_collection_save(ptr, sdoc, nil, &error)
                              ^
    /Users/HO/SwiftServerDemo/.build/checkouts/Perfect-MongoDB.git--8154448075697536604/Sources/MongoDB/MongoCollection.swift:580:16: warning: 'mongoc_collection_find' is deprecated
                    let cursor = mongoc_collection_find(ptr, flags.queryFlags, UInt32(skip), UInt32(limit), UInt32(batchSize), qdoc, fields?.doc, nil)
                                 ^
    /Users/HO/SwiftServerDemo/.build/checkouts/Perfect-MongoDB.git--8154448075697536604/Sources/MongoDB/MongoGridFS.swift:401:15: warning: 'mongoc_gridfs_find' is deprecated
          plist = mongoc_gridfs_find(handle, &query)
                  ^
    /Users/HO/SwiftServerDemo/.build/checkouts/Perfect-MongoDB.git--8154448075697536604/Sources/MongoDB/MongoGridFS.swift:403:15: warning: 'mongoc_gridfs_find' is deprecated
          plist = mongoc_gridfs_find(handle, filter?.doc)
                  ^
    Compile Swift Module 'SwiftServerDemo' (2 sources)
    Linking ./.build/debug/SwiftServerDemo
    

    然后再使用SPM重新生成一份xcode文件:

    HO-2:SwiftServerDemo HO$ swift package generate-xcodeproj
    generated: ./SwiftServerDemo.xcodeproj
    

    用xcode打开工程,在routeHandlers.swift中,添加import MongoDB,并用一个字符串常量指定MongoDB服务器地址var mongoURL = "mongodb://localhost:27017"

    添加一个新的路由,用来查找数据库数据:

    //添加一个新的路由,用来查找数据库数据
    func queryFullDBHandler(request: HTTPRequest, _ response: HTTPResponse) {
        //创建连接
        let client = try! MongoClient(uri: mongoURL)
        
        //连接到具体的数据库,假设有个数据库名字叫 SwiftServerDemoTest
        let db = client.getDatabase(name: "SwiftServerDemoTest")
        
        //定义集合
        guard let collection = db.getCollection(name: "SwiftServerDemoTest") else { return  }
        
        //在关闭连接时注意关闭顺序与启动顺序相反,类似栈
        defer {
            collection.close()
            db.close()
            client.close()
        }
        
        //执行查询
        let fnd = collection.find(query: BSON())
        
        //初始化一个空数组用于存放结果记录集
        var arr = [String]()
        
        //"fnd"游标是一个 MongoCursor 类型,用于遍历结果
        for x in fnd! {
            arr.append(x.asString)
        }
        
        //返回一个格式化的 JSON 数组。
        let returning = "{\"data\":[\(arr.joined(separator: ","))]}"
        
        //返回 JSON 字符串
        response.appendBody(string: returning)
        response.completed()
    }
    

    将该路由部署上去:

    func addURLRoutes() -> Routes {
        var routes = Routes()
        routes.add(method: .get, uri: "/mongo", handler: queryFullDBHandler(request:_:))
        return routes
    }
    

    编译运行,我们在浏览器中打开http://localhost:8080/mongo
    发现返回一个JSON对象:{"data":[]}

    接下来我们添加一个数据库写入接口:

    //添加一个新的路由,用来添加数据写入接口
    func addHandler(request: HTTPRequest, _ response: HTTPResponse) {
        //创建连接
        let client = try! MongoClient(uri: mongoURL)
        
        //连接到具体的数据库,假设有个数据库名字叫 SwiftServerDemoTest
        let db = client.getDatabase(name: "SwiftServerDemoTest")
        
        //定义集合
        guard let collection = db.getCollection(name: "SwiftServerDemoTest") else { return  }
        
        //定义BSON对象,从请求的body部分取JSON对象
        let bson = try! BSON(json: request.postBodyString!)
        
        //在关闭连接时注意关闭顺序与启动顺序相反,类似栈
        defer {
            bson.close()
            collection.close()
            db.close()
            client.close()
        }
        
        let result = collection.save(document: bson)
        
        response.setHeader(.contentType, value: "application/json")
        response.appendBody(string: request.postBodyString!)
        response.completed()
        
    }
    

    将该路由部署上去:

    func addURLRoutes() -> Routes {
        var routes = Routes()
        routes.add(method: .post, uri: "/mongo/add", handler: addHandler(request:_:))
        return routes
    }
    
    

    编译运行,没毛病!!

    现在我们借助接口调试工具PAW来测试这个接口。

    在接口调试工具中,选择POST,地址http://localhost:8080/mongo/add, body部分给出一个JSON对象,比如{"name" : "CRonaldo", "birth" : "1985-2-5", "nationality" : "Portugal","number":"7" },然后打出请求,返回值如果是我们打出的JSON对象,说明请求正常返回了,如下图:

    PAW工具

    图中1:请求method 请求 地址
    图中2:请求body
    图中3:返回json

    接下来用刚才部署好的http://localhost:8080/mongo 接口来验证一下我们是否真的插入了新的数据,返回结果默认是UTF8编码,如果有中文乱码的情况可以考虑下编码是否有问题。结果如下:

    {"data":[{ "_id" : { "$oid" : "59536b6a4c6063a74f4eee42" }, "name" : "C Ronaldo", "birth" : "1985-2-5", "nationality" : "Portugal", "number" : "7" },{ "_id" : { "$oid" : "59536bac4c6063a82e70c822" }, "name" : "C Ronaldo", "birth" : "1985-2-5", "nationality" : "Portugal", "number" : "7" },{ "_id" : { "$oid" : "59536baf4c6063a82e70c824" }, "name" : "C Ronaldo", "birth" : "1985-2-5", "nationality" : "Portugal", "number" : "7" }]}
    

    没毛病,到这里我们就成功了。。。。

    过滤器

    我们在上网时如果访问到不存在的资源,会看到一个“404 NOT FOUND”页面,类似还有“403 FORBIDDEN”、“401 UNAUTHORIZED”等等,要对这些页面进行过滤,并在发生问题的时候做出一些操作,Perfect为我们提供了HTTPResponseFilter。HTTPResponseFilter是一个协议,含有两个方法,本着Swift的“能用struct就不要用class”的思想,我们可以定义一个struct,遵循HTTPResponseFilter,作为我们的过滤器。代码如下:

    struct Filter404: HTTPResponseFilter {
        
        func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
            callback(.continue)
        }
        
        func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
            if case .notFound = response.status {
                response.bodyBytes.removeAll()
                response.setBody(string: "\(response.request.path) is not found.")
                response.setHeader(.contentLength, value: "\(response.bodyBytes.count)")
                callback(.done)
            }else{
                callback(.continue)
            }
        }
    }
    
    

    大概意思就是拦截下response,如果状态值是notFound,我们就把response的body改为“ …… path …… is not found.”。

    然后我们在main.swift文件中,把之前写好的代码稍加改动:

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    let server = HTTPServer()
    
    server.addRoutes(signupRoutes())
    server.serverPort = 8080
     
    do {
        try server
            .setResponseFilters([(Filter404(),.high)])
            .start()
    } catch PerfectError.networkError(let err, let msg) {
        print("Network Error Message: \(err) \(msg)")
    }
    

    类似的,我们还可以过滤其他http错误,具体可查阅HTTPResponse中的HTTPResponseStatus

    相关文章

      网友评论

          本文标题:全栈Swifter:一、Perfect框架初识

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