2016年2月24日 星期三

Swift Vapor Framework on Heroku

大家早,之前文章得到大家的喜愛,雖然沒有超過 300 人回覆,但有超過 300 人看過,筆者為了感謝大家的支持,想介紹如何把 Vapor 的專案放到 Heroku 這個有名的平台上面。讀者應該也是面臨後 Parse 時代,正在尋找替代方案,筆者個人覺得 Heroku 是個不錯的選擇,也撐超過 5 年呢,應該還蠻可靠的。
首先就是先註冊 Heroku 帳號,登入之後,有個 heroku 的工具程式要下載,在這邊下載。下載安裝完在 Terminal 執行 heroku login 就可以登入使用 heroku 的資源,大部分的設定都可以在本機,用 Heroku 的工具程式就可以完成,所以這個程式很重要。
還記得上一篇的 Vapor 專案嗎?如下
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

因爲我們要部署到 Heroku 所以需要一個 Procfile
新增如下

swiftServer/
├── Procfile
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

Procfile 內容如下

web: SwiftServer --port=$PORT

簡單的一行,存檔就行了
接著我們要把這個專案部署到 Heroku
詳細的內容可以參考 Heroku 的教學

接著我們先把 Vapor 專案用 Git 來管理
在 Terminal 輸入
git init
得到
Initialized empty Git repository in /Users/michael/Documents/project/heroku/<某資料夾>/.git/

再輸入
git add .
目的是加入檔案到 git repo
最後要 commit 輸入
git commit -m "first on heroku"
得到

6 files changed, 42 insertions(+)
 create mode 100644 .DS_Store
 create mode 100644 Package.swift
 create mode 160000 Packages/vapor-0.2.3
 create mode 100644 Procfile
 create mode 100644 Resources/index.html
 create mode 100644 Sources/main.swift



然後輸入 heroku create,藉以產生一個 heroku的專案,會看到如下結果。
https://stark-journey-93380.herokuapp.com/ | https://git.heroku.com/stark-journey-93380.git其中  stark-journey-93380 是 heroku 隨機產生的專案名稱,之後還可以改。
回到 Heroku 網頁看一下,會看到如下圖片


有看到一個同樣名稱的 Heroku App在上面。
然後為了可以讓 Heroku 認得是 Swift 的專案
要利用這個 heroku 的 buildpack
在 Terminal 輸入
heroku buildpacks:set https://github.com/kylef/heroku-buildpack-swift.git --app <專案名稱> 
最後的專案名稱就是 heroku 一開始產生給我們的,在這個例子是 stark-journey-93380
得到如下回覆

Buildpack set. Next release on stark-journey-93380 will use https://github.com/kylef/heroku-buildpack-swift.git.
Run git push heroku master to create a new release using this buildpack.

因為 Heroku 有提供 git 機制,所以我們要把這個 local 的 git 和 remote heroku 的 git 結合
利用 git remote -v 來看一下遠端有什麼可以結合的 git repositoty
會看到如下
heroku    https://git.heroku.com/stark-journey-93380.git (fetch)
heroku    https://git.heroku.com/stark-journey-93380.git (push)

這個 local 的 Vapor 專案就是要和 stark-journey-93380結合
輸入
heroku git:remote -a stark-journey-93380
會看到
set git remote heroku to https://git.heroku.com/stark-journey-93380.git
代表結合成功了
最後一個步驟就是把程式碼 push 到 Heroku 上面,輸入
git push heroku master
會得到如下回覆
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (10/10), 1.22 KiB | 0 bytes/s, done.
Total 10 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Fetching set buildpack https://github.com/kylef/heroku-buildpack-swift.git... done
remote: -----> Swift app detected
remote: Cloning into 'swiftenv'...
remote: -----> Installing DEVELOPMENT-SNAPSHOT-2016-02-08-a
remote: Downloading https://swift.org/builds/development/ubuntu1404/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-ubuntu14.04.tar.gz
remote: DEVELOPMENT-SNAPSHOT-2016-02-08-a has been installed.
remote: -----> Installing clang-3.7.0
remote: -----> Building Package
remote: Cloning https://github.com/tannernelson/vapor.git
remote: Using version 0.2.4 of package vapor
remote: Compiling Swift Module 'Vapor' (33 sources)
remote: Linking Library:  .build/release/Vapor.a
remote: Compiling Swift Module 'SwiftServer' (1 sources)
remote: Linking Executable:  .build/release/SwiftServer
remote: -----> Copying dynamic libraries
remote: -----> Copying binaries to 'bin'
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 7.8M
remote: -----> Launching...
remote:        Released v3
remote:        https://stark-journey-93380.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/stark-journey-93380.git
 * [new branch]      master -> master

