RFC 5988 "Web Linking" を読んだ

JSON を返す API サーバでページネーションを実装したいと思っていて、前/後ページや最初/最後のページなどといった他のリソースとの関係を表すメタ情報をどこに格納すべきかなと考えていました。解決方法として少なくとも次の二つがありそうです。

  • JSON の中(レスポンスのボディ)にメタ情報も入れる
  • レスポンスヘッダにメタ情報を入れる

レスポンスヘッダに当該メタ情報を入れる後者の方法について提案している文書としては、RFC 5988 “Web Linking” があります。この方法は大きなサイズのコレクションをページネーションで返す API のレスポンスを設計するときにも使えるという話を見つけたので、メタ情報はボディよりヘッダに入れたほうが場所としては適切だろうと思い、ページネーションを実装する前に、この RFC を読んでみました。

RFC 5988 の要点をハイパーざっくりまとめておきます。


イントロ

  • なにをやるのか
    • 特定の形式や応用例に縛られない型付きリンクの表現を定義する
  • なぜやるのか
    • リソース間の関係とその型を示す方法は HTML と Atom で別々に定義しているけど一般化できる
    • HTTP ヘッダでリンクを定義する方法が RFC 2068 で定義されてたけど RFC 2616 で削除されてしまった
    • 放っておくと各アプリケーション特有のやり方が生まれちゃってつらそうなので、この RFC ではそれを解決するよ

リンクとは

  • リンクは「IRI*1 で識別されるリソース間の型付き接続」として定義する
    • 基本的に IRI は URI と読み替えて OK
  • リンクは次の要素からなる
    • 一つのコンテキスト IRI
    • 一つのリンク関連型 (link relation type)
    • 一つのターゲット IRI
    • 任意個数のターゲット属性
  • <コンテキスト IRI> は <リンク関連型> のリソースを <ターゲット IRI> で持ち、そのリソースは <ターゲット属性> を持つ という文章として見ることができる
  • どんな型のリンクもどの IRI から何本出ていてもいいし、順番も気にしない
  • ターゲット属性は key-value ペア

リンク関連型

  • リンク関連型はリンクの意味を決める
    • copyright という関連型のリンクは、ターゲット IRI が指すリソースはコンテキスト IRI へ適用する著作権規定の文章であることを示している
  • リンク関連型はターゲットリソースが特定の属性を持つことも示す
    • service というリンクは、「サービスの説明」のようにリソースが何か定義されたプロトコルの一部であることを示している
  • メディアタイプではないので、リンクの参照先の表現形式は問わない
  • 他のリンク関連型の存在や出現数に依存して意味を付け加わるような関連型は望ましくない
    • alternatestylesheet は歴史的事情により例外

関連型の種類

  • 登録関連型 (Registered Relation Types)
    • IANA レジストリに登録済みの再利用できる関連型
    • 名前は reg-rel-type rule にしたがう
      • reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) と定義されている
      • つまり ASCII 小文字 "a".."z" 1 文字の後に任意個数の ASCII 小文字、数字、".", "-" が続く形式
  • 拡張関連型 (Extension Relation Types)
    • 関連型を一意に識別できる URI を使うこともできる
    • URIは関連型を定義しているものを指し示す必要があるけど、サーバに負荷がかかるのでクライアントは勝手にそのリソースへアクセスすべきではない

レジストリに最初に登録された関連は該当 RFC 6.2.2 節に載っている。

現在のレジストリ登録済み関連は IANA のページに載っている。結構増えてますね。

Link ヘッダフィールド

  • エンティティのヘッダ上で 1 本以上のリンクを表現する
  • HTML の <LINK> 要素とか Atom の atom:link 要素と等価
  • ターゲット IRI
    • ブラケットで囲む
    • 相対パスならパーサはパス解決する必要がある
  • コンテキスト IRI
    • デフォルトではリクエストされているリソース自体がコンテキスト IRI が指すリソース
    • 404 で該当リソースが存在しないときは anonymous になる
  • 関連型
    • rel パラメータで関連型の値を指定する
    • revrel の逆向きになっている関連であり、歴史的事情で存在しているが、もう使わない
  • ターゲット属性
    • hreflang は参照先の言語のヒント
    • media は参照先のメディアスタイル(画面表示、読み上げ、etc.)のヒント
    • title, title* は参照先リソースのタイトルで、title* は別の文字セットを使うことができる
    • type は参照先のメディアタイプのヒント

例を示す。

“chapter2” が現在のリソースの前に位置していることを示すリンク。

Link: <http://example.com/TheBook/chapter2>; rel="previous"; title="previous chapter"

ルートリソース (“/”) が拡張関連型 http://example.net/foo でこのリソースと関連していることを示すリンク。

