Vapor实现Swift的Server搭建

时间:2021-12-19 02:44:50

title: Vapor实现Swift的Server搭建
type: categories
date: 2016-12-29 10:19:06
categories: Swift

tags: [Swift服务器, Vapor,Postgres]

利用开源框架Vapor搭建Swift语言的服务器。

Vapor实现Swift的Server搭建

本文翻译自:https://www.bignerdranch.com/blog/server-side-swift-with-vapor/

一、准备工作

1、Xcode 和 Swift

第一件事就是下载最新的Xcode版本,其支持Swift3。

2、Vapor

第二件事就是安装Vapor和它的toolbox;

Vapor的官方文档:https://vapor.github.io/documentation/getting-started/install-swift-3-macos.html

具体的安装指令:

curl -sL check.vapor.sh | bash
curl -sL toolbox.vapor.sh | bash

注意:

第一条指令是为了验证Xcode的安装是否正确,可能会提醒设置Xcode的 Command Line Tools

Vapor实现Swift的Server搭建

第二条指令是为了安装toolbox工具,Xcode一定要处于打开状态,不然会有类似No such file or directory的报错。

3、Postgres

使用Postgres作为数据库。

如果已经安装了Homebrew,则可以执行下面的指令安装Postgres数据库;

brew install postgres

4、Heroku

在Heroku上面部署自己的云服务器。

需要注册,官网地址:https://dashboard.heroku.com/apps;

下载安装Heroku的Command Line: 地址:https://devcenter.heroku.com/articles/heroku-cli

二、创建Vapor项目

以下指令创建了一个名为 Friends 的App,创建过程较慢,耐心等待。

vapor new Friends

App创建成功之后,cd 到 Hello文件,执行以下指令,便可在Xcode中打开并运行项目。这个过程更加漫长。。。

vapor xcode -y

1、打开默认文件路径: http://localhost:8080

在打开的工程中,导航至 Sources/App/main.swift, 看到的代码如下:

import Vapor

let drop = Droplet()

drop.get { req in
return try drop.view.make("welcome", [
"message": drop.localization[req.lang, "welcome", "title"]
])
}

drop.resource("posts", PostController())

drop.run()

这个文件导入了Vapor框架,初始化一个Droplet类型对象,和添加了一个默认路径;

配置Xcode,如下,并Command+R运行:

Vapor实现Swift的Server搭建

在浏览器中打开 http://localhost:8080,

Vapor实现Swift的Server搭建

2、创建一个新的访问路径: http://localhost:8080/friends

同样实在main.swift文件中操作,具体添加后的代码如下:

import Vapor

let drop = Droplet()

drop.get { req in
return try drop.view.make("welcome", [
"message": drop.localization[req.lang, "welcome", "title"]
])
}

drop.get("friends") { req in
return try JSON(node: ["friends": [["name": "Sarah", "age": 33],
["name": "Steve", "age": 31],
["name": "Drew", "age": 35]]
])
}

drop.resource("posts", PostController())

drop.run()

friends路径接受get请求,返回一组json数据。浏览器访问效果如下:

Vapor实现Swift的Server搭建

3、创建一个Friend类型的Model

Sources/App/Models下创建一个Friend.swift文件:

struct Friend {
let name: String
let age: Int
let email: String

init(name: String, age: Int, email: String) {
self.name = name
self.age = age
self.email = email
}
}

注意:添加了一个email属性

Vapor提供了Model协议来保存model类型,同时也提供了方法来实现model到json的转换。为了结构体Friend遵从Model协议,我们需要导入在Friend.swift中导入Vapor库。

导入Vapor库的同时也意味着导入了Fluent库 。Fluent是针对Swift,处理众多数据库的对象关系映射工具(object relational mapping)。在这里我们使用它来处理我们的Postgres数据库。

注意:当看到Xcode导入Vapor库之后,可能会看到报错,此时返回终端,重新执行 vapor xcode -y;

最终的Friend.swift文件应该是下面的样子:

import Foundation
import Vapor

struct Friend: Model {
var exists: Bool = false
var id: Node?
let name: String
let age: Int
let email: String

init(name: String, age: Int, email: String) {
self.name = name
self.age = age
self.email = email
}

// NodeInitializable
init(node: Node, in context: Context) throws {
id = try node.extract("id")
name = try node.extract("name")
age = try node.extract("age")
email = try node.extract("email")
}

// NodeRepresentable
func makeNode(context: Context) throws -> Node {
return try Node(node: ["id": id,
"name": name,
"age": age,
"email": email])
}

// Preparation
static func prepare(_ database: Database) throws {
try database.create("friends") { friends in
friends.id()
friends.string("name")
friends.int("age")
friends.string("email")
}
}

static func revert(_ database: Database) throws {
try database.delete("friends")
}
}

