从开放源代码,到支持跨平台,再到出现了诸如PerfectVapor这样的服务端开发框架。可以说,近两年来,Swift在服务端开发领域取得了巨大的进步。如果,我们已经掌握了Swift编程语言,那么,用它来学习服务端开发,就变成了一个性价比很高的事情。

如果你之前没经历过服务端开发,没关系,在这个系列里,我们会从零开始,基于Swift Vapor,详细地讲述每一个服务端开发用到的技术细节。如果你有过诸如Node / PHP / Ruby等弱类型语言服务端开发框架的经验,也不妨体会下使用Swift这种静态类型编程语言进行服务端开发的感觉,这种差异微妙并且有趣。

好了,废话不多说,本着一直以来我们Learn by Doing的原则,第一件事,当然就是安装Vapor,并创建自己的工作环境。

安装Vapor

无论你在Mac还是Linux上安装Vapor,首先要做的,是确认一下当前的Swift环境是否兼容Vapor,在Terminal直接执行:

eval "$(curl -sL check.vapor.sh)"

就好了。如果一切正常,在Mac OS环境下,会看到类似下面的结果:


Xcode 9 is compatible with Vapor 2.
Xcode 9 is compatible with Vapor 3.
Swift 4.1 is compatible with Vapor 2.
Swift 4.1 is compatible with Vapor 3.

如果你在Linux环境安装Vapor,就不会有Xcode的兼容性检查了,只有Swift版本的检查。

接下来,就可以安装Vapor了,简单说,它是一个帮助我们管理各种服务端项目的工具。在Terminal,执行下面的命令:

brew install vapor/tap/vapor

在Debian环境,可以执行sudo apt-get install vapor -y进行安装。

这样,所有的准备工作就完成了。接下来,我们就通过一个简单的项目对所谓的服务端开发有一个感性的了解。

第一个服务端项目

准备一个目录,在其中执行下面的命令创建一个服务端API的模板:

vapor new HelloVapor --api --branch=beta

由于Vapor 3.0还没有正式发布,因此我们使用了--branch=beta选项。成功后,就会看到类似下面这样的提示:

Vapor会创建一个叫做HelloVapor的目录,并在其中添加相关的项目文件。

上面--api是指创建一个HTTP API项目的模板,这也是默认的项目类型。实际上Vapor还支持一些其他类型的模板。我们可以执行vapor --help查看。随着我们对Vapor的深入了解,后面会用到这些不同的类型。

了解项目构成

这个API的项目模板,看上去是这个样子的:

Package.swift

