Blogプログラミング・IT関連。知っていると便利なこと、気になって調査したこと、はまったけど解決できたことなど。2016の6日目の記事です。RESTfulなAPIを提供する場合、クライアントの都合にあわせて、フォーマットを変えてレスポンスを返したいケースがあります。CSVなどに変更して返すイメージです。人物情報(名前、年齢、性別など)の一覧を返す際には以下のようなデータが返されることになります。Suzuki,Male,43通常はフォーマット毎に別のURLを割り当てますが、Yesodでは1つのURLで複数フォーマットのレスポンスを返す実装が簡単にできます。このエントリでは、http://localhost:3000/に対応する一つのハンドラの中でTypedContentを利用して複数のフォーマットを扱う方法を紹介します。クライアントが送信するリクエストに記載されるAcceptヘッダによって、サーバーの挙動を変えます。siteを起動できる環境を作ります。stackが利用できる環境を前提にしています。yesod-simpleyesod-simpleは最も単純なyesodテンプレートでDB関連ライブラリへの依存がありません。develブラウザからhttp://localhost:3000/を開いてdevelサーバーにアクセスできることを確認してください。基礎1:Home.hsを修正してHTMLを返すシンプルなハンドラを作ってみるHandler/Home.hsに人物情報データ定義を追加yesod-simpleテンプレートではDBを利用しないプロジェクトが生成されます。プロジェクト内のHandlerディレクトリにHome.hsとCommon.hsが存在しているので、Home.hsをエディタで開いて、以下のコードを追加してください。サーバー上で管理するデータ定義。サーバー上のサンプルデータ。既存のgetHomeRの実装を削除し、toTableHtmlで置き換えて下さい。tableフォーマットでレスポンスを生成。toTableHtml動作確認ブラウザでhttp://localhost:3000/にアクセスし、以下のようなテーブルが表示されていればOKです。curlコマンドを実行すると実際にサーバーから返されているHTMLフォーマットデータを確認することができます。textのどちらかを返すいよいよ、クライアントからの要求に応じて異なるフォーマットを返すよう、getHomeRを改変します。ここでは"text/html"が要求されている場合にはHTMLを、"text/plain"が要求されている場合にはshow関数の実行結果を返すようにします。TypedContentに変更し、"text/html"と"text/plain"に対応するgetHomeRの実装をselectRep/provideRepを用いて以下のように変更します。samplePersonListselectRepはdoブロックの中でprovidRepによって提供される複数のフォーマットから、クライアントの要求に適合するものを選択します。RepPlainはHasContentTypeのインスタンスであり、hasContentTypeが実装されています。typeが比較され適切なContentが選択されます。Acceptableが返されます。textの2種類の結果が得られることが確認できます。parameterからAcceptヘッダを自動生成する便利機能が実装されています。この仕組みを利用することで、Acceptヘッダを入力できないブラウザ上でも(URL入力だけで)動作を確認できます。Male}]provideRepをprovideRepTypeに置き換えてみる"text/plain"をprovideRepTypeを用いて実装すると以下のようになります。samplePersonList)provideRepTypeを利用することで、HasContentTypeのインスタンスを持っていないフォーマットを返すことができます。CSVフォーマットをサポートするここまでの手順を応用して、CSV及びJSONフォーマットを返すようにgetHomeRを拡張します。CSVフォーマットのレスポンスを生成する。toJSONを自動導出。DeriveGeneric言語拡張が必要。JSONフォーマットのデータを返す方法を説明しました。ハンドラを実装する上で、selectRep/provideRep/provideRepTypeをどう使えばよいか、YesodフレームワークがContentTypeの判定にHasContentTypeを用いている、といったキモになる情報をまとめています。このエントリで利用したコードは以下のgithubリポジトリにコミットしてあるので、参考にしてください。emacs上のgrep関連機能、複数ファイルの一括置換手順のまとめemacs上でgrep関連の機能を利用する際のTIPSをまとめておきます(よく忘れて調べ直すので...)。GLOBにファイル名を指定する。GLOBに除外するファイルのファイル名を指定する(ワイルドカード指定可能)。TAB(compilation-next-error)次のマッチ箇所へ移動する。n(next-error-no-select)次のマッチ箇所へ移動する。M-}(compilation-next-file)同じファイルのマッチ箇所を飛ばして、次のファイルのマッチ箇所へ移動する。S-TAB(compilation-previous-error)手前のマッチ箇所へ移動する。p(previous-error-no-select)手前のマッチ箇所へ移動する。M-{(compilation-previous-file)同じファイルのマッチ箇所を飛ばして、手前のファイルのマッチ箇所へ移動する。?(describe-mode)ヘルプ。キーバインド一覧が表示される。C-s(wgrep-finish-edit)で編集を終了この操作を行っても編集した内容はファイルには書き込まれていない(emacs上で開かれた各ファイルのバッファに未保存の状態で変更が反映されるだけ)。cygwin64セットアップメモ自分向け備忘録。cygwin64環境をセットアップする際の手順をまとめておきます。APIを実装する手順を紹介します。Haskell上のデータ構造をJSONテキストに変換する、逆に、JSONテキストをパースしてHaskell上のデータ構造を生成する、といった処理が非常に簡単に実現できます。加えて、コードを書かなくてもバックエンドのDBとのORマッピングが可能になっており、効率的に開発することができます。ここで紹介しているコードはgithubにコミットしています。--system-ghc"--system-ghc"は省略可能。インストール済みのghcを使うことを指示しています。--no-install-ghc"--no-install-ghc"は省略可。ghcのインストールを抑制するオプションです。APIを提供するシンプルな掲示板を作ってみます。http://localhost:3000/posts記事を1件ポストする。http://localhost:3000/posts投稿済みの記事一覧を返す。http://localhost:3000/posts/sender"sender"によって投稿された記事一覧を検索して返す。バックエンドDBにはsqliteを利用し、postされた記事は以下のスキーマで生成された"post"テーブルに格納します。NULL);POSTの実装:DBのmodelの定義mode/configファイルを開き、以下の記述を追加します。"Post"の直後に記載されている"json"がキモです。この記述によりToJson/FromJson関数が自動生成されます。add-handlerコマンドを実行しHandlerを追加します。POST後で実装するGETもあわせて追加しておきます。handler/Post.hsのpostPostRを実装するhandler/Post.hsのpostPostR関数を以下のように修正します。()に変更されている点に注意してください。Text)add-handlerのバグで発生するjson-sample.cabalファイルの不備を修正します。Handler.Posts上記の通り、json-sample.cabalファイルの"exposed-modules"にHandler.Postsを手動で追記します。動作確認curlコマンドで記事をポストしてみましょう。Createdが返されていればOKです。GETで記事を取得できるかどうかは次のフェーズで確認します。シンプルなGETの実装:getPostR関数に実装を与える。Valueに変更しています。[]"により、persistentの機能を利用して、postテーブル内のすべての行を取得しています。title."}]}レスポンスボディのJSONテキストを整形すると、以下のようになっています。]}フィルタ処理を伴うGET:Filterハンドラを新たに追加add-handlerで新たにFilter.hsを生成します。GETFilter.hsにgetFilterRを実装getFilterRを以下のように変更します。先ほどのgetPostRとほぼ同じですが、selectListで検索条件としてsenderを指定している点だけが異なります。posts]persistentのクエリ機能の詳細については以前にまとめたブログエントリを参照を参照していただければ。動作確認curlコマンドでsenderを指定して、一覧を取得してみましょう。正しく動いているようです。tutorialHaskellのwebサービスフレームワークにYesodというフレームワークがあります。tutorialがあり、手順に沿っていくだけで簡単なwebサービスを動作させることができ、Yesodで何ができるかを簡単に理解できるようになっています。tutorialの記載は内容が古く、stackを利用した現行の手順とマッチしなくなっています。1.4.x)における順があると役に立つと思い、書き起こしてみました。start(はじめに)Install(インストール手順)stackをインストールする。以下のサイトが参考になります。newを利用するよう指示されます。insteadstack対応版では、以下のようにテンプレートを指定してプロジェクトを生成します。これでオリジナル版でプロジェクト名にyosogを、利用DBにsqliteを指定したのと同じ状態になっています。yesod-sqlite次にyosogプロジェクトをビルドします。オリジナルの手順は以下のようになっています。develこれに対応する、stack版での手順は以下になります。siteの画面が表示されます。siteに繋がらない現象があるので、そのときにはこちらの情報を参考に対処してください。データモデル(DBスキーマ)設定ファイルEchoさてEchoサーバーの実装です。最初の手順としてオリジナルサイトには以下のコマンド実行が記載されています。add-handlerstack対応版ではyesodコマンドを直に実行することはできません。exec"を介する必要があります。具体的には以下のコマンドを実行すればOKです。GET上記手順でEchoハンドラを登録することができます。本来ならばこの状態で何も編集を加えなくてもビルドができるはずなのですが、以下のエラーが発生してしまいました・・・。`myprozu5AgQdK8FuSe8tlY0YoDpHN_HandlerziEcho_getEchoR_closure'確認したところ、add-hanlderコマンドによってmy-project.cabalに追加された、Handler.Echoの追加場所が正しくなく、リンクエラーになっている模様。-Wall自動追加された上記の状態ではダメで、以下の場所に移動する必要がある。suite"欄に追加されてしまうのが原因です。siteを起動できる状態になります。tutorialに沿ってブラウザから以下のURLにアクセスしてみます。http://localhost:3000/echo/foo現時点ではまだハンドラの実装が空のままなので、以下のエラーが返されるのが期待値になります。Echoハンドラの実装です。エディタでHandler/Echo.hsを開いてみてください。implementedエラーはこのコードによるものです)。getEcho2R"Handler/Echo.hsを以下の実装に変えることでEchoが動作する状態になります。[whamlet|<h1>#{theText}|]試しにブラウザから以下のURLにアクセスしてみましょう!http://localhost:3000/echo/foo以下の通り"foo"がエコーバックされれば成功です。オリジナルのチュートリアルではEchoサーバーの実装後、以下の手順が案内されています。execを適用することで問題なく進められると思います。Blog一通りの手順を実装したコードをgithubの以下のサイトにコミットしています。必要に応じてこちらも参照してみてください。built」という表示が出てしまいました。このときターミナルには以下のようなログが出力されていました。error)ブラウザには「ビルドができてない!」と表示されていますが、ビルド自体は成功しています。以下のディレクトリにバイナリが生成されています。"*.exe"./.stack-work/dist/2672c1f3/build/my-project/my-project.exe./.stack-work/dist/2672c1f3/build/test/test.exePCによってはうまく動くこともあり原因を切り分けて調査したところ、HOST環境変数の有無で挙動が変わることがわかりました。ここから先は推測ですが、HOST環境変数に設定されている名前でIPアドレス解決を試みてエラーとなっているような気がします。siteを再起動すると正常にアクセスできるようになります。HOST=mac環境の現象:macでも同様の問題が発生します。mac上のログは以下のようになります。HOST環境変数を無効にすることでブラウザからアクセスできるようになりました。現状、stackで指定できるyesod関連のtemplatesには以下のものがあります。どのtemplateに何が用意されているのか、知りたかったのですがどこにも説明されていないようなので、調べてまとめてみました。(←現時点では削除されています)yesod-minimalyesod-mongoyesod-mysqlyesod-postgresyesod-postgres-fayyesod-simpleyesod-sqlite以下、各テンプレートの説明です。後に出てくるテンプレートほど内容が複雑になっています。テンプレートを指定して新しいプロジェクトを生成する場合は以下のコマンドを実行します。yesod-???yesod-hello-world最もシンプルなテンプレートapp.hs内のコードで"/"に対応するHomeハンドラだけが登録されている。configurationファイルなどは一切なし。追記:githubの情報によるとこのテンプレートは削除されたようです。)yesod-minimal次にシンプルなテンプレート。Addの2つのハンドラが登録されている。Addハンドラの実装は以下の通り。Home5+7の通常(HTML形式)リンクと、JSON形式のリンクを表示Add5+7の結果を出力(通常はHTML形式でレスポンスを返す)accept=application/jsonの場合のみJSON形式でレスポンスを返すyesod-simple各DB用のテンプレートのベースになるテンプレート。CSSの動的生成、リンク切れ検知など、DB接続と認証機能を除いて一通りの機能を確認できる。通常起動は、引数で設定ファイル(yaml)を指定可能。config/settings.yml以下のコマンドでdevel起動。POST設定ファイルstatic静的ファイル置き場。fontが配置される。templatesテンプレートファイル置き場。Haskellコードを埋め込むことができる。CSStestテストコード置き場。以下のコマンドでテスト実行。Commentハンドラが生成されている。Home.hsホーム画面定義。Common.hsFaviconR(favicon.ico)、RobotsR(robots.txt)への参照を定義。これらはFoundation.hsから参照される。Comment.hsDB接続がないため単にエラーを表示するだけ。ImportImport宣言まとめ。Settingsstaticディレクトリ内のファイル参照を記載しておき、コンパイル時にリンク切れのチェックを行う。yesod-mongo/mysql/postgres/sqlite各DB用のconnectionコードと認証機能が追加されたテンプレート。SQLiteと接続するためのコードが自動生成されます。yesod-simpleとの違いは下記の通りconfigroute下記の通り"/auth"が追加されています。test-setting.ymlDB接続のためのパラメタ追加。HandlerComment.hsクライアントから送信されたコメントをDBに格納・参照する処理が定義されている。認証済みの場合はユーザー情報も合わせて格納。yesod-postgres-fayPostgreSQL+Fayを利用するためのテンプレート環境。調査が追いついていないのですが、FayではHaskellの仕様のサブセットがサポートされていて、Haskellのコードをjavascriptにコンパイルしてくれるとこのこと。Platformにはmingwが同梱されておりgccが含まれています。2014.2.0.0をインストールしているのですが、これに付属されているgccでC++11のコードをコンパイルしようとすると、以下のようなエラーになってしまいました。'-std=c++11'-stdオプションで"c++11"を指定しても、認識してくれません。4.7でサポートされたようです。2014.2.0.0に付属されているgccのバージョンを確認したところ、4.6であることがわかりました。PURPOSE.gccは4.6で、'-std=c++11'オプションは認識できないバージョンであるため、冒頭のエラーとなってしまいます。gcc4.6でもC++11(相当)コードをコンパイルする方法はあります。4.3~4.6では'-std=c++0x'と指定すれば。以下のように実行すればコンパイルすることができます。SQLiteのロック・トランザクション関連仕様の整理SQLiteは共有ロック・排他ロックの仕組みを備えており、プロセス内の複数スレッド、だけでなく、プロセスをまたぐ状態でSQLが同時に発行されても適切に処理されます。トランザクションに複数のモードが存在し、指定するモードに依って取得されるロックが変わります。SQLの内容に依存してロック状態が遷移するため、複数プロセスがアクセスしたときの挙動を正確に把握できるよう、仕様を整理してみました。(文中の「プロセス」は、正確には「プロセス、もしくはスレッド」を意味します。)DBのロック状態の種類:UNLOCKEDロックされていない状態。誰も読み書きしていない。SHAREDread可、write不可な状態。複数プロセスが同時にSHAREDロックを取得可能。複数プロセスが同時にreadできることを意味する。DBがこの状態にあるとき、他のプロセスからのwriteはもちろん不可。RESERVEDRESERVEDロックは、プロセスが将来writeを行う予定であるが、現時点ではreadだけを行っている状態である。SHAREDロックは複数プロセスが同時に取得することができるが、RESERVEDロックを取得できるのは1プロセスに限定される。PENDINGロックとの違いは、新たにSHAREDロックを取得できる点(RESERVEDロック中であれば、別プロセスが新たにSHAREDロックを取得できるが、PENDINGロック中は新たにSHAREDロックを取得することはできない)。SQL実行におけるwrite処理がキャッシュ上で完結する場合は、このRESERVEDロック状態で実行される。PENDINGPENDINGロックは、既存のSHAREDロックの解放を待ち、EXCLUSIVEロックを取得してwriteを実行しようとしている状態である。PENDINGロック状態では新たなSHAREDロックの取得は許可されない。既存のSHAREDロックはそれが解放されるまでは有効。EXCLUSIVEEXCLUSIVEロックはDBファイルへのwrite実行時に必要なロックである。あるDBファイルの中では、ただ一つのEXCLUSIVEロックだけが有効になることができる(このとき、その他の全てのロックは解放された状態になる)。SQLiteは並列処理性能を最大化する目的で、EXCLUSIVEロックの取得時間を最小にするよう動作する。3トランザクションの開始:明示的にトランザクションを開始する、しないに関わらず、DBの更新ではかならずトランザクションが実行されます。TRANSACTIONを明示的に実行した場合には、COMMITもしくはROLLBACKを実行するまでトランザクション状態が継続されるトランザクションのモード:DEFERREDデフォルトはこのモード。トランザクションを開始した後でも、実際にDBに対するアクセスが発生するまではどのロックも取得しない。最初のread処理でSHAREDロックを取得し、最初のwrite処理でRESERVEDロックを取得する。TRANSACTIONが成功してしまう点には注意が必要)。TRANSACTION実行時にRESERVEDロックを取得する。connectionも、DBへのwrite、IMMEDIATE/EXCLUSIVEトランザクションの開始はできなくなる。TRANSACTION実行時にEXCLUSIVEロックを取得する。connectionでreadが禁止される。connectionにおいてwriteが禁止される。各トランザクション開始時にどのロックが取得されて、そのロック状態がどう遷移するのかがわかりにくかったため、絵にまとめました。以上、SQLiteの動作理解の助けになれば幸いです。