Functional HTTP Programming

はじめに

この記事は F# Advent Calendar 2012の13日目です。

今回は、仕事でも十分に”実用”できるFunctional HTTP Programmingについて書いていきます。

一般的な.NET HTTP プログラミング

.NETでHTTPプログラミング(サーバーサイド)といえば、一般的に下記のようなものがありますね。

これらでF#を使って関数型の恩恵を受けようとしても下記のようなさまざまな障害があります。

  • 肥大化して複雑な設計のHttpRequest, HttpResponse
  • パターンマッチの使えないNameValueCollection
  • コンパイル時検証がまったくないUriTemplate

Lemonとは

Lemonとは、ASP.NET上で関数型プログラミングを簡単に行えるライブラリです。 今回は主にLemonを使用し、簡単なHTTPプログラミングをしていきます。 このライブラリはgithubにてソースコードが公開されています。

https://github.com/otf/lemon

Lemonの使い方

最初のコード

まずは、Lemonでhello worldを書いてみましょう。

準備のために下記の手順でプロジェクトを作成します。(Visual Studio 2010の場合)

  1. "新規作成"で"新しいプロジェクト" > "Visual C#" > "ASP.NET空のWebアプリケーション"(プロジェクト名はHelloLemon)
  2. "追加"で"新しいプロジェクト" > "Visual F#" > "Windows" > "F# ライブラリ" (プロジェクト名はHelloLemon.Code、.NETは4.0)
  3. HelloLemonからHelloLemon.Codeをプロジェクト参照する。
  4. HelloLemon.CodeのLibrary1.fsを削除。
  5. HelloLemon.Codeにnugetでlemonを検索し、インストール。
  6. HelloLemon.Codeに下記の二つのファイルを追加

  7. Server.fs

module Server

open Lemon

let (server:Server) = function
  | GET(Url []) -> 
      ok >> response "hello world"
  | _ -> notFound
  • Handler.fs
namespace HelloLemon

type Handler () =
  inherit Lemon.HttpHandler(Server.server)
  1. HelloLemonのWeb.configを下記のように書き換える
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.web>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />
      <httpHandlers>
        <add verb="*" path="*" type="HelloLemon.Handler"/>
      </httpHandlers>
    </system.web>
</configuration>

これで準備完了です。 実行してみるとブラウザ画面にhello worldと表示されるはずです。

リクエストのパターンマッチ

Server.fsで定義したようなServer型の関数はHTTPメソッド、URL、HTTPヘッダー、クエリパラメータなどでパターンマッチをすることができます。

HTTPメソッドとURLによるパターンマッチ

HTTPメソッドとURLは同時にパターンマッチすると便利です。 アクティブパターンとしてGET, POST, PUT, DELETEが定義されていて、 一つ目の引数はURLのパターン、POSTとPUTの場合は二つ目の引数にボディのストリームが渡されます。 これらを使用したコード例を次に示します。

// GET /hoge/piyo/foo.txtのリクエストで文字列を返します。
| GET(Url["hoge" ; "piyo"; "foo.txt"]) -> ok >> response "GET /hoge/piyo/foo.tx" 

// POST /echo のリクエストで受け取った文字列ボディをそのままエコーします。
| POST(Url["echo"], body) -> ok >> (readText body |> response)

// PUT /put のリクエストでボディを受け取って201 Createdを返します。
| PUT(Url["put"], body) -> created

// DELETE /delete のリクエストで204 NoContetを返します。
| DELETE(Url["delete"]) -> noContent

HTTPヘッダーとクエリパラメータのパターンマッチ

あとで書く

レスポンス生成

レスポンスは、型がResponserである型を一つ以上合成することで生成されます。 ステータスコード関数、ボディ関数、ヘッダー関数などを組み合わせることができます。 これらを使用したコード例を次に示します。

// ステータスコード: 200 Ok
// ボディ: 空
ok

// ステータスコード: 200 Ok
// ボディ: "Hello World"
ok >> response "Hello World"

// ステータスコード: 404 Not Found 
// ボディ: "NotFound"
// ヘッダー: Hoge : aaa
notFound >> response "NotFound" >> setHeader "Hoge" "aaa"

ボディ関数はテキストを送信するresponse関数以外にも、XMLを送信するxmlResponseがあります。

エラー処理

あとでかく

機能拡張

あとでかく

注意点

lemonを使う上で注意すべき点が二つあります。 まず第一にHTTPヘッダーの扱いを簡単にするためにNameValueCollectionを単なるASOCリストにしています。 NameValueCollectioは実は同じキーを複数持てるのでこれは正しくありません。 ちなみに、ASP.NET MVCの新しいAPIではNameValueCollectionではなくIDictionary<string, IEnumerable>になっています。

もう一つの弱点は、ボディのバッファレス読み込みに対応してないことです。 そのため、ボディの読み込みは一時的にオンメモリにバッファしてしまいます。 これは.NET 4.5でサポートされた機能ですが、lemonは.NET 4.0で作られているためです。

http://msdn.microsoft.com/ja-jp/library/hh160863.aspx

さいごに

レガシーなAPIにFunctionalな皮を被せて扱うことは非常に楽しいです。 僕は仕事でHTTPのコードをガリガリ書くことが多いので、もはやLemonは仕事必需品です! みなさんも関数型で仕事をバリバリ効率化しましょう!

F# Advent Calendar 2012 14日目は、僕の右斜め後ろの席の @htid46 さんです。 よろしくオナシャス! それではまた。