これは Qiita のアドベントカレンダー 2018 の初日のエントリーです。
特に Swift コミュニティに何の貢献もしていないのですが、空いてたのでついポチッと登録してしまいました。
qiita.com
2018 年の 10 月 4 日に Prime Video のエンジニアの Simon さんのツイートが僕のTLに流れてきました。
Amazon が Swift 製の軽量サーバーサイドフレームワーク smoke-framework を作ったとのこと。
早速ためしてみたかったのですが、閃の軌跡というゲームをやるために忙しくて、ずっと試せていませんでした。
ほぼ2ヶ月経過して日本語の紹介記事等がなさそうなので、紹介してみることにしました。
smoke-framework は Swift-NIO の上に作られた軽量 Web フレームワークです。 JSON でのリクエスト-レスポンスの機能だけを提供しているだけのシンプルなものとなっています。
github.com
ここでは例として POST されたメッセージをそのまま返す Echo HTTP アプリケーションを作ってみます。
インストール
Swift Package Manager を使います。 swift package init
でプロジェクトを初期化します。
mkdir ExampleResponseApp
cd ExampleResponseApp
swift package init --type executable
この後に、 Package.swift
が次の通りになるように dependencies
/targets
/products
に追記します。
import PackageDescription
let package = Package(
name: "ExampleResponseApp",
products: [
.executable(name: "ExampleResponseApp", targets: ["ExampleResponseApp"]),
],
dependencies: [
.package(url: "https://github.com/amzn/smoke-framework.git", .upToNextMajor(from: "0.6.0")),
],
targets: [
.target(name: "ExampleResponseApp", dependencies: ["SmokeOperationsHTTP1"]),
.testTarget(name: "ExampleResponseAppTests", dependencies: ["ExampleResponseApp"]),
]
)
アプリケーション
このフレームワークでアプリケーションを作る場合、以下の3ステップで作れます。
- オペレーション関数を記述する
- パスとオペレーション関数を結びつけたハンドラーセレクターを作る
- ハンドラーセレクターをサーバーにわたす
以下のプログラムは次のモジュールをインポートしておきます。なお、 LoggerAPI
は smoke-framework が依存している IBM-Swift の LoggerAPI です。
import SmokeHTTP1
import SmokeOperationsHTTP1
import SmokeOperations
import NIOHTTP1
import LoggerAPI
オペレーション関数
オペレーション関数として次の型を満たす関数を作ります
(InputType, ContextType) throws -> OutputType
オペレーション関数に挙げられている型は特定のプロトコルを満たしている必要があります。README には書かれていますがチュートリアルの部分には書いていないため、最初戸惑いました。
ContextType
ContextType
はアプリケーションを実行する際の様々な情報を持つデータで、ユーザーは任意の型を作れます。
とりあえずは、このような struct
を作ってみます
struct MyAppContext {}
InputType
と OutputType
次に、 InputType
と OutputType
ですが、こちらは ValidatableCodable
プロトコルを満たす必要があります。 ValidatableCodable
プロトコルの定義は次のようになっています。
public typealias ValidatableCodable = Validatable & Codable
public protocol Validatable {
func validate() throws
}
これを満たすように InputType
と OutputType
を作ります。なお、ここにある ErrorResponse
型については次で述べます。
struct Message {
let text: String
}
extension Message: ValidatableCodable {
func validate() throws {
if self.text.isEmpty {
throw ErrorResponse.badRequest
}
}
}
エラーの型
投げるエラーの型は ErrorIdentifiableByDescription
プロトコルを満たす必要があります。これの定義は次のとおりです。
typealias ErrorIdentifiableByDescription = Swift.Error & CustomStringConvertible
これを満たすように ErrorResponse
を書いてみます。
enum ErrorResponse {
case badRequest
}
extension ErrorResponse: ErrorIdentifiableByDescription {
var description: String {
return "bad request"
}
var statusCode: Int {
return 400
}
}
オペレーション関数を組み立て
上記の条件を満たした後、オペレーション関数を組み立てます。届いたメッセージをそのまま返す単純な関数です。
func echo(message: Message, context: MyAppContext) throws -> Message {
Log.info("message: \(message.text)")
return message
}
ハンドラーセレクターを作る
作成したオペレーション関数とパスのマッピングを作ります。まずは次のような型エイリアスをつくります。
public typealias HandlerSelector =
StandardSmokeHTTP1HandlerSelector<MyAppContext, JSONPayloadHTTP1OperationDelegate>
StandardSmokeHTTP1HandlerSelector
には addHandlerForUri(_ uri: httpMethod: operation: allowedErrors: operationDelegate:)
という関数が生えているので、この関数にパスとオペレーション関数を渡します。
func handlerSelector() -> HandlerSelector {
var selector = HandlerSelector()
selector.addHandlerForUri(
"/example",
httpMethod: .POST,
operation: echo,
allowedErrors: [(ErrorResponse.badRequest, ErrorResponse.badRequest.statusCode)],
operationDelegate: nil)
return selector
}
アプリケーション組み立て
アプリケーションを組み立てます。先程準備したコンテキストオブジェクトとハンドラーセレクターを SmokeHTTP1Server
の class 関数 startAsOperationServer
に渡します。
do {
try SmokeHTTP1Server.startAsOperationServer(
withHandlerSelector: handlerSelector(),
andContext: MyAppContext(),
defaultOperationDelegate: JSONPayloadHTTP1OperationDelegate())
} catch {
Log.error("failed to start server: \(error)")
}
これをそのまま実行してもよいのですが、デフォルトの LoggerAPI はログを出力しません。そこで、次のような Logger
をつくって、 Logger
に登録します。
struct StandardOutLogger: Logger {
func log(_ type: LoggerMessageType, msg: String, functionName: String, lineNum: Int, fileName: String) {
if isLogging(type) {
print("\(type) (\(fileName) - \(functionName) -\(lineNum)) - \(msg)")
}
}
func isLogging(_ level: LoggerMessageType) -> Bool {
switch level {
case .info, .error, .warning: return true
default: return false
}
}
}
アプリケーションを実行
これでアプリケーションの完成です。次のコマンドでアプリケーションを実行してみます。
swift run ExampleResponseApp
次のようなログが出てきて、ポート 8080 でサーバーが起動していることがわかります。ポートの選択の仕方が Java っぽいです。
INFO (/Users/user/ExampleResponseApp/.build/checkouts/smoke-framework.git-5627467287507921261/Sources/SmokeOperationsHTTP1/SmokeHTTP1Server+startAsOperationServer.swift - startAsOperationServer(withHandlerSelector:andContext:defaultOperationDelegate:andPort:invocationStrategy:) -49) - Server starting on port 8080...
INFO (/Users/user/ExampleResponseApp/.build/checkouts/smoke-framework.git-5627467287507921261/Sources/SmokeOperationsHTTP1/SmokeHTTP1Server+startAsOperationServer.swift - startAsOperationServer(withHandlerSelector:andContext:defaultOperationDelegate:andPort:invocationStrategy:) -53) - Server started on port 8080...
curl で動作確認してみます。このようなレスポンスが返ってくるのではないかと思います。
$ curl -i http://localhost:8080/example -d '{"text":"hello, smoke-framework!"}'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 40
{
"text" : "hello, smoke-framework!"
}
比較的簡単に Swift で web アプリケーションを書けるので、普段は iOS を触っている方も、モックの HTTP サーバーを書くのに使ってみてはいかがでしょうか?
smoke-frmaework シリーズ
現在 GitHub の amzn のレポジトリーに smoke を冠したレポジトリーは smoke-framework を含めて 4 つほどあります。
- smoke-framework
- smoke-http
- Swift-NIO ベースの http client
- smoke-aws
- smoke-aws-credentials
- ECS で使うことが前提っぽい短時間の aws クレデンシャル
- smoke-dynamoDB
- Swift で dynamoDB を扱いやすくするためのライブラリー
これらのレポジトリーが 10 月中旬ころからできており、また smoke-aws のコミットログを見るとモデルクラスの生成を aws-sdk-go から行っているようです。割とすごい速さで充実していってるような印象を持ちました。
というわけで、 Amazon 製の smoke-framework の紹介でした。
明日は @fumiyasac さんが UIについて書いてくれるそうです。
余談話
2018/11/30 に開催された 中央線Meetup 用にデモとして、
Azure で smoke-framework アプリケーションをビルドしてみたのですが、このようなログが出てきました。
2018-11-25T12:30:56.2632921Z ==============================================================================
2018-11-25T12:30:56.2633017Z Task : Command Line
2018-11-25T12:30:56.2633076Z Description : Run a command line script using cmd.exe on Windows and bash on macOS and Linux.
2018-11-25T12:30:56.2633167Z Version : 2.142.2
2018-11-25T12:30:56.2633216Z Author : Microsoft Corporation
2018-11-25T12:30:56.2633314Z Help : [More Information](https://go.microsoft.com/fwlink/?LinkID=613735)
2018-11-25T12:30:56.2633377Z ==============================================================================
2018-11-25T12:30:57.1610232Z Generating script.
2018-11-25T12:30:57.1634662Z Script contents:
2018-11-25T12:30:57.1635377Z swift build --configuration release --product ExampleResponseApp
2018-11-25T12:30:57.1666791Z [command]/bin/bash --noprofile --norc /__w/_temp/5c9a12a6-7144-410f-b5ff-96bb137424b9.sh
2018-11-25T12:30:57.5146866Z Fetching https://github.com/apple/swift-nio.git
2018-11-25T12:30:57.5159040Z Fetching https://github.com/IBM-Swift/LoggerAPI.git
2018-11-25T12:30:57.5173837Z Fetching https://github.com/apple/swift-nio-zlib-support.git
2018-11-25T12:30:58.5142300Z Fetching https://github.com/amzn/smoke-framework.git
2018-11-25T12:31:03.6976063Z Completed resolution in 6.19s
…
おわかりいただけただろうか一つのログの中に
の巨大な4つの会社の名前が出ているのである。