HTTPヘッダーについて
見えないから普段はないがしろ、トラブル時にはわからない、そんなHTTPヘッダーの理解を深めるためにMDNを読み解きました。
HTTPヘッダーとは
MDN:「HTTPヘッダー」によれば、HTTPヘッダーとはHTTPリクエストやレスポンスでクライアントとサーバーが追加の情報を受渡しするためのものだということです。
これは、大文字小文字を区別しないヘッダー名とそれに続く:(コロン)と、値によって構成されます。値の前のホワイトスペースは無視されます。
よくX-から始まるヘッダを見ますが、これは独自ヘッダを意味するものですが2012年に使用が非推奨となったようです。
標準ヘッダや標準化前の仮状態(Provisional)のヘッダの種類はIANAレジストリで公開されています。
仮状態のヘッダの中にはX-PGP-Sig(PGP署名)など、独自ヘッダを示すX-から始まるものもいくつか含まれています。
実際のヘッダーを確認
実際のヘッダーを確認してみます。
Google Chromeでは開発者ツールのNetworkタブ内のHeaderで表示することができます。発生する通信毎にヘッダーは存在しますが、トップページのヘッダーはおそらくリストの最初にあると思います。
telnetでWebサーバにアクセスすることで手動で確認することもできます。ただ、Windowsのtelnetだと入力がコンソールに表示されなかったり扱いづらいので、もしやるならWSLのLinux経由で使うといいと思います。
下の例では下線のある部分が入力項目、あとはレスポンスです。
telnet www.google.com 80 Trying 142.250.207.100... Connected to www.google.com. Escape character is '^]'. GET / HTTP/1.1 Host: www.google.com (エンター2回押下) HTTP/1.1 200 OK Date: Sat, 06 Nov 2021 02:58:13 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info." Server: gws X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN Set-Cookie: 1P_JAR=2021-11-06-02; expires=Mon, 06-Dec-2021 02:58:13 GMT; path=/; domain=.google.com; Secure Accept-Ranges: none Vary: Accept-Encoding Transfer-Encoding: chunked ....
HTTPヘッダーの分類
HTTPヘッダはその利用環境(コンテキスト)によって分類されます。例えばMDNではエンティティヘッダーとされている「Content-Length」がリクエストヘッダ扱いにされたり、認識の相違によって違うこともあり、種類を意識する必要はないかもしれません。
- リクエストヘッダー
リクエストヘッダには表示内容についての情報や、それを要求するクライアントに関する詳細な情報を付与します。
OSやブラウザの情報を持つ「User-Agent」や「Cookie」、「Referer」などがこれにあたります。
他には「Accept」等の条件付きリクエストが使わることがあります。
- レスポンスヘッダー
リクエストに対するレスポンスにまつわる追加情報です。「Keep-Alive」や「Access-Control-Allow-Origin」、「Date」などがこれにあたります。
先にも書きましたが「Content-Type」や「Content-Encoding」は正確にはエンティティヘッダーですが、レスポンスに表れるヘッダーということでレスポンスヘッダーと解釈されることもあります。
- リプレゼンテーションヘッダー
表現ヘッダという和訳の通り、ひとつのデータの表現形式の違いを示します。具体例としては、XMLとJSONの違いだったり、圧縮やエンコードの有無による違いなどがあります。
このヘッダーもリクエストとレスポンスの両方に出現します。要求時は表示形式の要求になりますが、レスポンス時は採用された表示形式となります。
- ペイロードヘッダー
転送されるデータの表現から独立した情報で、データの安全な転送や再構成に関する情報を持ちます。
HTTPヘッダーの使用例
ここからはHTTPヘッダーが利用される具体例の紹介です。ベーシック認証時にはサーバーから「WWW-Authenticate」ヘッダーが送信され、クライアントは「Authorization」ヘッダーで資格情報を送信します。
ローカルにHTTPのベーシック認証サーバーを立ててtelnetから通信してみました。
GET / HTTP/1.1
Host: 192.168.1.1
(エンター2回押下)
HTTP/1.1 401 Unauthorized
Date: Sat, 06 Nov 2021 05:21:29 GMT
Server: Apache/2.4.51 (Debian)
WWW-Authenticate: Basic realm="auth"
Content-Length: 458
Content-Type: text/html; charset=iso-8859-1
....
パスワードを設定せずに通信すると、401エラーが返されました。ブラウザならこのレスポンスを受け取ると認証情報の入力画面が表示されます。
Authorizationヘッダーに認証情報を付与して送りると認証が通り通常のレスポンスが帰ってきます。Authorizationの値は「ユーザー名:パスワード」をBase64エンコードしたものになります。
GET / HTTP/1.1
Host: 192.168.1.1
Authorization: Basic YXV0aDoxMjM=
(エンター2回押下)
HTTP/1.1 200 OK
Date: Sat, 06 Nov 2021 05:32:29 GMT
Server: Apache/2.4.51 (Debian)
Last-Modified: Sat, 06 Nov 2021 05:11:15 GMT
ETag: "29cd-5d017ca05cb6e"
Accept-Ranges: bytes
Content-Length: 10701
Vary: Accept-Encoding
Content-Type: text/html
....
他のHTTPヘッダー
ほかのHTTPヘッダーには次のようなものがあります。
- Age
プロキシのキャッシュに入ってからの経過秒数が入っています。
- Cache-Control
キャッシュに関係する指示を設定するヘッダーです。
これらに設定できるものには、有効期限を示す「max-age=秒数」や、キャッシュを禁止する「no-store」などがあります。
- Expires
レスポンスの有効期限をHTTP-date形式で設定します(例 Expires: Wed, 21 Oct 2015 07:28:00 GMT)
- Vary
キャッシュされたレスポンスを使用できるかどうかの判断基準を設定します。
Cache-Control: no-storeとほぼ同じ意味になる「*」が入るか、判断基準となるヘッダー名のリストが入ります。
たとえば「Vary: User-Agent」があった場合、ユーザーエージェントが異なればキャッシュされたレスポンスは利用されません。ただしこれらの挙動はキャッシュサーバーの実装に依存します。
- Accept
クライアントが解読できるコンテンツのMIMEタイプを伝えます。これを受け取ったサーバーは提案のうちのひとつを選択し、Content-Typeレスポンスヘッダーで伝えます。
「;q=0.9」という記述はQ値(Quality values)と言って優先度を0~1の間で示すものです。これは1.0が一番優先順位が高く、最大3つまで指定できます。
- Accept-Encoding
HTTPのリクエストヘッダーでコンテンツのエンコーディング(通常圧縮方法)の指定をするものです。
- Accept-Language
リクエストする英語、日本語などの言語です。これにもQ値の指定ができます。一致する言語がない場合は論理的には(406 Not Acceptable)エラーを返すルールですが、利便性の観点からたいていのサーバーではAccept-Languageの値を無視しているようです。
- Cookie
ユーザー側に保存されているCookieもヘッダ情報としてやりとりされます。
- Set-cookie
サーバーがユーザーにCookieをセットする際に利用されます。
- Origin
どこから読み込みが発生したかというオリジン情報が入ります。「プロトコル(http or https)://ホスト名:ポート」という記述になります(ポートは省略可)。
- Access-Control-Allow-Origin
ブラウザに表示されたアドレスと違う通信を行うCORS通信の際、許可するオリジンを設定します。
- Content-Disposition
この値が「inline」だとページ表示、「attachment」だとダウンロードになります。「attachment」指定時は;で区切った後にfilename="filename"としてファイル名を指定できます。
- Content-Length
データの大きさをバイト単位の10進数で示します。
- Content-Type
MIMEタイプを設定するほか、;で区切って「charset=文字コード」として文字コードを設定したり、マルチパートのデータは「boundary=--文字列」で境界を区切る文字を指定したりします。
- Content-Encoding
メッセージの圧縮アルゴリズムを指定します。
- Content-Language
メッセージの言語を指定します。
- Location
ページのリダイレクト先です。PHPでリダイレクトをする際に、header('Location: URL');とすることでなじみのある人もいると思います。
- Referer
リファラです。
- User-Agent
ユーザーエージェント情報です。
- X-Content-Type-Options
通常ブラウザはMIMEタイプを自動で判別して表示するのですが、それを無効化してContent-Typeで指定したタイプに強制します。
使用時は「X-Content-Type-Options: nosniff」とします。
- Strict-Transport-Security
秒を指定し、期間中はこのヘッダーを送信したサイトと受け取ったブラウザの間の通信をSSLに限定します。このブログでもWindowsのApacheをSSL化した記事を載せていますが、そのようなサイトがSSL通信を強制する際に用います。
mod_rewrite等でhttpsへリダイレクトさせることも可能ですが、そのリダイレクト自体が攻撃者に狙われる隙になることもあるためこのような設定が存在します。
apacheサーバーでヘッダーを設定する
Debian10上で動いているapacheサーバーでキャッシュを無効化(Cache-Control: no-store)してみます。
まずヘッダを生成するにはheadersモジュールが必要です。確認するには「apachectl -D DUMP_MODULES」を利用します。出力されるモジュール群に「headers」は存在しなかったので、ここではa2enmodコマンドで有効化しました。
# apachectl -D DUMP_MODULES
...
# a2enmod headers
...
次に設定ファイルに「Header set 項目名 値」という形式でエントリーを記述します。ここでは、/etc/apache2/apache2.confにディレクトリエントリーを作り記述しました。
apache2.conf
<Directory /var/www/html/nocache> Header set Cache-Control no-store </Directory>
サーバー再起動後、ヘッダを確認して反映されているかブラウザから確認してみます。
PHPを使っていればheader関数で出力できます。今度はキャッシュの有効期限を1日(86400秒)にしてみます。
注意点としては本文を記述する(echo)前にheader関数を呼ぶ必要があります。
sample.php
header('Cache-Control: max-age=86400');