Link: </>; rel="http://example.net/foo"

title* 属性でドイツ語で「前の章」、「次の章」というタイトルを指定しているリンク。

Link: </TheBook/chapter2>; rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

複数のリンクも持てる。

Link: <http://example.org/>; rel="start http://example.net/relation/other"

結局、ページネーションでは、Link Relations あたりに載っているリンク関連型から、first, last, next, previous を使ってリンク先を示すようなメタ情報をヘッダに入れておけば十分そうです。

参考資料

MySQL 徹底入門 第 3 版を読んだ

『MySQL 徹底入門 第 3 版』を読みました。

MySQL徹底入門 第3版 ~5.5新機能対応~

MySQL徹底入門 第3版 ~5.5新機能対応~

  • 作者: 遠藤俊裕,坂井恵,館山聖司,鶴長鎮一,とみたまさひろ,班石悦夫,松信嘉範
  • 出版社/メーカー: 翔泳社
  • 発売日: 2011/08/26
  • メディア: 大型本
  • 購入: 9人 クリック: 82回
  • この商品を含むブログ (9件) を見る

なぜ読んだか

  • 特定の DB プロダクトについての知識がなかった
  • Web サービス開発の仕事に転職して、開発のために MySQL の操作をするようになったけど、知識が少ないので時間がかかる

こういうとき、幅広く薄く体系的にとっかかりとなる知識があると、頭のなかにインデックスができて調べやすくなるかなと考えました。入門系の本を何冊か Amazon で見て、よさそうなのを選んだ記憶があります。2011 年初版で「5.5 対応」と書いてあり、それほど新しい本ではないけれど、基本的な機能の学習には影響がそれほどないだろうと判断しました。

Vagrant で CentOS 7 の VM を立ち上げて MySQL をインストールし、それを触りながら読んでました。

感想

3 章でクライアント ( mysql ) を通じた操作の基本がだいたいわかり、4 章で周辺ツール( mysqladmin, mysqldump など)やユーザ管理、サーバ設定、ログなどについての基本がだいたいわかりました。このへんは求めていたことだったのでよかったです。

ストレージエンジンの説明やコマンドのオプションも載っていますが、わりとさらっとしてるので、このあたりは素直にマニュアルを読もうと思いました。

おわりに

MySQL の基本的な使いかたを体系的に把握することはできた本でした。あとは公式のリファレンスマニュアルを参照する生活を過ごしたいと思います。

Web 上のリソースとその表現

RSpec で request spec を書くとき、get "/users/:id.json" と書くかわりに get "/users/:id" と書くとエラーになりました。

ActionController::UnknownFormat:
  UsersController#show is missing a template for this request format and variant.

リソース /users/:id に対して拡張子で指定しないならば、HTTP リクエストのヘッダに Accept: application/json をつけて、クライアントが利用したいデータ形式を指示する必要があります。

そもそもなぜこういうふうになっているのかを整理しました。

リソース

リソースとは Web 上に存在する情報そのものであり、URI (Uniform Resource Indicator) で指し示せます。たとえば次のようなリソースが考えられます。

  • 人のプロフィール
    • URI 例:http://example.com/users/john
  • ブログ記事
    • URI 例:http://example.com/entries/2017/01/01

わかりやすい URI でリソースを指定することで、どのような情報なのかが人間にとってわかりやすくなります。なお、ここでは、その情報がどのような見た目(表現)になっているかについては言及していません。

また、リソースに対して、HTTP のメソッド (GET, POST, PUT, PATCH, DELETE) を使うことで CRUD をはじめとする各種操作が実行できます。

リソースの「表現」

「表現」という言葉はリソースがどのような形式のデータになって、サーバ/クライアント間で通信されるかを指しています。表現には次のようなものがあります。

  • メディアタイプ
    • HTML, XML, JSON, JPEG, PDF など
  • 言語
    • 日本語、英語、中国語など
  • 文字エンコーディング
    • UTF-8, Shift_JIS など

リソースは複数の「表現」をとれる

あるリソースは次のように複数の「表現」をとりえます。

  • 人のプロフィールが HTML, JSON, PDF で表現できる
  • ブログ記事が日本語、英語で表現できる

リソース表現の指定方法

HTTP を介したサーバとのやりとりにおいて、リソースの表現をどう指定するかについては次のような方法があります。

  • クエリ文字列
    • GET http://example.com/users/john?format=json で人のプロフィールの JSON 表現を得られる
  • 拡張子
    • GET http://example.com/entries/2017/01/01.en でブログ記事の英語表現を得られる
  • コンテントネゴシエーション
    • GET http://example.com/users/john のリクエストヘッダに Accept: application/pdf を利用するとプロフィールの PDF 表現を得られる
    • ほかには Accept-LanguageAccept-Charset など

