Server-side Swift, Perfectを使ってHelloWorld ~HTTP Server編~

あけましておめでとうございます。 前回のSwiftLinux環境を作った記事の続きです。

今回のゴールはSwiftフレームワークであるところのPerfectを用いてHTTPレスポンスを返すところがゴールになります。

環境等は前回の続きです。

とりあえずログイン。 今回はgitを利用するので、鍵等はssh-agentを利用していきます。

$ ssh-add ~/.ssh/github用key
$ vagrant ssh

サーバを立ててみる

Perfectのダウンロード

$ git clone git@github.com:PerfectlySoft/Perfect.git

Perfectを利用する場合、まずはサーバとライブラリのビルドをする必要があります。

$ cd Perfect/
$ make

Makefileの中身はこちら。 どうやらPerfectLibMakefilePerfectServerMakefileを叩いているようです。

ちなみにPerfectServerMakefileは下記です。

# Makefile for Perfect Server

TARGET_FCGI = perfectserverfcgi
TARGET_HTTP = perfectserverhttp
DEBUG = -g -Onone -Xcc -DDEBUG=1
OS = $(shell uname)
SWIFTC = swift
SWIFTC_FLAGS = -frontend $(DEBUG) -c -module-cache-path $(MODULE_CACHE_PATH) -emit-module -I /usr/local/lib -I ../PerfectLib/linked/LibEvent \
    -I ../PerfectLib/linked/OpenSSL -I ../PerfectLib/linked/ICU -I ../PerfectLib/linked/SQLite3 -I ../PerfectLib/linked/LinuxBridge
MODULE_CACHE_PATH = /tmp/modulecache
Linux_SHLIB_PATH = $(shell dirname $(shell dirname $(shell which swiftc)))/lib/swift/linux
SHLIB_PATH = -L$($(OS)_SHLIB_PATH)
LFLAGS = $(SHLIB_PATH) -g -lFoundation -lswiftCore -lswiftGlibc /usr/local/lib/PerfectLib.so -Xlinker -rpath -Xlinker $($(OS)_SHLIB_PATH)

all: modulecache $(TARGET_FCGI) $(TARGET_HTTP)


install: all
    ln -sf `pwd`/$(TARGET_FCGI) /usr/local/bin/
    ln -sf `pwd`/$(TARGET_HTTP) /usr/local/bin/

modulecache:
    @mkdir -p $(MODULE_CACHE_PATH)

$(TARGET_FCGI): $(TARGET_FCGI).o
    clang++ $(LFLAGS) $@.o -o $@

$(TARGET_HTTP): $(TARGET_HTTP).o
    clang++ $(LFLAGS) $@.o -o $@

$(TARGET_FCGI).o: main_fcgi.swift
    $(SWIFTC) $(SWIFTC_FLAGS) main.swift $< -o $@ -module-name $(subst .o,,$@) -emit-module-path $(subst .o,,$@).swiftmodule

$(TARGET_HTTP).o: main_http.swift
    $(SWIFTC) $(SWIFTC_FLAGS) main.swift $< -o $@ -module-name $(subst .o,,$@) -emit-module-path $(subst .o,,$@).swiftmodule

clean:
    @rm *.o

どうやらswift -frontendでオブジェクトファイルを生成し、clang++コンパイルしているようです。

makeすると、perfectserverfcgiperfectserverhttpが生成されて、make installするとコマンドのパスを通すところまでやってくれます。

上記コマンドをコンパイルするときにPerfectLibに依存しているため、そちらのビルドが先に必要なようです。ナルホド。

これでサーバを立てる準備が整いましたので、とりあえずHTTPサーバを起動してみます。 下記の通りデフォルトでは8181番ポートに割り当てられています。

$ Perfectserverhttp
Current working directory: /home/vagrant/Perfect/PerfectServer
Starting HTTP server on 0.0.0.0:8181

とりあえずリクエストを投げてみます。

curl localhost:8181
The file "/" was not found.

HTTPレスポンスが返ってきました。どうやら動いてるっぽいですね。

次に出力用のHTMLを用意して動作を確認します。

main_http.swiftファイルを見るとわかりますが、デフォルトのドキュメントルートはプロジェクト直下のwebroot/になっていました。

PerfectServerはHTMLあるいは画像リソース、Mustacheのテンプレートを扱うことができますので、とりあえず適当なHTMLを用意。

$ echo 'Hello World!!' > webroot/index.html

実際にHTMLを解釈してレスポンスが返ってくるか確認しましょう。

$ curl localhost:8181
Hello World!!

正しくレスポンスが返ってきていそうです。非常に簡単ですね。

ルーティング用のスクリプトを書いてみる

PerfectServerのREADMEを読む限り、ハンドラー用のModuleを作成して利用する機能があるようです。

ModuleはPerfectServerModuleInitという関数をもった共有ライブラリとして作成し、PerfectLibrariesディレクトリに配置することで、PerfectServerに登録されます。

