net/httpについて
RubyのプログラムでWebAPIを叩く時などに、標準で含まれているnet/httpライブラリを使うことがあると思います。もしくは利用しているHTTPのライブラリが内部でnet/httpを使っていることも多いでしょう。
net/httpにおいてHTTPリクエストにヘッダフィールドを追加する場合、[]=
もしくはadd_field
を使います。
req = Net::HTTP::Get.new('/some_path')
req['X-My-Header'] = 'some_value'
なお、一般的によく使われるフィールドにはcontent_type=
のように専用のメソッドが用意されています。HTTPヘッダに関するメソッドはNet::HTTPHeaderモジュールで定義されています。
大文字小文字の扱い
[]=
のリファレンスでは、「key(ヘッダフィールド名)は大文字小文字を区別しません。」と書かれています。
実際にこのメソッドのソースコードを読んでみましょう。
def []=(key, val)
unless val
@header.delete key.downcase
return val
end
@header[key.downcase] = [val]
end
ヘッダフィールド名は、内部的にはdowncase
メソッドを使って全て小文字に直されています。これはinitialize
やadd_field
についても同様です。
では送信時にパケットに含まれる文字列も全て小文字なのか?というと、そうではありません。送信時に、each_capitalizedなる処理が挟まれます。具体的にフィールド名にどのような文字列操作が掛かるのか、というのはここに記載されています。
name.to_s.split(/-/).map {|s| s.capitalize }.join('-')
これはヘッダフィールド名の記載方法として慣用化されている、User-Agent
やIf-Modified-Since
のような形式に直すということです。
HTTPのRFCでは
現在有効なHTTP/1.1のRFCはRFC7230~7235です。その中でヘッダフィールド名に関しては、case-insensitiveであると記載されています。これを遵守しているサーバであれば、大文字小文字はどのように記載していても区別なく解釈してくれるはずです。
上記のnet/httpの実装はRFCが遵守されていることを前提に、内部的な表記を統一することでプログラマが大文字小文字を意識しなくてすむようにしているのだと思います。
送信時に一手間加えているのは、単に慣用に倣っただけなのか、それ以上の強い理由が何かあるのか、詳しくはわかりません。ただ調べた限りでは、主要なWebブラウザはだいたいこの慣用に従っているようなので、とりあえず合わせておけば安心と言えるかもしれません。
case-sensitiveなサーバの場合
ところで、なぜこんなことを調べたかというと、RFCの記載に則らず大文字小文字を区別するAPIを叩く機会があったからです。そのAPIは以下のようなヘッダフィールド名(仮)を要求していました。
X-ABC-Token: token_value
これをnet/httpで送信しようとしても、セット時の表記にかかわらず内部のキー名はx-abc-token
となり、送信時はX-Abc-Token
として送信されます。残念ながらそのAPIを提供するサーバは、このフィールドを解釈してくれませんでした。以下はWiresharkで表示したパケットの中身です。
このようなサーバを相手にする際は、素直にnet/httpではなく他のHTTPライブラリを使いましょう。ただし、net/httpに依存していて、ここの処理が丸々使われているライブラリだと結局解決されないので注意です。各種HTTPライブラリの依存関係については、この図がわかりやすいです。
net/httpに依存していないHTTPClientは、この問題を解消することができました。以下はHTTPClientを用いた場合のパケットです。
元はといえばサーバ側のAPI実装がイマイチなことに起因しているので、同じような問題に当たる人がいるのかはわかりませんが…色々調べて勉強にもなったので、書き残しておきます。