ここではウェブ実装で必要となってくる知識について解説する
HTTPとは(Hypertext Transfer Protocol)の略でwebの閲覧によく用いられるプロトコルである。
もともとHTTPはWebブラウザとサーバ間の通信のために設計されたが、それ以外にも使用されている(WebAPIの呼び出しなど)。
HTTPはステートレスであるがセッションレスではない。
今回の解説は、このHTTPを前提に解説を行う。
本筋から外れてしまうため解説はしないが、現在主流であるのは、このHTTPの通信を暗号化したHTTPS(Hypertext Transfer Protocol Secure)と呼ばれるプロトコルである。
HTTPSについて気になる場合は、HTTPS、SSL、TLSなどの単語で調べると良い。
ステートレスとは State(ステート:状態)と言う単語に less(レス:ない)の意味を持った接尾辞がついた単語である。
HTTPの通信におけるステートレスとは、2つ以上の通信において、それぞれに関係性がないことを指す。
例えばログインのリクエストをサーバに対して行い、成功したとする。
HTTPはログインが成功したと言う情報(state)を保持しないため、ログイン済み限定のページにアクセスしようとすると失敗する。
もちろん、実際にはこのようになっておらず、HTTPがこのようになっていないのは、HTTPがセッションレスではないからである。
通信自体は状態を持たないが、通信にセッションの情報を乗せているため、HTTPはステートレスだがセッションレスではない。
インターネット上のリソースにアクセスする場合、その接続先の情報をブラウザ等に与える必要がある。
ここで使用されるのがURL(Uniform Resource Locator)である。
http://example.com:8080/hoge/index.html/?name=Taro#profile
上記のようなURLがあったとき、以下のような呼び分けをする。
- スキーム :通信のプロトコルを指定する。
http
- ドメイン :接続先のアドレスを指定する。
example.com
- ポート :接続先のサーバのポート番号を指定する。通常は省略される。
8080
- パス :ファイルが設置されている、サーバ内の場所を指定する。
hoge/index.html
- クエリパラメータ:クライアントからデータを渡すとき、URLに含めるならここに含める
name=Taro
- アンカーリンク :主にwebページで用いられ、ページ内の特定の場所を指定する。
#profile
また、スキーム、ドメイン、ポートまでをまとめてオリジンと呼ぶ。
今回の例であればhttp://example.com:8080
がオリジンである。
URLのスキーム以降、ポートまでをオーソリティと呼ぶ。
あまり見かけないが、URLのドメイン名の前に認証のためのパスワードなどを含める場合があり、これもオーソリティに含まれる。
http://hoge:[email protected]:8080/hoge
のようなURLであればhoge:[email protected]:8080
を指す。
http://example.com:8080/hoge
であれあばexample.com:8080
である。
HTTPで通信を行うとき、メソッドと呼ばれるアクションを指定する。 仕様書で定義されているHTTPメソッドを以下に示す。
- GET
- POST
- PUT
- DELETE
- HEAD
- CONNECT
- OPTION
- TRACE
- PATCH
今回は頻繁に使用されるGET
とPOST
の二つについて解説する。
サーバからデータを取得する際に使用する。URLにクエリパラメータを含めることもできる。
クエリパラメータは人の目に触れやすい場所に付与されるため、他人に見られて困る内容(個人情報等)はGETで送信してはいけない。
GETメソッドは、HTTPメソッドとしてべき等であり安全であるとされる。
ここで言う安全とはセキュリティ的な安全を指す言葉ではなく、サーバ側へ更新の要求をしないことを指す言葉である。
サーバのデータを更新する時に使用する。データ自体はbodyに含めて通信を行う。
ただし、暗号化を行うわけではないので注意が必要である。
個人情報などを扱う場合はHTTPSなどを利用して、通信そのものを暗号化する必要がある。
HTTPメソッドとして、べき等ではなく安全でないとされる。
なお、このbodyに含めた情報のことをペイロードと言う。
HTTPメソッドでは、ある一つのリクエストを複数回処理しても、一回リクエストを処理した時と、サーバのリソース(データ)が同一の状態になることをべき等性があるという。
リクエストにおけるべき等と安全
ここではHTTPメソッドにおける定義から、GETやPOSTについてべき等性・安全性を説明した。
ただし、GETメソッドやPOSTメソッドを用いたAPIリクエストについてのべき等性・安全性は、メソッドの定義に依らず、サーバー/クライアントの実装に依存することに注意したい。
実際にGETとPOSTを使用した通信を見てみる
まず、GETの通信を見てみる
- 新しいタブを開いて開発者ツールを開く
- もしそのブラウザで既にgithubにログインしている場合はシークレットブラウザを起動
Network
タブを開くgithub.com
にアクセス- 開発者ツールに表示されている項目から
github.com
と表示されているものを探し、メソッドがGETであることを確認。 - ページをリロードしたり、左上のgithubのロゴマークを何度かクリックしてみてページの内容が変わらないことを確認(べき等であることを確認)
- github中央上部の検索窓に
jigintern
など好きな文字列をいれて検索をしてみる - 結果が表示されたら開発者ツールの中から
search
を探し、GETメソッドで呼び出されていること、Request URLの中にjigintern
などの自分が入力したの文字列が含まれていることを確認 - 同じ文字列なら何度検索をかけても同じ内容が表示されることを確認
続いてPOSTの通信を見てみる
- githubのログインを行う
- ログイン完了後、開発者ツールに表示されている項目から
session
を探し、POSTで呼び出されていること、URLにはパスワードが入っていないが、payloadタブを開くとパスワードが入っていることを確認
-
GETで情報を送るときは、URLのパスの後ろにクエリパラメータとして付与される
-
GETで人に見られてはいけない情報は扱わない
-
GETはべき等であると定義されているので何度呼び出されても変わらない処理のみに使う
-
POSTは情報を送るとき、bodyの中にペイロードとして持つ
-
POSTはパスワードなど人に知られてはいけない情報で使う
-
ただし、POST自体は暗号化を行わない
-
POSTはべき等ではないので呼び出されるたびにサーバ側のデータが更新されるような処理に使う
-
HTTPメソッドは動作や性質が定義されているが、動作を保証するわけではない
-
サーバ側が定義通りの実装をする必要がある
サーバとの通信を行うための規格であるHTTPのリクエストの構成は主に以下の4つである。
- メソッド(GET、POSTなど)
- パス(ホスト以下の部分)
- プロトコルバージョン (HTTP 1.1など)
- リクエストヘッダ (詳細は後ほど)
また、POSTなら追加でbodyを持つ。
レスポンスの構成は主に以下の4つである。
- プロトコルバージョン
- ステータスコード (200など)
- ステータスメッセージ (OKなど)
- レスポンスヘッダ(詳細は後ほど)
- レスポンスボディ(GETメソッドでリクエストしたときにhtmlなどがここに入る)
なお、レスポンスのボディは省略可能。(POSTの場合は不要なことがある)
ここに追加の情報を載せることで主に通信を行う。 主なリクエストのヘッダを以下に示す。
- user-agent
- cookie
- host
- accept
- accept-language
- accept-encoding
また、主なレスポンスヘッダを以下に示す。
- content-type
- Content-Encoding
- set-cookie
- connection
- server
なお、ヘッダは基本的にブラウザなどが必要なものを自動的に付与するため、開発者が必要なヘッダを付与する必要はあまりない。
必要ではないだけで、任意の自作のヘッダなど好きなヘッダを付与することもできる。
ただし、既に定義されているヘッダは避ける必要があるため、慣れるまではヘッダ自作や付与はブラウザに任せるのが良い。
GETの中身を確認
- 開発者ツールを開いたブラウザのタブを開く
- ログインした状態の
github.com
を開く(リロードする) - Nameのカラムにgithub.comと書いてある行をクリック
- リクエストヘッダやレスポンスヘッダ、レスポンスボディなどを確認する
POSTの中身を確認
- 開発者ツールを開いたブラウザのタブを用意
- githubにログイン(ログイン中の場合はサインアウトして再度サインイン)
- NameのカラムがSessionになっているものをクリック
- ヘッダやペイロードを確認
- リクエストもレスポンスも、送受信したいデータ以外にもさまざまな情報を持っている
- ヘッダから通信の情報を得ることができる
- ヘッダを任意に追加できるが、基本的にやらなくて良い
Cross-Origin Resource Sharingの略。
オリジンを跨いでデータの送受信を行える。
オリジンとは、スキーム(プロトコル)、ホスト(ドメイン)、ポート、この3つによって構成されるものである。
ブラウザにはCSRFやXSSといった攻撃を防ぐために、異なるオリジン間ではデータを送受信できないようにする同一オリジンポリシーが存在する。
しかし、HTMLやJavaScriptを取ってくるオリジンとアクセスするAPIのオリジンが異なることがあり、このような場合に不都合が出るためCORSが考案された。
CORSは安全だと認められたオリジン間のみ、跨いでデータの送受信を行うことができる。
CORSを使用するにはサーバ、クライアントでそれぞれヘッダを付与する必要がある。
なお、一定の条件を満たす場合を除いて、プリフライトリクエストを行い安全性の確認を行う。
CORSリクエストを行う場合はリクエストヘッダに origin
を付与する必要がある。
このとき、レスポンスにはAccess-Control-Allow-Origin
のヘッダが付与され(サーバー側はこのヘッダを付与する必要がある)、このヘッダの内容とオリジンが一致するとき、レスポンスを受け取る。
なお、以下の条件をすべて満たすとプリフライトリクエスト(後述)を発生させないCORSリクエストとなる。
- 特定のHTTPメソッドで通信を行う
- GET
- HEAD
- POST
- 特定のヘッダのみが付与されている
- Accept
- Accept-Language
- Content-Language
- Content-Type
- Range
- Content-Typeに指定できるもの
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- XMLHttpRequest オブジェクトを使用してリクエストを行う場合は、XMLHttpRequest.upload プロパティから返されるオブジェクトにイベントリスナーが登録されていないこと。すなわち、
XMLHttpRequest
インスタンスをxhr
とした場合、どのコードもxhr.upload.addEventListener()
が呼び出してアップロードを監視するためのイベントリスナーを追加していないこと。 - リクエストに ReadableStreamオブジェクトが使用されていないこと
/transfer-protocol
に移動deno run server.js
を実行- 別のターミナルで
deno run client.js
を実行 - ブラウザで
http://localhost:8000
にアクセスして開発者ツールを開く try cors
ボタンを押して NetworkタブやConsoleに出力されたデータを確認する。try cors (simple request)
ボタンを押して、NetworkタブやConsoleに出力されたデータを確認する。
サーバは3000
番のポートで動作するように設定されており、クライアントは8000
番で動作するようになっている。
ポート番号が異なるためCORSを用いてアクセスする必要がある。
try cors
ボタンは/cors
を、try cors (simple request)
は/cors-additional-header
のエンドポイントを叩くようになっている。
/cors-additional-header
はヘッダーにAccess-Control-Allow-Origin:*
を付与してレスポンスを返しているが、/cors
は何も付与していない。
従ってtry cors
ボタンはエラーを吐くが、try cors (simple request)
ボタンは正常にレスポンスを受け取ることができ、メッセージを描画することができる。
前述の条件を満たせなかった場合、主にユーザのデータに影響があるリクエストの場合、プリフライトリクエストが発生する。
CORSリクエストの前に OPTION
メソッドを用いてプリフライトリクエストを送信し、そのレスポンスが安全だったときに通常のリクエストを行う。
プリフライトリクエストでは、クライアント側のヘッダには origin
に加えて Access-Control-Request-Method
とAccess-Control-Request-Headers
を付与する必要がある。
サーバ側のレスポンスヘッダには Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
、Access-Control-Max-Age
を付与する必要がある。
これらのヘッダの結果により、安全性が確保できた場合、単純リクエストと同様のヘッダで通信を行う。
/transfer-protocol
に移動deno run server.js
を実行- 別のターミナルで
deno run client.js
を実行 - ブラウザで
http://localhost:8000
にアクセスして開発者ツールを開く try cors (prefright request)
ボタンを押して NetworkタブやConsoleに出力されたデータを確認する。
try cors (prefright request)
ボタンはPOSTメソッドで、bodyにJSON形式のデータを持ってリクエストを行う。
そのため、Content-Type: application/json
のヘッダを持つ必要があるが、これはCORSの単純リクエストとしては許可されていない。
そこで、プリフライトリクエストを送り、このレスポンスに必要な情報があるか確認している。
- オリジンを跨ぐアクセスを許可する場合はCORSの設定を行う
- 特定の条件を満たせない場合プリフライトリクエストという、安全性の確認用の通信が発生する
- 外部のWebAPIを呼び出すときはCORSの設定が必要になる可能性あり
ここまでサーバとの通信に使われる技術について紹介してきた。 最後に実装していく上で意識することを中心にまとめる。
HTTPメソッドは、個人情報を扱うかデータを変更するならPOST
、クライアントがデータを要求するだけ(サーバ側のデータを更新しない)ならGET
を利用する。
その他にもHTTPメソッドは仕様として定義されているが、実装に慣れてきてから利用していくで問題ない。
ただし、どのHTTPメソッドを利用するにしても、サーバ側の実装は原則そのメソッドの仕様を守って実装する必要がある。
HTTP通信のヘッダは基本何もせず、ブラウザが自動で自動で付与するヘッダのみで問題ない。
CORSの設定について、サーバ実装においてはあまり気にしなくて良い。
これは、自分で実装したサーバに別のオリジンからアクセスされることがないからである。
クライアント側は外部のAPIを叩くときにCORSの設定をする必要があるが、fetchAPIを利用する場合、modeをCORSにすれば基本的に問題なく動作する。
もし、より深く理解したい場合は参考文献等を利用して自学することをお勧めする。