Swift 後端開發

山河永寂發表於2016-11-20

原文來自靜雅齋,轉載請註明出處。

作為一門新興的現代化語言,Swift 可以說是蘋果在開發語言上的一次集大成之作,吸收了很多語言的優點。而且蘋果還期望 Swift 能在服務端開發上能發揮作用。更加誘人的是,作為一種編譯型語言,有著 C++ 一般的效能,並且相比 Golang、Java 來說使用 ARC 管理記憶體避免了 GC 導致程式停頓。可以說 Swift 就是程式設計師夢寐以求的語言。

Swift 目前在後端開發的缺點

雖然 Swift 本身有很多優點,但是在後端開發上依舊任重道遠,比如有以下問題:

  1. 目前只適配了 Ubuntu 下的二進位制包,沒有 RHEL 等其他 Linux 下的二進位制包
  2. 缺乏非 Mac 系統下的 IDE 開發,這個目前看起來好像只能指望 Jetbrains 了
  3. 沒有其他語言那麼完善的生態系統
  4. 缺乏的文件,比如包管理系統的語法文件,必須自行檢視原始碼
  5. 沒有交叉編譯鏈,不能在 Mac 上面編譯出 Linux 可用的二進位制檔案
  6. 缺乏好用的單元測試

但是這些問題目前都有方法克服,比如使用 Docker 作為承載 Swift 程式的容器,而使用 Mac 來開發 Swift 程式也不是很大的問題,因為大多數的後端開發都是用 Mac 開發的。

Docker 的作用

筆者個人認為 Docker 解決的最大的問題就是開發環境和生產環境的矛盾,對於開發人員來說,追新永遠是必備素質,而測試和運維不會希望環境變更導致的問題,比如線上伺服器跑的是 CentOS,而 Swift 則是必須在 Ubuntu 上執行,但是 Docker 的出現就能解決這個問題。筆者認為最適合執行在 Docker 中的就是像 Web 這樣的服務,Nginx 和資料庫之類的就不適合放在 Docker 中,因為它們是有狀態的,而且 Docker 這樣的快速消亡快速建立的模式也不適合資料庫這樣對資料有著嚴格要求的應用。當然,Kubernetes 目前推出的 petset 就很適合資料庫這樣的有狀態的應用。

Perfect 框架

Perfect 框架是 Swift 開發的 Web 應用伺服器,它支援包括 Redis、SQLite、PostgreSQL、MySQL、MongoDB、FileMaker 這樣的資料庫,並且能以 fastcgi 或者 Web 伺服器的形式提供服務。更加美妙的是,還有高質量的中文文件。

HelloWorld

Perfect 提供了基礎模板工程,可以使用以下命令下載

> git clone git@github.com:PerfectlySoft/PerfectTemplate.git複製程式碼

然後安裝依賴

> swift package fetch複製程式碼

然後就能編譯執行了

# 以 Debug 方式編譯
> swift build
# 以 Release 方式編譯
> swift build -c release複製程式碼

分析 HelloWorld

HelloWorld 工程依賴了 Perfect-HTTPServer 模組,然後其中有兩個原始檔,arguments.swiftmain.swift
main.swift

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

// Create HTTP server.
let server = HTTPServer()

// Register your own routes and handlers
var routes = Routes()
routes.add(method: .get, uri: "/", handler: {
        request, response in
        response.setHeader(.contentType, value: "text/html")
        response.appendBody(string: "Hello, world!Hello, world!")
        response.completed()
    }
)

// Add the routes to the server.
server.addRoutes(routes)

// Set a listen port of 8181
server.serverPort = 8181

// Set a document root.
// This is optional. If you do not want to serve static content then do not set this.
// Setting the document root will automatically add a static file handler for the route /**
server.documentRoot = "./webroot"

// Gather command line options and further configure the server.
// Run the server with --help to see the list of supported arguments.
// Command line arguments will supplant any of the values set above.
configureServer(server)

do {
    // Launch the HTTP server.
    try server.start()
} catch PerfectError.networkError(let err, let msg) {
    print("Network error thrown: \(err) \(msg)")
}複製程式碼

