Webを支える技術　第3部　HTTP

備忘録です。

## 第6章 HTTPの基本

- TCP / IPとは
    - Transmission Control Protocol, Internet Protocol
    - 階層型プロトコル
        
        
        | アプリケーション層 | HTTP, NTP, SSH, SMTP, DNS |
        | --- | --- |
        | トランスポート層 | UDP, TCP |
        | インターネット層 | IP |
        | ネットワークインターフェース層 | イーサネット |
    - ネットワークインターフェース層
        - 物理的なケーブルやネットワークアダプタに相当する部分
    - インターネット層
        - IPアドレスを送り先として、パケット単位でデータをやり取りする
        - データを送り出すことだけ保証
    - トランスポート層
        - データの転送を保証（TCP）
        - TCPは相手に対しコネクションを張り、データの抜け漏れをチェックする
        - どのアプリケーションに渡るかはポート番号で決定する
            - HTTPはデフォルトで80番ポート
    - アプリケーション層
        - 具体的なインターネットアプリケーションを実現
- HTTPのバージョン
    - 0.9
        - ヘッダはなく、メソッドはGETのみ
    - 1.0
        - ヘッダの導入、GET以外のメソッドの追加
    - 1.1
        - 1.0の機能に加えて、チャンク転送、Acceptヘッダによるコンテントネゴシエーション、複雑なキャッシュコントロール、持続的接続など
- クライアントとサーバ
    - 各種のリクエストを出してレスポンスを受け取る
    - ユーザーエージェントとクライアントはほぼほぼ同じ意味
        - コネクション確立がクライアントリクエスト発行がユーザーエージェント
- リクエストとレスポンス
    - クライアント側で行われている事
        1. リクエストメッセージ構築
        2. リクエストメッセージの送信
        3. レスポンスが返るまで待機
        4. レスポンスメッセージの受信
        5. レスポンスメッセージの解析
        6. クライアントの目的を達成するために必要な処理
    - サーバー側で行われている事
        1. リクエストの待機
        2. リクエストメッセージの受信
        3. リクエストメッセージの解析
        4. 適切なアプリケーションプログラムへの処理の委譲
        5. アプリケーションプログラムから結果を取得
        6. レスポンスメッセージの構築
        7. レスポンスメッセージの送信
- HTTPメッセージ
    - リクエストメッセージ+レスポンスメッセージ
    - リクエストメッセージ
        - リクエストライン
            - メソッド、リクエストURI、プロトコルバージョン
            - GET /test HTTP/1.1
        - ヘッダ
            - Host: example.jp
        - ボディ
    - レスポンスメッセージ
        - ステータスライン
            - プロトコルバージョン、ステータスコード、テキストフレーズ
            - HTTP/1.1 200 OK
        - ヘッダ
            - Content-Type: application/xhtml+xml; charset=utf-8
        - ボディ
- HTTPのステートレス性
    - ステートレスなやり取りでは、サーバがアプリケーション（セッション）状態を記憶しない
    - ステートフルなやり取りでは記憶しておく
        - FTPサーバーなど
- ステートフルの欠点
    - サーバ間でデータを同期するオーバーヘッドが課題となり、スケールアウトさせにくい
- ステートレスの利点
    - リクエスト処理ごとに必要な情報がすべて含まれている自己記述的メッセージであるため、サーバ側のシステムが単純になる
- ステートレスの欠点
    - 送信データ量が多くなったり、認証などサーバに負荷がかかる処理を繰り返すことで、パフォーマンスの低下につながる
    - ネットワークトラブルが起きた時にそのリクエストが処理されたかどうかわからなくなるため、通信エラーへの対処が必要になる

## 第7章 HTTPメソッド

- HTTPメソッド