PerfectServer.swiftファイルのこの辺りを見るとその様子が伺えます。

また、実際にルーティングを行っているサンプルがこちらにあります。

下記がルーティングの登録部分の抜粋です。

PerfectServerModuleInit()内でRouting.Handler.Register()を呼び出して登録し、RoutingRoutes(RoutMap構造体)に対して、パスと振る舞いを表すクラスを登録することで、動作を定義することができます。

public func PerfectServerModuleInit() {

    // Install the built-in routing handler.
    // Using this system is optional and you could install your own system if desired.
    Routing.Handler.registerGlobally()

    Routing.Routes["GET", ["/", "index.html"] ] = { (_:WebResponse) in return IndexHandler() }
    Routing.Routes["/foo/*/baz"] = { _ in return EchoHandler() }
    Routing.Routes["/foo/bar/baz"] = { _ in return EchoHandler() }
    Routing.Routes["GET", "/user/{id}/baz"] = { _ in return Echo2Handler() }
    Routing.Routes["POST", "/user/{id}/baz"] = { _ in return Echo3Handler() }

    // Check the console to see the logical structure of what was installed.
    print("\(Routing.Routes.description)")
}

振る舞いを表すクラスは、RequestHandlerを継承しhandleRequest()を実装したものとなります。

リクエストを表すWebRequestのオブジェクトが渡ってくるので、それを用いて特定の処理を行い、WebResponserequestCompletedCallback()を呼び出すことでレスポンスを返す事ができます。

サンプルではレスポンスボディに文字列を含めて返すものを実装していました。

class EchoHandler: RequestHandler {

    func handleRequest(request: WebRequest, response: WebResponse) {
        response.appendBodyString("Echo handler: You accessed path \(request.requestURI()) with variables \(request.urlVariables)")
        response.requestCompletedCallback()
    }
}

上記サンプルを参考にして、Hello Worldを返す処理を実装してみます。

まずはModuleのソースを用意します。今回はこんな感じで実装しました。

import PerfectLib

public func PerfectServerModuleInit() {
    Routing.Handler.registerGlobally()

    Routing.Routes["GET", "/"] = { _ in return IndexHandler() }
    print("\(Routing.Routes.description)")
}

class IndexHandler: RequestHandler {

    func handleRequest(request: WebRequest, response: WebResponse) {
        response.appendBodyString("Hello World!! with Request Handler\n")
        response.requestCompletedCallback()
    }
}

次にソースをコンパイルしていきます。

$ swift -frontend -c -module-cache-path /tmp/modulecache -emit-module -I /usr/local/lib -I /home/vagrant/Perfect/PerfectLib/linked/LibEvent -I \
/home/vagrant/Perfect/PerfectLib/linked/OpenSSL_Linux -I /home/vagrant/Perfect/PerfectLib/linked/ICU \
-I /home/vagrant/Perfect/PerfectLib/linked/SQLite3 -I /home/vagrant/Perfect/PerfectLib/linked/LinuxBridge \
"URLHandlers.swift" -o URLRouting.o -module-name URLRouting -emit-module-path URLRouting.swiftmodule
$ clang++ -L/home/vagrant/swift/swift-2.2-SNAPSHOT-2015-12-21-a-ubuntu14.04/usr/lib/swift/linux \
-lFoundation -lswiftCore -lswiftGlibc /usr/local/lib/PerfectLib.so \
-Xlinker -rpath -Xlinker /home/vagrant/swift/swift-2.2-SNAPSHOT-2015-12-21-a-ubuntu14.04/usr/lib/swift/linux \
-shared URLRouting.o -o URLRouting.so

結果、下記のファイル群が生成されました。

drwxrwxr-x 2 vagrant vagrant  4096 Jan  8 02:23 ./
drwxrwxr-x 3 vagrant vagrant  4096 Jan  8 01:31 ../
-rw-rw-r-- 1 vagrant vagrant   395 Jan  8 02:23 URLHandlers.swift
-rw-rw-r-- 1 vagrant vagrant  9624 Jan  8 02:23 URLRouting.o
-rwxrwxr-x 1 vagrant vagrant 20003 Jan  8 02:23 URLRouting.so*
-rw-rw-r-- 1 vagrant vagrant 27924 Jan  8 02:23 URLRouting.swiftmodule

このうち、共有ファイルであるところのURLRouting.soをプロジェクト直下の PerfectLibrariesディレクトリにつっこんでperfectserverhttpを起動すれば動作が確認できます。

mkdir -p /home/vagrant/Perfect/PerfectLibraries/
cp URLRouting.so /home/vagrant/Perfect/PerfectLibraries/.

サーバを立てなおして動作確認をします

$ perfectserverhttp
$ curl localhost:8181
Hello World!! with Request Handler

作成したモジュールが読み込まれて、文字列の出力が確認できました。

次回はPerfectが採用しているテンプレートエンジンであるところのMustacheあたりを触ってまとめようかと思っています。