美文网首页
全栈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框架初识

    因为一直是在做移动端的开发,在工作中总会遇到比较坑的后端。之前就遇到过一个让人抓狂的后端开发人员,本来只需一句代码...

  • Swift服务器框架-Perfect学习之路[持续更新]

    初识Perfect 在简书上看到了一篇很详细的博客尝试Swift服务器框架 - Perfect 官方文档

  • swoft初窥

    前言 swoole 大名耳闻已久,前段时间发现了基于swoole的高性能协程全栈框架swoft,所以准备初识一下,...

  • swift-perfect-跑起来

    Perfect框架简介 Perfect框架是目前比较完善的Swift服务端框架之一Perfect框架也是开源的,在...

  • SSM Spring

    Spring 分层的java se/ee 应用 全栈式 轻量级开源框架 全栈式:对主流技术和框架进行整合,对三层架...

  • Python全栈快餐教程(1) - 用Flask处理HTTP请求

    Python全栈快餐教程(1) - 用Flask处理HTTP请求 初识Flask Flask是最流行的Python...

  • 如何用pm2部署java全栈项目

    标题说的java全栈项目特指前端采用vue/react/angular等前端框架,后端采用java技术栈开发的项目...

  • Laravel框架-初步认识

    Laravel框架是一款非常流行的PHP全栈开发框架,同样也是一款颇具争议的框架,争议在哪里呢? 一款PHP框架设...

  • readme.md

    目的 全栈学习以及应用,主要聚焦框架以及语言学习,业务简单略过 技术栈 webhtmljs/vuecss 服务器j...

  • Swift Perfect Mac本地环境配置

    教程原文地址 Perfect框架中文文档地址: http://perfect.org/docs/index_zh_...

网友评论

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

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