有點長
因為我們有寫 Heroku buildpack,所以除了把 code 放到 heroku 之外,heroku 還會利用 Swift Package Manager 去下載 compiler 再來 compiler 我們的 Vapor 專案
在 Terminal 可以輸入
heroku open
就可以用預設 Browser 打開 Heroku App 會看到


為什麼事 Page not found 是因為我們沒有定義 / 要做什麼事
所以我們更改路徑爲
https://stark-journey-93380.herokuapp.com/welcome
就會看到

再測試 https://stark-journey-93380.herokuapp.com/view
就會看到

如此,部署完成。
之後如果程式有什麼修改
就用
1. git commit -a
2. git push heroku master

這兩個指令就可以了
如果要更改 Heroku app 名稱要用
heroku apps:rename <新的名稱>
不過要注意名稱不能被其他 Heroku 使用者使用,會更改不成功,多注意回覆就好了。


2016年2月23日 星期二

用 Swift 開發 Server Side App 並利用 Swift Package Manager - Vapor Framework

首先和各位看官說聲 2016 新年快樂。希望大家在新的一年立訂新的目標。這篇文章要如何用 Vapor 這個 Framework 來開發 Server Side App。Vapor 使用 Swift 來架構而成,個人覺得使用起來比起 Swifton 或是 Kitura 還有 Perfect 來的簡潔。有興趣的可以去多多比較。
而 Vapor 剛好利用 Swift Package Manager 的方式來管理套件,有別於比較常見的 CocoaPods 和 Carthage 來管理,Vapor 使用 Apple 提出的 Swift Package Manager 方式來管理。
要提醒讀者,Vapor 更新還蠻頻繁的所以 Google 到的寫法可能是舊的,請看好 Vapor 的版本,這篇用的是 0.2.3 。筆者會持續更新。

安裝 Swift 3.0 Dev


準備工具:Swift 3.0 Dev 從這邊下載,目前 Swift 2.2 Release 還沒支援 Swift Package Manager 所以要下載 Latest Development Snapshot 。
作業環境: OS X El Capitan 10.11.2
下載完會看到一個 swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-osx.pkg
點兩下安裝。如果沒有看到如下安裝畫面
 
這是安全的考量,Mac 認為這個程式不是認證的開發者???
要手動打開,系統偏好設定->安全性與隱私
安裝好之後會在
 /Library/Developer/Toolchains/
看到這幾個資料夾
swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a.xctoolchain/
swift-latest.xctoolchain/
之後就用 swift-latest.xctoolchain/ 就可以了
試一下 Swift 版本
打開 Terminal 輸入以下指令
 /Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift --version
看結果是不是以下
Apple Swift version 3.0-dev (LLVM a7663bb722, Clang 4ca3c7fa28, Swift 1c2f40e246)
Target: x86_64-apple-macosx10.9
如果是 3.0-dev 就沒有問題了。
為了省輸入時間可以在.profile 檔加上
export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:$PATH
開啟 Terminal 就執行  .profile 這樣就直接用 swift 就好了。

建立 Vapor 專案

建立一個資料夾名為 swiftServer,其架構如下
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

Package 內容如下
 
import PackageDescription