实际上,这就是一个通过SPM创建的项目。因此,我们先来看下Package.swift

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "HelloVapor",
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git",
            from: "3.0.0-rc.2"),

        .package(url: "https://github.com/vapor/fluent-sqlite.git",
            from: "3.0.0-rc.2")
    ],
    targets: [
        .target(name: "App", dependencies: ["FluentSQLite", "Vapor"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

可以看到,这个项目有两个依赖关系,一个是vapor,这是Vapor的核心代码;一个是fluent-sqlite,这是Vapor的ORM,等我们讲到和数据库相关的部分时,才会用到它。接下来,是项目的两个target。其中,App用于构建我们的API程序,Run用于执行。最后,是AppTests,用于测试。

其它的项目文件和目录

了解完Package.swift之后,我们看一下Vapor创建的其它文件和项目目录:

  • Public:这里存放的是Web服务所需的静态资源,例如:图片、CSS或JS等;
  • Sources:这是我们工作的主要目录,几乎全部服务端的代码都保存在这个目录里。其中,Sources/Run中存放的是Vapor程序的启动代码,通常我们不需要修改它;而Sources/App则是我们添加各种代码的地方;
  • Tests:顾名思义,这是保存各种测试用例的目录;

除此之外,Vapor还会在HelloVapor中创建一些隐藏的文件和目录。例如,Vapor已经自动为我们创建了一个本地的git repository,以及.gitignore文件:


### Vapor ###
Config/secrets

### Vapor Patch ###
Packages
.build
xcuserdata
*.xcodeproj
DerivedData/
.DS_Store

# End of https://www.gitignore.io/api/vapor

其中包含了服务器的相关秘钥、SPM安装的各种依赖组件、生成的结果以及Xcode相关的项目文件等。显然,它们都不应该或没必要托管到repository中。

生成Xcode项目文件

对项目模板有了一个大体的了解之后,我们就该选择一个编辑器或者IDE来进行开发了。这里还是推荐大家使用Xcode或者AppCode,毕竟对于Vapor这个比较陌生的东西,这些IDE能给予的各种提示,在初期还是非常方便的。

为了可以使用Xcode打开Vapor生成的项目,我们可以在HelloVapor目录,执行:vapor xcode,生成Xcode项目文件之后,Vapor会提示我们是否用Xcode打开:

生成项目的命令可能会执行一段时间,大家耐心等待就好。

输入y,就可以用Xcode打开这个Vapor项目了。此时,之前提到的Sources目录,就在下图中的红框里:

这时,直接按Cmd + B应该可以成功通过编译。

根据Swift以及Vapor版本的差异,可能在构建的过程中会有一些警告,这应该不会影响正常的功能。

但是,按Cmd + R执行,Xcode却没有任何反应,这是因为Xcode默认的target,是Package.swift中的App,我们在Scheme中,改成Run就好了:

这时,按Cmd + R执行,就会在控制台看到下面的提示:

这表示,Vapor已经开始工作了,我们可以直接访问http://localhost:8080/hello,就会看到下面的结果:

这样,Vapor初期的开发环境就准备好了。不过,有一点需要注意的是,当通过Xcode进行开发的时候,最好通过Xcode来管理项目中的文件,而不要直接在Terminal或Finder中创建文件,这种在外部添加的文件,不会被Xcode自动包含到项目中,我们还是要手工拖动进来。

理解Vapor的启动过程

在正式开始动手之前,我们来了解一下Vapor大体的执行过程。这一切,都是从Sources/Run/main.swift开始的。

首先,是引入的modules:

import App
import Service
import Vapor
import Foundation

这里,App就是SPM中创建的App Module,也就是Sources/App这个文件夹的内容,ServiceVapor是Vapor自身的两个Module,提供了Vapor的核心功能,Foundation则是Mac OS自身的Library。

其次,Vapor分别定义了表示服务端配置、执行环境以及相关服务的对象,然后,用App.configure方法加载了这些内容:

var config = Config.default()
var env = try Environment.detect()
var services = Services.default()

try App.configure(&config, &env, &services)

我们先不用管这些方法的首先细节,只要从语义上明白这些代码的功能就好了。

第三,我们用configenvservices创建了一个Application对象,它代表正在执行的Vapor程序:

let app = try Application(
    config: config,
    environment: env,
    services: services
)

第四,把这个app对象传递给App module中的boot方法:

try App.boot(app)

这个boot就定义在Sources/App/boot.swift中,默认情况,这是一个空函数:

public func boot(_ app: Application) throws {
    // your code here
}

之所以要这样做,是Vapor给了我们一个机会,让程序开始执行之前,做一些必要的准备工作。当然现在我们不用做什么,因此让它留空就好了。

最后,执行app.run()启动Vapor,这样我们的应用就就准备就绪,可以接受http请求了。

理解了Vapor启动过程之后,我们再来看一下Vapor的配置过程,这部分代码在Sources/App/configure.swift中:

public func configure(
    _ config: inout Config,
    _ env: inout Environment,
    _ services: inout Services
) throws {
    /// Register providers first
    try services.register(FluentSQLiteProvider())

    /// Register routes to the router
    let router = EngineRouter.default()
    try routes(router)
    services.register(router, as: Router.self)

    /// ...
}

为了简单,我们只节选了一部分代码。这里,用services.register的形式,我们向Vapor注册了一些功能,例如一开始说的FluentSQLite以及EngineRouter,这些注册进来的功能,就叫做Vapor中的Services。这里,EngineRouter是Vapor中专门处理路由的服务。所谓路由,就是把访问的URL和对应的程序逻辑对应起来的过程。简单说,就是之前访问/hello,得到字符串"Hello world!"这样的过程。

自定义路由

接下来,我们修改一下之前访问/hello的路由,让它可以接受一个参数,并根据我们传递的参数动态返回响应的内容。访问每一个URL时,对应的执行逻辑,是在Sources/App/routes.swift中定义的,Vapor默认创建的是这样的:

public func routes(_ router: Router) throws {
    router.get("hello") { req in
        return "Hello, world!"
    }
}

这里,router就是我们之前在configure中注册的EngineRouter对象,get表示处理HTTP GET请求,这个方法接受两个参数:第一个参数表示请求的URL,第二个参数表示接受到请求之后执行的逻辑。这就是为什么我们访问/hello可以得到Hello, world!的原因。

现在,我们要修改一下这个路由,让这个GET /hello请求可以接受一个参数,例如:/hello/Mars。当我们在服务端接受到这个参数之后,动态返回一个欢迎信息。把之前处理/hello的路由改成这样:

router.get("hello", String.parameter) {
    req -> String in
    let name = try req.parameters.next(String.self)
    return "Hello, \(name)!"
}

这次,get接受了第二参数,String.parameter是一个PathComponent对象,表示URL中的一部分。而String.parameter就表示URL中的一段字符串,当然也可以理解成是我们添加在GET请求中的参数。例如:GET /hello/Mars,此时的String.parameter就是Mars。

接下来,在它的closure里,我们可以通过req.parameters.next()这样的方式获取添加在URL后面的参数,后面跟了几个参数,我们就可以调用几次next(),这样就可以在服务端依次得到客户端传递的参数了。这里由于我们只打算通过/hello传递一个参数,因此,只调用了一次next

得到了name之后,我们生成了一个特定的欢迎信息Hello, \(name)!,这样传递不同的请求,我们在网页中看到的欢迎信息就可以动态变化了。

What's next?

以上,就是关于Vapor的一个感性的体验。我们开发了一个只能接受一个GET请求的HTTP API。虽然它很简单,但却表达了一些很重要的概念。接下来,我们就围绕着服务端开发要面对的常见场景,来逐步深入了解Vapor相关组件的用法。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们