テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

spray: Content-Typeを強制するにはrespondWithMediaTypeを使う

http://spray.io/documentation/1.2.2/spray-can/http-server/#http-headers

spray-canはいくつかの基本的なヘッダについて自動的に管理をしてくれる. これは, ほとんどの場合はうまく働くが, S3のようにややトリッキー(or クレイジー)なRESTサーバを作る場合は問題となってくる. 例えば

  • HEAD Objectはデータを返さない. この時, Content-Lengthは0となってしまうが, クライアントの期待は実際のContent-Length(>=0)である.
  • completeする場合, MarshallerがMediaTypeを自動判別するわけであるが, Array[Byte]Stringなど汎用的なデータ構造で返す場合, それがテキストであったかimageであったかという情報は失われる. getFromFile (http://spray.io/documentation/1.2.2/spray-routing/file-and-resource-directives/getFromFile/)を使うと, ファイルのMediaTypeを自動判別してくれると思うが, 使い勝手がやや悪い. 理由は2つ. 1つは, Marshallerとして切り出されていないこと. もう1つは以下にあるように, (ファンキン無能なことに)拡張子から判定しやがること. S3のbackendに拡張子の情報を入れるわけにはいかない. そもそも, クライアントから渡されてくるのがバイト列なのだから, バイト列で返すという発想が自然である.

The getFrom... directives automatically set the content type of the HTTP response to the media type matching the file extension. For example ".html" files will be served with Content-Type: text/html while ".txt" files will receive a Content-Type: text/plain. You can also very easily supply your own media-types and/or file extensions. See the Custom Media Types chapter for further documentation on this.

というわけで結論として, バイト列で返して強引にContent-Typeをつけてあげる作戦となる.

completeの第二引数は, Seq[HttpHeader]を受けるが, ここにContent-Typeを格納してもoverrideされないことが分かった. respondWithMediaTypeを使うとoverrideされるためこちらを使う.

respondWithMediaTypeはMediaTypeを引数にとるが, これは文字列ではないので, "text/plain"などと入れることは出来ない.

文字列からMediaTypeを取得するには, MediaType.custom(String)を使う. ファイルコンテンツから文字列としてのMediaTypeを推論する方法は色々あると思うが, 私はApache Tikaを使った. 推論に期待するのは危険だが, fallbackする先としては妥当とみる. (octet-streamになるよりはマシであろうし, クレイジーなファイルを突っ込むのであれば, Content-Typeを指定するのはクライアント側の義務であると考える. 例えばresponse-content-typeを使う)

私が現在作っているのは, (ギョム上の目的は違うのだが)共有・分散ファイルシステムbackedな完全S3ラッパーである. あらゆるファイルシステム(完全なPOSIXは期待していない)の上にtrait的にS3の機能を追加する世界を目指している. 本当はもっと面白い狙いも含んでいるが, それはいえねえ. S3のプロトコルは非常にややこしいので, 汎用的なファイルシステムの上で完全に動作するS3ソフトウェアは, きっと価値があると信じている.