用過 Node 的 express 框架的朋友是不是感覺很熟悉,使用事件迴圈處理 HTTP 請求,事實上早期的 Perfect 框架用的就是 libev 框架來處理事件迴圈的 HTTP 請求。
arguments.swift

import PerfectHTTPServer
import PerfectLib

#if os(Linux)
    import SwiftGlibc
#else
    import Darwin
#endif

// Check all command line arguments used to configure the server.
// These are all optional and you can remove or add arguments as required.
func configureServer(_ server: HTTPServer) {

    var sslCert: String?
    var sslKey: String?

    var args = CommandLine.arguments

    func argFirst() -> String {
        guard let frst = args.first else {
            print("Argument requires value.")
            exit(-1)
        }
        return frst
    }

    let validArgs = [
        "--sslcert": {
            args.removeFirst()
            sslCert = argFirst()
        },
        "--sslkey": {
            args.removeFirst()
            sslKey = argFirst()
        },
        "--port": {
            args.removeFirst()
            server.serverPort = UInt16(argFirst()) ?? 8181
        },
        "--address": {
            args.removeFirst()
            server.serverAddress = argFirst()
        },
        "--root": {
            args.removeFirst()
            server.documentRoot = argFirst()
        },
        "--name": {
            args.removeFirst()
            server.serverName = argFirst()
        },
        "--runas": {
            args.removeFirst()
            server.runAsUser = argFirst()
        },
        "--help": {
            print("Usage: \(CommandLine.arguments.first!) [--port listen_port] [--address listen_address] [--name server_name] [--root root_path] [--sslcert cert_path --sslkey key_path] [--runas user_name]")
            exit(0)
        }]

    while args.count > 0 {
        if let closure = validArgs[args.first!.lowercased()] {
            closure()
        }
        args.removeFirst()
    }

    if sslCert != nil || sslKey != nil {
        if sslCert == nil || sslKey == nil {
            print("Error: if either --sslcert or --sslkey is provided then both --sslcert and --sslkey must be provided.")
            exit(-1)
        }
        if !File(sslCert!).exists || !File(sslKey!).exists {
            print("Error: --sslcert or --sslkey file did not exist.")
            exit(-1)
        }
        server.ssl = (sslCert: sslCert!, sslKey: sslKey!)
    }
}複製程式碼

這裡就很簡單了,就是提供引數用於 HTTP 伺服器的建立,而這個好處就是能通過引數獲得更多功能。

建立自己的工程

首先使用 swift package init 命令建立工程,一般來說如下

> swift package init --type executable複製程式碼

然後在 Package.swift 中增加依賴,但是 Swift 目前所有的 IDE 都沒有提供對 PackageDescription 模組的程式碼提示,估計是因為這是 Swift 內建模組。具體內容得到 Swift 原始碼中可以找到。
一般來說只要增加如下內容

import PackageDescription

let package = Package(
    name: "XXXX",
    dependencies: [
        .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minor: 0)
    ]
)複製程式碼

然後建立 main.swift 檔案,並且建立 HTTPServer 偵聽埠,就能建立自己的工程了。

Docker

一般來說,Docker 目前想要執行 Swift 必須使用 Ubuntu 的映象,因為 Swift 的預編譯包只提供 Ubuntu 的壓縮包,但是很多 Docker 映象存在很多問題,比如缺少支援庫,所以需要作出以下修改,下面提供一個樣例

FROM swiftdocker/swift:3.0.1
MAINTAINER ChasonTang 

RUN apt-get update \
    && apt-get install -y uuid-dev libcurl4-openssl-dev libssl-dev \ 
    && git clone https://github.com/PerfectlySoft/PerfectTemplate /usr/src/PerfectTemplate \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/src/PerfectTemplate
RUN swift build -c release
CMD .build/release/PerfectTemplate --port 80複製程式碼

Perfect 需要 libcurl 是因為 swift build 獲取依賴的時候是使用 curl 來獲取程式碼的,uuid 是因為 Perfect 框架內建函式庫所需,而 openssl 則是 Perfect 依賴的庫所需。這裡使用的是 git clone 的方式獲取工程程式碼,但是也可以通過 COPY 指令複製當前目錄下的檔案。

相關文章