リソースと表現を分離する利点

リソースと表現の関係を疎にして、リソースが複数の表現をとれるようにすると、さまざまなクライアントが求める形式のデータを統一された HTTP のインタフェースで提供できるようになります。また、拡張性の面でも利点があります。

参考資料

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

Rails 5.0.1 で rails new するとできる Gemfile の git_source ブロックの意味

Rails 5.0.1 で rails new したときに作成される Gemfile の先頭に次のブロックが挿入されるようになっていました。

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

これの意味

Gemfile の中で :github オプションをつけた gem について、HTTPS 経由で GitHub から取得します。

git_sourceBundler で定義されているメソッド で、引数のオプション(ここでは :github)が付いた gem の取得先 URL をブロック内で指定できます。

やる理由

Bundler が持つ :github オプションは Git プロトコルで通信するのでセキュアでなく、さらに Bundler 1.13 以降は :github オプションを使っていると警告が出ます。公式ドキュメントでも :github オプションを使うのを避けるよう明記してあります。

このコードを入れておくと、HTTPS 経由で GitHub から gem を取得できるので警告が出ずにすみます。ちなみにデフォルトの Gemfile では web-console:github オプションを使っていますね。Bundler 2.0 ではこの点が対策されるようで、それまでの対処ということです。

入ったプルリクはこちらです。

github.com

参考資料

Yokohama.rb Monthly Meetup #76 に参加した

2017-01-14(土)の Yokohama.rb Monthly Meetup #76 に参加したので、メモを書きます。1 週間たってしまった。

yokohamarb.doorkeeper.jp

Ruby レシピブック

今回はレシピ 244 から 247 まで読みました。システム寄りの話題が中心です。

レシピ 244

スレッドファイバの扱いかたについてです。

スレッドを使うときに気をつけるべき点は次のようなところでしょう。

  • あるスレッドが別のスレッドの変数を操作しないように、Thread#fork の引数としてスレッド固有のローカル変数を渡す
  • メインスレッドが勝手に終了するのを防ぐため Thread#join でスレッドが終了するのを待つ

ファイバは軽量スレッドのようなものです。スレッドは自動で他スレッドに制御が移りますが、ファイバが持つコードを実行するには Fiber#resume を実行し、制御を呼び出し元に戻すには Fiber#yield を実行する必要があります。Enumerable#each など内部イテレータをジェネレータのような外部イテレータに変換するコードとともに紹介されていました。

ちなみに、Ruby 1.9 以降では組み込みの Enumerator をジェネレータとして使うことができます。

sentence =<<EOF
lorem ipsum
hoge fuga fizz bazz
EOF
enumerator = sentence.each_line  # Enumerator を返す
loop { p enumerator.next }
# => "lorem ipsum\n"
# => "hoge fuga fizz bazz\n"

また、レシピのサンプルコードで Object#__send__ というメソッドが使われていました。これは、send という名前が一般的で再定義されやすいことから定義された Object#send の別名ということです。

レシピ 245

プロセスのフォークについてです。

レシピ内では fork して exec するサンプルコードが紹介されていました。この用途なら Kernel.#spawn でもよいという話がありました。子プロセスの終了を待つなら Process.waitpid2 を使う必要があります。

pid = spawn('job')
exitpid, status = *Process.waitpid2(pid)  # 'job' が終了するまで待つ

レシピ 246

プロセスがシグナルを受けたときの動作を設定するシグナルハンドラについてです。

有名な SIGINT などの他に SIGUSR1, SIGUSR2 というはシグナルがあって、これはユーザ定義用のものだそうです(参照:シグナル (ソフトウェア) - Wikipedia)。

Pry など REPL 上で Singal.trap(:USR1) { puts 'USR1' } などとして、killSIGUSR1 を送ると、シグナルハンドラが動作していることが確かめられます。

レシピ 247

プロセスをデーモン化する方法についてです。

注意点として、デーモンの場合、必ず Kernel.#exit! で終了するようにします。exit!ensure 節など後処理も飛ばしてプロセスを終了します。これを使わないと stdio ライブラリのバッファが 2 回フラッシュされてしまうなどの問題が起きるそうです。

そもそもなぜデーモンなのか? nohup ではだめなのか?という話に展開して、次のページを皆で見ていました。

もくもく

後半はもくもく会でした。僕は swagger-docsSwagger UI を使って、Rails で作った API のドキュメント化を試していました。

懇親会

miyohide さん誕生日おめでとうございました。


次回は 2017-02-18(土)です。次のリンクから申し込めます。

yokohamarb.doorkeeper.jp