| メソッド | 意味 |
| --- | --- |
| GET | リソースの取得 |
| POST | 子リソースの作成、リソースへのデータの追加、そのほかの処理 |
| PUT | リソースの更新、リソースの作成 |
| DELETE | リソースの削除 |
| HEAD | リソースのヘッダ（メタデータ）の取得 |
| OPTIONS | リソースがサポートしているメソッドの取得 |
| TRACE | 自分宛にリクエストメッセージを返す（ループバック）試験 |
| CONNECT | プロキシ動作のトンネル接続への変更 |
- CRUDとの対応

| CRUD | メソッド |
| --- | --- |
| Create | POST/PUT |
| Read | GET |
| Update | PUT |
| Delete | DELETE |
- GET
    - 指定したURIの情報を取得
- POST
    - 子リソースの作成
    - リソースへのデータ追加
        - リソースの作成なのか、リソースの追加方法などはサーバ側の実装に依存する
    - 他のメソッドでは対応できない処理
        - GETでURIに含めていたキーワードをリクエストボディに含める
- PUT
    - リソースの更新
    - リソースの作成
- DELETE
    - リソースの削除
- HEAD
    - リソースのヘッダ（メタデータ）の取得
    - GETと似たメソッドだが、ネットワーク帯域の節約
- OPTIONS
    - リソースが許可するメソッド一覧を返す
    - Allow: GET, HEAD, PUT, DELETE
- POST / PUTの使い分け
    - リソースのURIをサーバで決定（POST）クライアントが決定（PUT）するか
    - PUTはサーバとの結合が密になるため、特に理由がなければPOSTを使うべき
- POSTでPUT / DELETEを代用する方法
    - _methodパラメータを使う
        - <input type=”hidden” id=”_method” name=”_method” value=”PUT”/>
        - フォームで隠しパラメータ_methodを用意し、valueに本来送りたかったメソッドを入れる
        - サーバ側はこのリクエストをPUTとして扱う
    - X-HTTP-Method-Overrideを使う
        - X-HTTP-Method-Override: PUT
        - POST内容がapplication/x-www-form-urlencoded以外の場合でもOK
- 条件付きリクエスト
    - メソッドを実行するかどうかヘッダの情報を使ってサーバが選択できる
- 冪等性と安全性
    - 冪等とはある操作を何回行っても結果が同じことを意味する
    - 安全性とは操作対象のリソースの状態を変化させない（副作用がない）ことを意味する

| メソッド | 冪等 | 安全 |
| --- | --- | --- |
| GET、HEAD | ○ | ○ |
| PUT、DELETE | ○ | × |
| POST | × | × |
- メソッドの誤用
    - GETの安全性を破壊するような使い方はやめる
        - GET /resources/1/delete HTTP/1.1
    - 他のメソッドでできることをPOSTで実現しない
    - PUTが冪等でなくなるような使い方をしない
        - PUTでリソース内容の相対的な差分を送信するような使い方をすると、冪等性が崩れる
        - なるべくリソースの完全な表現を送信するようにする
    - DELETEが冪等でなくなるような使い方をしない
        - 削除リソースが、/latestのような形式で最新バージョンのリソースを指し示すようなケースでは冪等性が崩れる
        - エイリアスリソースのような特殊なリソースは更新、削除などの操作ができないように設計する

## 第8章 ステータスコード

- ステータスコードの意味
    - 1XX　処理中
    - 2XX　成功
        - 200 OK（リクエスト成功）
        - 201 Created（リソースの作成成功）
    - 3XX　リダイレクト
        - 301 Moved Permanently（リソースの恒久的な移動）
        - 303 See Other（別URIの参照）
    - 4XX　クライアントエラー
        - 400 Bad Request（リクエストの間違い）
        - 401 Unauthorized（アクセス権不正）
        - 404 Not Found（リソースの不在）
    - 5XX　サーバエラー
        - 500 Internet Server Error（サーバ内部エラー）
        - 503 Service Unavailable（サービス停止）