let package = Package(
name: "SwiftServer",
dependencies: [
.Package(url: "https://github.com/tannernelson/vapor.git", majorVersion: 0)
 ]
)
稍微解釋一下,Package.swift 的語法也是 Swift 一開始要 import PackageDescription 接著就可以用 Package 這個 class 有興趣的讀者可以在這看到 Package 的全貌。majorVersion 爲 0 因為現在 Vapor 的版本是 0.2.1 majorVersion 爲 0 依照 source code 會找 0..<1 的版本

main.swift 內容如下
 
import Vapor

let app = Application()
app.get("welcome") { request in
 return "Hello Vapor"
}
app.start(port:8080) 
Vapor 裡面有 Application 這個 class 也就 Server 一直執行的 Daemon 。然後綁定在 port 8080,
接著我們利用 app.get 來定義 HTTP GET Method 的 Path。目在這個例子新增 /welcome 這個 Path。
是時候在本機來執行這個 Vapor server 。
在 Package.swift 同一個目錄下,在 Terminal 輸入以下指令。
swift build

成功之後會看到

Cloning https://github.com/tannernelson/vapor.git
Using version 0.2.3 of package vapor
Compiling Swift Module 'Vapor' (32 sources)
/Users/michael/Documents/project/swiftServer/Packages/vapor-0.2.3/Sources/Application+Route.swift:108:13: warning: variable 'handler' was never mutated; consider changing to 'let' constant
        var handler = { request in
        ~~~ ^
        let
Linking Library:  .build/debug/Vapor.a
Compiling Swift Module 'SwiftServer' (1 sources)
Linking Executable:  .build/debug/SwiftServer

看到目前的 Vapor 版本為 0.2.3
然後這理有個 Warning 是 Swift compiler 常見的 Warning ,建議把 var 改成 let 。雖然我們有 source code 但是還是不建議自己改,就給 Vapor 維護者在一個版本改。最後一行是 Linking Executable:  .build/debug/SwiftServer 就是代表 compiler 完的執行檔。
只要在 Terminal 直接執行
.build/debug/SwiftServer
會看到
Server has started on port 8080

然後打開本機的 Browser 在 URL 的地方輸入
http://localhost:8080/welcome

就會看到如下



幾件事提醒

1. 如果有修改 code 要重新利用 swift build 來 compile 之前,請先用 rm .build/debug/SwiftServer 把舊的執行檔刪除。
2. 程式碼的 app.start(port:8080) 請務必是最後一行。
3. 也可以輸出 JSON 比如直接回傳 [ "name":"Michael"],留給讀者自行嘗試。

HTML 結合

輸出的結果有時候需要是 html ,Vapor 也可以輸出已經編寫好的 html 檔案,但必須放在 Resources 這個資料裡面,在看一次專案結構圖。
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

在這 index.html 的內容為

 <html>
    <body>
        <h1>Vapor Server</h1>
This is tutorial for building Vapor Server.
    </body>
</html>
輸入好之後在 main.swift 新增一個 Path
修改完如下
 
import Vapor

let app = Application()
app.get("welcome") { request in
 return "Hello Vapor"
}
app.get("view") { request in
    var view:View? = nil
    do {
        try view = View(path: "index.html")
    }catch {
        print("can not find html : error")
    }
    if let okView = view {
        return okView
    }
    return "Error"
}
app.start(port:8080) 
我們新增了 /view 這個 path。 要注意的是 View 這個 class 的 init?(path:) 會丟出 error,所以要用 do-try-catch 來承接。
最後把 unwrapped 的 okView 回傳就好了。
先把 Server 停掉,ctrl+C
記得
1. rm  .build/debug/SwiftServer
2. swift build

再重新執行
.build/debug/SwiftServer

打開 Browser 輸入,http://localhost:8080/view
會看到


如果這篇很多人回應我們再來分享一篇,如何結合 Database。很多人是多少呢?超過 300 人吧?試試人氣。