Suave で REST API を作る


この記事は F# Advent Calendar 2015 の 7 日目です。

Suave は F# のウェブアプリケーションフレームワークです。 .NET Framework や Mono の動作する環境上で動きます。

最近 Suave を使って API を作る機会があったので,その方法について簡単に紹介します。

Suave とその他パッケージのインストール

Suave は NuGet からインストール可能です。以下のパッケージを導入します。

  • Suave

Suave には単体テスト用のパッケージも公式で配布されています。今回はテスティングフレームワークとして Persimmon を例に挙げます。

  • Suave.Testing
  • Persimmon

REST API を作る

REST API では "/path/{id}/{property}" のような形式の URL に対して特定の HTTP メソッドでリクエストを送ります。つまりサーバー側では以下のことができなければなりません。

  1. パスからパラメーターを取得
  2. HTTP メソッドのフィルタリング

パスからパラメーターを取得

パスからパラメーターを取得するには, pathScan 関数を利用します。ちなみにパラメーターがない場合は path 関数を利用します。

pathScan 関数は, printf 関数と類似した形式で,型安全にパスからパラメーターを取得することができます。パラメーターを利用した処理は,取得したパラメーターを引数として WebPart を返すような関数として定義します。具体的には以下のような形式になります。

pathScan "/path/%d/%s" (fun (id, property) ->
  // WebPart を返す
)

何の説明もなく WebPart という言葉が出てきました。 Suave では WebPart という型を >>= というパイプで接続して処理を連結します。

webPart_1 >>= webPart_2 >>= ... >>= webPart_n

詳しい説明は割愛しますが, WebPart の型は HttpContext -> Async<HttpContext option> だと言えば,わかる人もいると思います。ざっくり言えば,定義した条件マッチした場合に非同期処理を行い,そうでなければ失敗するような関数です。

HTTP メソッドのフィルタリング

Suave には HTTP メソッドと同名の WebPart があらかじめ定義されています。 GET メソッドに対して GET 関数, POST メソッドに対して POST 関数といった感じです。

エンドポイントの作成

上記を組み合わせて,以下のようにエンドポイントを定義できます。

open Suave.Http.Applicatives
open Suave.Http.Writers

let endpoint =
   GET
      >>= setMimeType "application/json"
      >>= request (fun req -> pathScan "/path/%d/%s" (fun (id, property) ->
         // 実際の処理
      ))

API の作成

実際の API ではエンドポイントが複数あるので,それを列挙する必要があります。それには choose 関数を利用します。

open Suave.Http

let api =
   choose [
      endpoint
      endpoint_2
      // 中略
      endpoint_n
   ]

API サーバーを起動する

以下のようにして API サーバーを起動します。

open System.Net
open Suave.Types
open Suave.Web

let config = {
   defaultConfig with
      bindings = [HttpBinding.mk HTTP IPAddress.Any 80us]
}

startWebServer config api

実際には 2 つ目の引数の設定を編集して,ロギングやエラーハンドリングといった処理を指定してやる必要があるでしょう。

単体テストを作成する

サーバーを起動する startWebServer のシグネチャは SuaveConfig -> WebPart -> unit です。 unit の代わりに何かテストの結果を返してくれるような関数があれば,テストができるのではないかと考えられます。実際には runWith という,まさに SuaveConfig -> WebPart -> SuaveTestCtx という,テスト結果を返す関数が定義されています。

SuaveTestCtx は名前の通りコンテキストなので,そこからレスポンスを取り出すような関数が必要になります。 reqResp 関数を利用することで,リクエストを投げてレスポンスを取得するという一連の流れをテストすることができます。実際には reqResp のパラメーターをあらかじめいくつか設定してレスポンス文字列を返す req 関数を使う場合が多いかもしれません。

以下のような形で, Suave のテストを行うことが可能です。

open Suave.Testing
open Suave.Types
open Persimmon

/// GET リクエストのレスポンス文字列を取得する。
let get path = runWith config api |> req HttpMethod.GET path None

let ``/path/1/foo にリクエストを投げて JSON レスポンスを得る`` = test {
   let content = get "/path/1/foo"
   do! assertEquals content """{"propertyName":"foo","propertyValue":"bar"}"""
}

まとめ

Suave を利用すると簡単に REST API が作れるというお話でした。

ちなみにこの Suave を手本にして R で API を作れるようにしたのが shadowy というライブラリーです。まだ機能的に足りていないところが多いので,誰か手伝ってください。