HTTP
HTTPリクエスト
HTTPレスポンス

HTTPリクエスト/レスポンスの構成要素を初心者にも分かるように解説してみた

初めに

そもそも、なぜこのような記事を書くことになったかというとバイト先で「HTTPについて調べます。」と先輩に宣言して、どうやって成果物を出すか迷っていた際に、「記事にすれば、良いんじゃないか?」と思ったからです。
HTTPを勉強しようと思った理由はブロックチェーンとかXR(VRARMR)とかが流行ってるけど、結局はHTTPというプロトコルの上で動作しているよね?それ知らないで作るのきつくね?と思ったからです。

対象としている読者は以下の通りです。

  • 何かしらのフレームワークを使用して、Webアプリケーションを作ったことがある。
  • 研修明けで開発チームに配属されて間もない。
  • HTTPって何それ?美味しいの?状態、要するに何も知らない状態である。

そして、誤りがある場合は指摘をいただきたいです。もう一度調べ直した上で、修正/加筆を行います。理由は以下の通りです。

  • この記事を見たHTTPについて知らない人に誤った情報が渡ってしまうことを防ぐため。
  • 私自身の勉強のため。

HTTPとは

HTTPとはアプリケーション層における通信の規約の1つです。この通信の規約をプロトコルと呼びます。プロトコルには他にも多くの種類が存在します。メールを送信する際に使用されるSMTP、果てにはSpaceWireと言われる宇宙機用に標準化作業が行われているデータ通信I/F規格(https://ja.wikipedia.org/wiki/SpaceWire) もあるらしいです。ですが、今回の記事では説明しないです(というかできません)。気になる人はググってください。

では、HTTPの動作を確認しつつ、中身を見ていくことにします。

ここでは、実際にcurlコマンドを使って確認します。読者の方は実際にやる必要はないですが、やって見たい場合はcurlを使用できる環境を整えて、実行してください。Macの方は元々インストールされているはずなので、特に準備をする必要はありません。Windowsの方は分からないので、ググってください。

では、実際にどうなっているを説明します。

まず、このようなコマンドを入力します。

curl --http1.1 --get -v https://qiita.com

すると、https://qiita.com に対して、リクエストを送信し、その結果レスポンスが返って来るのが分かります。

リクエスト
GET / HTTP/1.1
Host: qiita.com
User-Agent: curl/7.54.0
Accept: */*
レスポンス
HTTP/1.1 200 OK
Date: Mon, 26 Mar 2018 04:07:56 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
ETag: W/"Y2Y2OWYyMzFkMzliNzc2YTkxNjFjYzc2OWUzYjJmYzE"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _qiita_login_session=TkdGNFkyczNWVlpuYUVKaE9IbFRTMFJ2T0U5QmVYRXpVaXRHU1hwMGMzYzFlRkZ6ZFU1dGJIWkVVRzF3Y1cxQ1VIjZSREZhYWtOWVVWSTVieXRvVld0NFJIWkpOWFJ6YkRSRlRITTJXbGwwU1ZkT1ZtMXRTWHBJVmtScFNtaFlORlJDTUdOaE1rUXlZamhqWTNrME5HWnRiRWhhY0RoVmExUllOR2RqSzBGWE5XWnlRV1I2ZHpWNlZURTJkRGxxYUM5cmJHd3ZSRkZoVW05UmFrVTFSVkJMVFRKNWNYSk1Va001T0ZGb01IWmxMeXQwSzFFeWRXc3ZWWGxRVlZCdkxTMXZPRWRUVWtVck1HY3lRbEphVjB0d2FFaEZUMVJCUFQwJTNELQ-673bf4b86b9a8dc0f5090d260ad34123922fd859; domain=.qiita.com; path=/; expires=Tue, 26 Mar 2019 04:07:56 -0000; HttpOnly
X-Runtime: 0.178189
Strict-Transport-Security: max-age=2592000
X-Request-Id: b034baca-2c72-4af0-b0b1-7d94e8a1d4dd
Content-Security-Policy-Report-Only: default-src https: data: 'unsafe-eval' 'unsafe-inline'; report-uri https://us-central1-qiita-csp-report.cloudfunctions.net/csp-report

<!DOCTYPE html><html><head><meta charset="utf-8" /><title>Qiita</title><meta content="width=device-width,initial-scale=1,shrink-to-fit=no" name="viewport" /><meta content="#55c500" name="theme-color" /><link href="/opensearch.xml" rel="search" title="Qiita" type="application/opensearchdescription+xml" /><meta name="csrf-param" content="authenticity_token" />
#以下略

で、このコードで>で始まる行はリクエストです。<で始まる行はレスポンスです。
なので、コードだとGET / HTTP/1.1からAccept: / までがリクエストです。
一行空いて、HTTP/1.1 200 OKからこの写真の最後までがレスポンスです(本来であれば、見切れたところから大量の文字の羅列があります)。

では、リクエストの方から読んでいくことにします。

HTTPリクエスト

GET / HTTP/1.1
Host: qiita.com
User-Agent: curl/7.54.0
Accept: */*

この一行目はリクエストラインと呼ばれています。リクエストラインはHTTPメソッドで始まり、リクエスト先のURIとそのプロトコルのバージョンが続きます。そして、CRLFで終わります。
このコードでいうと、GETがHTTPメソッド、/がリクエスト先のURIを表しています。/だけなので、分かりづらいですが、パスが入るということです。例えば、こんな感じです。

GET /trend HTTP/1.1

これはqiitaのログインユーザーのトップページのパスです。最後にHTTP/1.1とあり、これがプロトコルのバージョンを表しています。

では、二行目以降の説明をします。
この二行目以降をヘッダーと呼びます。
リクエストというコンテキストの中ではヘッダーはリクエストヘッダーというものを意味していますが、話の中ではヘッダーということが多いので、ヘッダーと言います。
ヘッダーはそれに対応する値から構成されています。上記のコードでは

Host: qiita.com
User-Agent: curl/7.54.0
Accept: */*

などがヘッダーです。

ヘッダーが存在している理由はクライアントがリクエスト、リクエストを送信したクライアントに関する追加情報をサーバーに送信するためです。追加情報ってどういうことなのかというと、HTTPは現在は1.1が主流ですが、0.9/1.0が使用されていた時代もあり、0.9の時代はここでいう、リクエストラインのみでリクエストが構成されていました。なので、0.9の時代を考えれば、ヘッダーは追加情報と言えることができます。なので、追加情報とされているのでしょう。
ヘッダーは上で使用されているもの以外にも多くの種類があります。とりあえず、今は上で使用されているものだけを説明します。

  • Host
    ドメイン名と任意でサーバーが開いているTCP Port番号を指定することができます。Port番号を指定しない場合は、デフォルトで決められているPort番号にアクセスします。
    このヘッダーが実装された理由はバーチャルホストという仕組みが大きく関係しています。バーチャルホストとは一つのサーバーマシンで複数のウェブサイトを提供する仕組みのことです。詳細を知りたい人はググってください。

  • User-Agent
    リクエストを送信したクライアントのアプリケーションタイプ、OS、ソフトウェアベンダー、ソフトウェアのバージョンを入力することができます。
    上のコードではcurlのバージョン7.54.0を使用して送信されたリクエストであるということを示しています。

  • Accept
    クライアントが処理することができるcontent-typeをサーバーに伝えるために使用されています。ここではcontent-typeとは後述するボディに含まれているデータの種類のことです。そして、サーバー側でコンテントネゴシエーションと呼ばれる仕組みを用いて、クライアントから送信されてきたcontent-typeを基にどのタイプのデータをレスポンスに含むかを決めます。さらにヘッダーにContent-Typeを含めて、そのデータのContent-Typeを指定します。

今回はボディが存在しないリクエストになっていますが、HTTPメソッドがPOST等の場合はボディが含まれます。身近な例を挙げると、フォームの送信ではPOSTメソッドを用いたリクエストが行われています。フォームに入力した内容がボディには含まれます。

実際にやってみるとこのような感じになります。

curl --http1.1 -d title="studying about HTTP" -d author="koheiyamayama" http://localhost:18888
実行結果
POST / HTTP/1.1
Host: localhost:18888
Accept: */*
Content-Length: 46
Content-Type: application/x-www-form-urlencoded
User-Agent: curl/7.54.0

title=studying about HTTP&author=koheiyamayama

ヘッダー部分に改行が入ってそのあとに続く内容がボディです。

リクエストはリクエストライン、ヘッダー、ボディで構成されていて、それぞれがどんな感じなのかをまとめました。次はレスポンスについて説明します。

HTTPレスポンス

先ほどのリクエストに対するレスポンスは以下です。

HTTP/1.1 200 OK
Date: Sun, 25 Mar 2018 14:19:50 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
ETag: W/"ZGFmaWhlcmlvaGZnaWVhZ2h1aWVybGFoZmdpbGVyaA"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _qiita_login_session=YWZlZ3Jld2d3aW9lZ3Zkbmp2Y252ZGthamZnaWVv; domain=.qiita.com; path=/; expires=Mon, 25 Mar 2019 14:19:50 -0000; HttpOnly
X-Runtime: 0.253271
Strict-Transport-Security: max-age=2592000
X-Request-Id: 1650d346-2ed3-4bcb-9789-ed404ae13d31
Content-Security-Policy-Report-Only: default-src https: data: 'unsafe-eval' 'unsafe-inline'; report-uri https://us-central1-qiita-csp-report.cloudfunctions.net/csp-report

<!DOCTYPE html><html><head><meta charset="utf-8" /><title>Qiita</title><meta content="width=device-width,initial-scale=1,shrink-to-fit=no" name="viewport" /><meta content="#55c500" name="theme-color" /><link href="/opensearch.xml" rel="search" title="Qiita" type="application/opensearchdescription+xml" /><meta name="csrf-param" content="authenticity_token" />
#以下略

この一行目はステータスラインと言います。プロトコルのバージョン、数字でステータスコードとそれに付随するテキスト(Reason Phraseと言います)がこの一行で表されています。具体的にいうと、HTTP1.1はプロトコルのバージョン、200はステータスコード、OKはそれに付随するテキストです。
ステータスコードはレスポンスを理解しようとした結果を表す3桁の整数です。ステータスコードの一桁目はレスポンスの大まかな状態を表します。二桁目と三桁目はその状態の詳細を表します。一覧はここにあるので、見てください。 https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
Railsでよく見る404 Not Foundもステータスコードに即した形になっていますし、リダイレクトに関する状態もここで詳細に決められていることが分かったりするので、気になった時はこのページを見れば、正しい情報を手に入れることができます。

二行目以降はヘッダーです。リクエストと同じでレスポンスヘッダーと言いますが、話の流れでヘッダーと書くことが多いので、ヘッダーと書きます。
ヘッダーの説明は長いのと、調べる必要がある時は調べればいいと考えているので、今回は割愛します。ですが、後日書こうと思っている記事はETagとCache-Controlについてなので、この二つについてはそちらの記事を読んで欲しいです。
ですが、Content-Typeだけはここで説明します。先ほどのリクエストの説明で少しだけ関連した話が出てしまったので。

  • Content-Type レスポンスではContent-Typeヘッダーはリクエストに対して返したボディがどのcontent typeなのかをクライアントに伝えています。 人間はファイルの種類を拡張子で判断すると思いますが、ブラウザはファイルの判別をContent-Typeで行うようです。その理由はアプリケーションによっては拡張子を送信しない場合があるからです。 余談ですが、IEはファイルの中身を見て、ファイルの種類の判別を行うContent Sniffingという動作をすることがあるらしいです。これは現在は使用されることは少ないです。本来であれば、text/htmlで実行して欲しいが、そのファイル内にJSが記述されている場合、そのJSのコードを実行してしまうことがあったため、セキュリティホールになってしまう可能性があるからです。そして、sniffingをしないようにサーバーからクライアントに指示するためのヘッダーがX-Content-Type-Options: nosniffです。

ヘッダーの最後に改行が挟まってその次から始まるのがボディです。
今回はQiitaのTOPページのHTMLが保持されています。リクエストした結果、HTMLが返ってきてますが、返ってくるのはHTMLに限られません。JSON、XML、CSS等様々なものが返ってくる可能性があります。

最後に

この記事はここでお終いです。HTTPとは何なのかはこの記事に出てくる用語を理解できれば、Webアプリケーションを開発する上では困ることは少ないと思います。私自身、このような基礎的な理解はしましたが、HTTPS、ストリーミング、HTTP2などの仕組みは未だに分かっていません。これからはそのような勉強しつつ、ネットワークの勉強をしようと思っています。その前にCache-Controlに関する記事を書きますので、興味ある方はそちらの記事も読んでほしいです。

参考資料

  • Webを支える技術 HTTP、URI、HTML、そしてREST(p67~p151)
  • Real World HTTP 歴史とコードに学ぶインターネットとウェブ技術
  • HTTPの教科書
  • RFC2616 https://tools.ietf.org/html/rfc2616