第一件事是我们要实现Model协议。意味着,我们需要一个Node?类型的id属性;id的值在保存到数据库之前是nil。Friend同时有一个默认值为falseexists的属性,这个属性展示的是从数据库中是否获取到实例对象(This property details whether or not the instance was retrieved from the database and should not be interacted with directly.)。

同时,我们还需要实现其他的协议。NodeInitializableNodeRepresentablePreparation。其中,前两个协议 的实现是因为 Model协议继承于Entity协议。第一个协议告诉我们怎样从数据库中初始化model;第二个协议是怎样保存model到数据库中。第三个协议是怎样创建数据库。

接下来,我们在main.swift中利用上面的Friend,在新的路径 friends下创建一个json,如下:

drop.get("friends") { req in
let friends = [Friend(name: "Sarah", age: 33, email:"sarah@email.com"),
Friend(name: "Steve", age: 31, email:"steve@email.com"),
Friend(name: "Drew", age: 35, email:"drew@email.com")]
let friendsNode = try friends.makeNode()
let nodeDictionary = ["friends": friendsNode]
return try JSON(node: nodeDictionary)
}

我们创建了一个包含三个Friend对象的friends数组。之后数组调用makeNode()方法转换成一个Node。之后的字典和json结果。

重新运行Command+R,在浏览器中访问http://localhost:8080/friends,即可看到效果。

4、配置Postgres数据库

设置Postgres涉及几个步骤:

  1. Package.swift中为Postgres添加一个provider;
  2. 在main.swift中导入provider,用Droplet来使用它;
  3. 配置我们的app使用Postgres;
获取一个provider

 postgres-provider是一个优秀的provider。在Package.swift添加这个依赖。

import PackageDescription

let package = Package(
name: "Friends",
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 1),
.Package(url: "https://github.com/vapor/postgresql-provider", majorVersion: 1, minor: 0)
],
exclude: [
"Config",
"Database",
"Localization",
"Public",
"Resources",
"Tests",
]
)

之后,在终端执行 vapor xcode -y

导入provider

打开main.swift,准备使用数据库Postgres。当然需要先安装Postgres。

Postgres地址:https://github.com/vapor/postgresql

MacOS上的Postgres安装使用指令:

brew install postgresql
brew link postgresql
brew services start postgresql

// to stop
brew services stop postgresql

注:我在Mac上brew install postgresql时出现404报错,导致Postgres安装失败,所以接下来的内容就没办法跟着原文继续实现。

首先,导入 VaporPostgreSQL;

其次,为Droplet的preparations添加Friend.self ;

最后,drop.addProvider(VaporPostgreSQL.Provider.self),添加porvider到drop以使用数据库。

在工程中配置Progres

在工程的Config文件夹下创建 secrets文件夹,并在secrets下创建 postgresql.json。最终的路径应该是这样的 Config/secrets/postgresql.json;而postgresql.json中内容如下:

{
"host": "127.0.0.1",
"user": "DubbaDubs",
"password": "",
"database": "friends",
"port": 5432
}

注意:user要用你自己的;friends只是我们生命的一个数据库,仍旧需要创建它。

使用Protgres

接下来,我们创建一个新的路径,执行POST方法,将Friend数据保存到数据库;

import Vapor
import VaporPostgreSQL

let drop = Droplet()
drop.preparations.append(Friend.self)

do {
try drop.addProvider(VaporPostgreSQL.Provider.self)
} catch {
print("Error adding provider: \(error)")
}

drop.get { req in
return try drop.view.make("welcome", [
"message": drop.localization[req.lang, "welcome", "title"] ])
}

drop.get("friends") { req in
let friends = [Friend(name: "Sarah", age: 33, email:"sarah@email.com"),
Friend(name: "Steve", age: 31, email:"steve@email.com"),
Friend(name: "Drew", age: 35, email:"drew@email.com")]
let friendsNode = try friends.makeNode()
let nodeDictionary = ["friends": friendsNode]
return try JSON(node: nodeDictionary)
}

drop.post("friend") { req in
var friend = try Friend(node: req.json)
try friend.save()
return try friend.makeJSON()
}

drop.resource("posts", PostController())

drop.run()

这个POST路径的body,类似下面:

{
"name": "Some Name",
"age": 30,
"email": "email@email.com"
}

我们尝试用req.json创建一个Friend类型的实例对象,作为POST请求发送出去;

之后,调用friend.save()将对象保存到数据库;

关于friend为什么用 Var .save()的理解:friend中有一个在保存到数据库之前是nil的id属性,这里的作用就是当成功保存到数据后,在回调中修正这个id的值。

我们仍旧需要创建数据库

postgres -D /usr/local/var/postgres/

在终端执行上面的指令,Protgres 服务将在本地运行;一旦服务开始运行,我们就可以创建数据库。