- ステータスコードとエラー処理
    - プロトコルに従ったフォーマットでエラーを返す
        - WebサービスであればHTMLで問題ないが、WebAPIの場合はクライアントが解釈できる形式でエラーメッセージを返す必要がある
            - AtomPubを利用したWebAPIの場合、エラーメッセージはAtomで返す
    - Acceptヘッダに応じたフォーマットでエラーを返す
        - Accept: application/xhtml+xml;q=0.9,text/plain;q=0.3 → HTML
        - Accept: application/atom-xml;q=0.9,text/plain;q=0.5 → Atom
- ステータスコードの誤用
    - 404 Not Foundを返すべきところで200 OKを返すような実装をすると、専用のクライアント実装が必要になったり、誤作動を起こしたりする危険がある

## 第9章 HTTPヘッダ

- HTTPヘッダの重要性
    - ヘッダはメッセージのボディに対する付加的な情報（メタデータ）
    - リソースへのアクセス権の設定やキャッシュなどのHTTPの機能もヘッダで実現する（ヘッダ+メソッド、ステータスコード）
- HTTPヘッダの生い立ち
    - HTTPの仕様策定が進められるに従って、電子メールのメッセージ仕様（RFC822）のヘッダ形式を借りてくる形で追加
        - そのため電子メールのメッセージヘッダと共通する部分が多い
        - 一方でHTTPでは双方向（リクエスト/レスポンス）のやり取りをするため、電子メールにはない、さまざまなヘッダが追加されている
- 日時ヘッダ
    
    
    | 利用するメッセージ | ヘッダ | 意味 |
    | --- | --- | --- |
    | リクエストとレスポンス | Date | メッセージを生成した日時 |
    | リクエスト | If-Modified-Since | 条件付きGETでリソースの更新日時を指定する時に利用する |
    |  | If-Unmodified-Since | 条件付きPUTや条件付きDELETEでリソースの更新日時を指定する時に利用する |
    | レスポンス | Expires | レスポンスをキャッシュできる期限 |
    |  | Last-Modified | リソースを最後に更新した日時 |
    |  | Retry-After | 再度リクエストを送信できるようになる日時の目安 |
    - HTTPでは日時は全てGMTで記述する
- MIMEメディアタイプ
    - Multipurpose Internet Mail Extensions
    - メッセージでやり取りするリソースの表現の種類を指定する
    - Content-Type
        - メッセージのボディの内容がどのような種類かをメディアタイプで示す
        - Type/Subtypeという構成で、Typeは9種類に固定されているが、Subtypeは比較的自由に増やせる
        - 独自のメディアタイプを発明する前にIANAに同様のものが登録されていないか確認する
    
    | タイプ | 意味 | 例 |
    | --- | --- | --- |
    | text | 人が読んで直接理解できるテキスト | text/plain |
    | image | 画像データ | image/ipeg |
    | audio | 音声データ | audio/mpeg |
    | video | 映像データ | video/mp4 |
    | application | そのほかのデータ | application/pdf |
    | multipart | 複数のデータからなる複合データ | multipart/related |
    | message | 電子メールメッセージ | message/rfc822 |
    | model | 複数次元で構成するモデルデータ | model/vrml |
    | example | 例示用 | example/foo-bar |
    
    | タイプ / サブタイプ | 意味 |
    | --- | --- |
    | text/plain | プレーンテキスト |
    | text/csv | CSV形式のテキスト |
    | text/css | CSV形式のスタイルシート |
    | text/html | HTML文書 |
    | text/xml | XML文書（非推奨） |
    | image/jpeg | JPEG画像 |
    | image/gif | GIF画像 |
    | image/png | PNG画像 |
    | application/xml | XML文書 |
    | application/xhtml+xml | XHTML文書 |
    | application/atom+xml | Atom文書 |
    | application/atomsvc+xml | Atomのサービス文書 |
    | application/atomcat+xml | Atomのカテゴリ文書 |
    | application/javascript | JavaScript |
    | application/json | JSON文書 |
    | application/msword | Word文書 |
    | application/vnd.ms-excel | Excel文書 |
    | application/vnd.ms-powerpoint | PowerPoint文書 |
    | application/pdf | PDF文書 |
    | application/zip | ZIPファイル |
    | application/x-shockwave-flash | Flashオブジェクト |
    | application/x-www-form-urlencoded | HTMLフォーム形式 |
    - charsetパラメータ
        - charsetパラメータは省略可能だが、textタイプの場合はISO 8859-1で解釈される
            - 尚、XMLのような文書本体で文字エンコーディング宣言ができる場合でもcharsetパラメータが優先されてしまう
            - 対策としては
                - charsetパラメータを省略しない
                - applicationタイプを活用する