在新的终端窗口,执行

createdb friends
psql

之后,可以输入 \l来查看数据库列表,应该就可以看到你的friends数据库。

最后,你可以利用POSTMAN等工具发送一个post请求,测试一下。

然后用 psql来确认你发送数据到数据库了。

You can test this new route by building and running your app within Xcode as you did above and using curl or a tool like Postman. You can use psql to verify that you are posting data to the database. From inside the command line interface, type \c friends to connect to your friends database. Next, type SELECT * FROM friends; and hit enter. You should see the information for the friend that you just POSTed to the "friend" route.

Removing Hardcoded Data

GET requests to our "friends" currently return hardcoded data. But we can POSTfriends to our database now! Go ahead and POST a few more friends to your database to fill it out a little more.

Back in main.swift, let’s update our "friends" route to return the data in our database. That method should now look like this:

drop.get("friends") { req in
let friends = try Friend.all().makeNode()
let friendsDictionary = ["friends": friends]
return try JSON(node: friendsDictionary)
}

Visit http://localhost:8080/friends in your browser or send a GET request via Postman and you should see your friends returned to you.

GETting by id

Add a new route to main.swift that will use a user’s id to find an entry in the database.

drop.get("friends", Int.self) { req, userID in
guard let friend = try Friend.find(userID) else {
throw Abort.notFound
}
return try friend.makeJSON()
}

The above will match a route that will end in something like, .../friends/1, where the integer at the end is the friend’s id. Notice that we use Int.self as the second argument in the path. This means that we can have type safe parameters in our routes! In Vapor, there is no need to cast this parameter from a String to an Int. We can express exactly what we need. The userID parameter in the closure will match the integer passed at the end of the route.

Also, through the power of Fluent, we can simply find(_:) the instance of our model by its id. Since this lookup can fail, we must try. If we fail to find a Friend, we’ll throw the error Abort.notFound. Otherwise, we makeJSON() from the friend we found and return. Go ahead and try it out!

部署到 Heroku

All that is left to do is to deploy to Heroku. Vapor makes this process very easy.

Heroku works with Git, so you should make sure that you have that installed as well. Create a Git repository and commit your files.

git init
git add .
git commit -m "Initial commit"

Now, all you need to do is create a Heroku instance and push to it.

vapor heroku init

The Heroku CLI will ask four questions:

Would you like to provide a custom Heroku app name? Answer ‘n’ and hit enter if you don’t have a custom name.

Would you like to provide a custom Heroku buildpack? Answer ‘n’ and hit enter if you would like to use the default buildpack that Heroku provides.

Are you using a custom Executable name? Answer ‘n’ and hit enter here if you aren’t using a custom executable name.

Would you like to push to Heroku now? Answer ‘n’ and hit enter.

We need to answer ‘no’ because we need to configure our database to work online. We’ll do that below.

Before you plug the URL you see in your Terminal output into your browser of choice, we need to provision a Postgres add-on on Heroku. Type the following in your Terminal to add Postgres to your app.

heroku addons:create heroku-postgresql:hobby-dev

Adding the database can take up to five minutes to provision. Once it is ready, you can type heroku config in Terminal to see your database’s URL to confirm that the add-on was provisioned.

Now, we need to update our app’s Procfile, which was created via vapor heroku init, to use the DATABASE_URL environment variable that was created by Heroku. Open the Procfile in your text editor and update it so that looks like the below.

web: App --env=production --workdir="./"
web: App --env=production --workdir=./ --config:servers.default.port=$PORT --config:postgresql.url=$DATABASE_URL

Notice that we added a new configuration so that Heroku knows to use our database’s URL on the web and not the instance we have been running locally: --config:postgresql.url=$DATABASE_URL.

Save the Procfile and type git push heroku master in Terminal to deploy your app.

Deploying will take a few minutes. Heroku needs to build your application, install Swift on your Heroku instance, and so on. After some time, your app will be live on the web.

Note that we didn’t push up the data from the local database to the remote database. You will have to exercise your app’s API to add some data to your database on Heroku.

Making Changes

Making changes is easy! Simply write your code, commit the changes, and push them up to Heroku.

git commit -am "Adds new code"
git push heroku master

Wrapping Up

This post covered a lot of ground: We downloaded a number of tools. Introduced Vapor. Wrote a small web app. Developed an API to read and write some data to Postgres. Finally, we deployed the app to Heroku.

This post is intended to you get started with server-side Swift. To that end, we introduced Vapor and explored some of its features. But this introduction just scratches the surface; there is a lot more to Vapor. What’s exciting is that server-side Swift is moving fast! Stay tuned here for more posts on the subject.

Challenges

If you’re looking for more to do on your own, try your hand at these extra challenges.

  • Create a route to DELETE a friend from the database.
  • Create a route to PATCH a friend on the database.