- 言語タグ
    - リソース表現の自然言語を指定するヘッダ（Content-Language）
    - Content-Language: ja-JP（言語コード-地域コード）
- コンテントネゴシエーション
    - メディアタイプ、文字エンコーディング、言語タグをクライアントと交渉しながら決める
    - Accept
        - クライアントが処理できるメディアタイプをサーバに伝える
        - Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
            - ;q=XXがないものは優先度1
            - *は任意のものを表す
        - サーバが応えられない場合は406 Not Acceptableが返される
    - Accept-Charset
        - 処理できる文字エンコーディングを伝える
    - Accept-Language
        - 処理できる言語を伝える
- Content-Lengthとチャンク転送
    - 静的なファイルなどあらかじめサイズのわかっているリソースを転送する場合は、Content-Lengthヘッダを使う
    - Transfer-Encodingヘッダにchunkedを指定すると、最終的なサイズがわからないボディを少しずつ転送できるようになる
        - ファイルサイズが決まるまでレスポンスを返せずに応答性能が低下するのを防げる
- 認証
    - HTTP認証方式にはBasic認証とDigest認証がある
        - Basic認証
            - ユーザー名とパスワードによる認証方式
            - Basicに続けてユーザー名:パスワードをBase64エンコーディングしたもの
                - 簡単にデコード可能であることに注意する
                    - セキュリティ強度の確認、SSL、TLSを使ってHTTPS通信し通信路上で暗号化するか検討する必要がある
                    - HTTPS
                        - HTTP＋SSL/TLSを組み合わせた通信の総称
                        - クライアントとサーバでやり取りするデータを保護し、盗聴を防ぐ目的で使う
                        - SSL/TLSの機能は以下を提供する
                            - 共通鍵暗号に基づく暗号化機能
                            - 公開鍵証明書に基づく認証機能
                            - ハッシュ用共通鍵に基づく改ざん検知機能
                        - HTTPSで通信する場合のURIはhttpsスキームを使う、デフォルトのポート番号は443
            
            ```python
            DELETE /test HTTP/1.1
            Host: example.jp
            Autorization: Basic dXNlcjpwYXNzd29yZA==
            ```
            
        - Digest認証
            - Basicよりセキュア（でやや複雑な手続きが必要）な認証方法
            - WWW-Authenticateヘッダ（チャレンジ）を使ってクライアントは次のリクエストを組み立てる
                
                ```python
                # リクエスト
                DELETE /test HTTP/1.1
                Host: example.jp
                
                # レスポンス
                HTTP/1.1 401 Unauthorized
                WWW-Authenticate: Digest realm="Example.jp", nonce="lac421d9e0a4k7q982z966p903372922", qop="auth", opaque="92eb5ffee6ae2fec3ad71c777531578f"
                ```
                
                - nonce（number used once）
                    - リクエスト毎に変化
                    - サーバの実装依存でタイムスタンプ（有効期限を狭める目的）やパスワードから生成
                - qop（quality of protection）
                    - authかauth-initを指定
                        - authはメソッド+URIからダイジェストを作成
                        - auth-initはメソッド+URI+ボディからダイジェストを作成（メッセージ全体が改ざんされていないか保証できる）
                - opaque
                    - クライアントに推測できない文字列
                    - 同じURI空間へのリクエストでは共通してクライアントからサーバへ送られる
                - ダイジェストの生成と送信
                    1. ユーザー名、realm、パスワードを”:”で連結し、MD5（Message Digest Algorithm 5）ハッシュ値を求める
                    2. メソッドとURIのパスを”:”で連結し、MD5ハッシュ値を求める
                    3. 1.の値、nonce、クライアントがnonceを送った回数、cnonce（クライアントが生成したnonce）、qopの値、2.の値を”:”で連結し、MD5ハッシュ値を求める
                        
                        ```python
                        DELETE /test HTTP/1.1
                        Host: example.jp
                        Authorization: Digest username="yohei", realm="Example.jp", nonce="lac421d9e0a4k7q982z966p903372922", uri="/test", qop="auth", nc=00000001, cnonce="900150983cd24fb0d6963f7d28e17f72", responce="0fde218e18949a550985b3a034abcbd9", opaque="92eb5ffee6ae2fec3ad71c777531578f"
                        ```
                        
            - 利点
                - パスワードを盗まれる危険性がなく、サーバ上にパスワードのハッシュ値を置くだけで良いのでセキュリティリスクは下がる
            - 欠点
                - メッセージ自体は平文でネットワーク上を流れるので、暗号化したい場合はHTTPSを利用する
                - リクエストのたびに一度401 Unauthorizedレスポンスを受け取らなければならず、操作が煩雑
                - ApacheなどのWebサーバではDigest認証がオプション扱い
                    - CGIなどの別プロセスで動作するプログラムの場合は、セキュリティの問題で認証関係のヘッダを渡してくれないため、Digest認証が利用できない
        - あるリソースにアクセス制限がかかっている場合、ステータスコード401 UnauthorizedとWWW-Authenticateヘッダを利用して、アクセスに必要な認証情報を通知できる
            
            ```python
            # request
            Delete /test HTTP/1.1
            Host: example.jp
            
            # response
            HTTP/1.1 401 Unauthorized
            WWW-Authenticate: Basic realm="Example.jp"
            ```
            
        - 同じURI空間に属するリソースには同じ認証情報を送信できると仮定して良いことになっている
    - HTTP1.1標準外の認証方式
        - WSSE認証
            - WS-SecurityのUsernameTokenがベースとなった認証機構（WS-Security Extension）
            - Basic認証とDigest認証の中間に位置する認証方式のイメージ
            - 活用シーンとしては、SSLやTLSが利用できず（Basic認証不可）、ホスティングサービス上のCGIスクリプトなどでDigest認証も使えない場合など
                
                ```python
                # request
                Delete /test HTTP/1.1
                Host: example.jp
                
                # response
                HTTP/1.1 401 Unauthorized
                WWW-Authenticate: WSSE realm="Example.jp", profile="UsernameToken"
                ```
                
        - ダイジェストの生成と送信
            1. パスワードと自前のnonceと日時を連結した文字列のSHA-1（Secure Hash Algorithm 1）ハッシュ値を求め、Base64エンコードし、パスワードダイジェストを作成する
            2. AuthorizationヘッダにWSSEとprofile=”UsernameToken”を指定して、X-WSSE拡張ヘッダにパスワードダイジェストやnonce、日時情報を入れてリクエストを送信する
                
                ```python
                DELETE /test HTTP/1.1
                Authorization: WSSE profile="UsernameToken"
                X-WSSE: UsernameToken Username="test", PasswordDigest="pKKkpKSmpKikqqSrpK2krw==", Nonce="88akf2947cd33aa", Created="2010-05-10T09:45:22Z"
                ```
                
        - 利点
            - パスワードそのものをネットワーク上に流さなくて良い
            - パスワードダイジェストほどの手間がない
        - 欠点
            - サーバ側で生のパスワードを保存しておく必要がある
    - OpenIDとOAuth
        - OpenID
            - Identity Provider（IdP）のアカウントを利用してService Provider（SP）にログインできるようになる
        - OAuth
            - ユーザーがService Provider（SP）からConsumerにデータを渡すことに同意するとService ProviderとConsumerがデータをやり取りできるようになる
            - 認可情報を移譲する機能
- キャッシュ
    - サーバから取得したリソースをローカルストレージに蓄積し、再利用する手法
    - キャッシュ用ヘッダ
        - Pragma
            - キャッシュを抑制する
        
        ```python
        HTTP/1.1 200 OK
        Content-Type: application/xhtml+xml; charset=utf-8
        Pragma: no-cache
        ```
        
        - Expires
            - キャッシュの有効期限を示す
            - 最長でも1年後の日時を入れるように推奨されている
        
        ```python
        HTTP/1.1 200 OK
        Content-Type: application/xhtml+xml; charset=utf-8
        Expires: Thu, 11 May 2010 16:00:00 GMT
        ```
        
        - Cache-Control
            - 詳細なキャッシュ方法を指定する
            - Pragma, Expiresの代用も出来る
            - 相対的な時間指定をしたい場合に仕様する
    - 条件付きGET
        - キャッシュ用ヘッダでキャッシュの再利用ができないと判断した場合でも条件付きGETでサーバ側リソースがキャッシュから変更されているか調べるヒントをリクエストヘッダに含めることで再利用が可能な場合がある
        - If-Modified-Since
            - リソースの更新日時を条件にする
            - 指定した更新日時以降リソースが変更されていなければ、304 Not Modifiedを返す
            
            ```python
            GET /test HTTP/1.1
            Host: example.jp
            If-Modified-Since: Thu, 11 May 2010 16:00:00 GMT
            ```
            
        - If-None-Match
            - 指定した値（ETag）を条件にする
            - 指定した値にマッチすれば（キャッシュしてあるリソースのETagヘッダと比較）304 Not Modifiedを返す
            
            ```python
            GET /test HTTP/1.1
            Host: example.jp
            If-None-Match: ab3322028
            ```
            
        - 出来るだけ、ETagヘッダを利用する
        - ETagの計算の注意点
            - Apacheのデフォルトでは、ETagはinode番号、ファイルサイズ、更新日時から自動で計算されるので、サーバを分散させている場合は注意が必要
                - その場合は、ファイルサイズ、更新日時から計算するよう設定できる
            - 動的ページでは、Webサーバが自動で計算してくれないので、WebアプリケーションでETagの値を計算する
                - リソースのハッシュを使うのが簡単だが、サイズが大きい場合は、そのメタデータを使ったり、リソースの更新カウンタなどを用意して代用する
        - 持続的接続
            - HTTP1.0ではクライアントがTCPコネクションを確立してリクエストを送信し、サーバがそれにレスポンスを返すたびに、TCPコネクションを切断していたが、コネクションの確立はコストがかかる処理なので動作遅延の問題があった
            - そこでKeep-Aliveヘッダが導入され、まとめて接続し続けることを可能にした
            - 持続的接続では、クライアントがレスポンスを待たずにリクエストを送信できる（パイプライン化）ことにより、より効率的にメッセージ処理ができるようになった
            - コネクション切断の際は、リクエストでConnectionヘッダにcloseを指定する
            
            ```python
            GET /test HTTP/1.1
            Host: example.jp
            Connection: close
            ```
            
        - そのほかのHTTPヘッダ
            - Content-Disposition
                - サーバがクライアントに対してそのリソースのファイル名を提示するために利用するレスポンスヘッダ
                
                ```python
                Content-Disposition: attachment; filename="rest.txt"
                ```
                
            - Slug
    - クライアントがAtomのエントリをPOSTする際に、新しく生成するリソースのURIのヒントとなる文字列をサーバに提示できる
    
    ```python
    Slug: %E3%83%86%E3%82%B9%E3%83%88
    ```