Skip to content

Commit

Permalink
勉強会資料の作成(サーバとの通信) (#3)
Browse files Browse the repository at this point in the history
* ドキュメントを追加

* サンプルプログラムを追加

* Update README.md

* 👍ドキュメントの指摘事項の修正

* ドキュメントの修正と、まとめのセクションを追加

* コンフリクトの解消ができていなかった場所を修正

* 指摘事項を修正

* タイポの修正

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* 指摘事項を修正

* 目次が足りてなかったので追加

* べき等の説明に少し追加

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* 指摘事項の修正

* 冪等に関する説明の修正

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

* Update communication-with-server/README.md

Co-authored-by: yamaji <haruyuki.program@gmail.com>

---------

Co-authored-by: yamaji <haruyuki.program@gmail.com>
  • Loading branch information
Ryo-7 and haruyuki-16278 authored Aug 14, 2023
1 parent 5949d1d commit 9525fde
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 0 deletions.
265 changes: 265 additions & 0 deletions communication-with-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# サーバとの通信
ここではウェブ実装で必要となってくる知識について解説する
- [HTTPとは](#httpとは)
- [URLの構成](#urlの構成)
- [HTTPメソッド](#httpメソッド)
- [HTTPの中身](#httpの中身)
- [CORSとは](#corsとは)

# HTTPとは
HTTPとは(Hypertext Transfer Protocol)の略でwebの閲覧によく用いられるプロトコルである。
もともとHTTPはWebブラウザとサーバ間の通信のために設計されたが、それ以外にも使用されている(WebAPIの呼び出しなど)。
HTTPはステートレスであるがセッションレスではない。
今回の解説は、このHTTPを前提に解説を行う。
本筋から外れてしまうため解説はしないが、現在主流であるのは、このHTTPの通信を暗号化したHTTPS(Hypertext Transfer Protocol Secure)と呼ばれるプロトコルである。
HTTPSについて気になる場合は、HTTPS、SSL、TLSなどの単語で調べると良い。

> ### TIPS:ステートレストとセッションレス
> ステートレスとは **State**(ステート:状態)と言う単語に **less**(レス:ない)の意味を持った接尾辞がついた単語である。
> HTTPの通信におけるステートレスとは、2つ以上の通信において、それぞれに関係性がないことを指す。
> 例えばログインのリクエストをサーバに対して行い、成功したとする。
> HTTPはログインが成功したと言う情報(state)を保持しないため、ログイン済み限定のページにアクセスしようとすると失敗する。
> もちろん、実際にはこのようになっておらず、HTTPがこのようになっていないのは、HTTPがセッションレス**ではない**からである。
> 通信自体は状態を持たないが、通信にセッションの情報を乗せているため、HTTPはステートレスだがセッションレスではない。
# URLの構成
インターネット上のリソースにアクセスする場合、その接続先の情報をブラウザ等に与える必要がある。
ここで使用されるのが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`がオリジンである。

> ### TIPS:オーソリティ
> URLのスキーム以降、ポートまでをオーソリティと呼ぶ。
> あまり見かけないが、URLのドメイン名の前に認証のためのパスワードなどを含める場合があり、これもオーソリティに含まれる。
> `http://hoge:pass@example.com:8080/hoge`のようなURLであれば`hoge:pass@example.com:8080`を指す。
> `http://example.com:8080/hoge` であれあば `example.com:8080`である。
# HTTPメソッド
HTTPで通信を行うとき、メソッドと呼ばれるアクションを指定する。
仕様書で定義されているHTTPメソッドを以下に示す。

- GET
- POST
- PUT
- DELETE
- HEAD
- CONNECT
- OPTION
- TRACE
- PATCH

今回は頻繁に使用される`GET``POST`の二つについて解説する。
## GETとは
サーバからデータを取得する際に使用する。URLにクエリパラメータを含めることもできる。
クエリパラメータは人の目に触れやすい場所に付与されるため、他人に見られて困る内容(個人情報等)はGETで送信してはいけない。
GETメソッドは、**HTTPメソッドとして**べき等であり安全であるとされる。

ここで言う安全とはセキュリティ的な安全を指す言葉ではなく、サーバ側へ更新の要求をしないことを指す言葉である。
## POSTとは
サーバのデータを更新する時に使用する。データ自体はbodyに含めて通信を行う。
ただし、暗号化を行うわけではないので注意が必要である。
個人情報などを扱う場合はHTTPSなどを利用して、通信そのものを暗号化する必要がある。
**HTTPメソッドとして**、べき等ではなく安全でないとされる。

なお、このbodyに含めた情報のことをペイロードと言う。

> ### TIPS:べき等とは
> HTTPメソッドでは、ある一つのリクエストを複数回処理しても、一回リクエストを処理した時と、サーバのリソース(データ)が同一の状態になることをべき等性があるという。
<details>
<summary>リクエストにおけるべき等と安全</summary>

ここではHTTPメソッドにおける定義から、GETやPOSTについてべき等性・安全性を説明した。
ただし、GETメソッドやPOSTメソッドを用いた*APIリクエストについてのべき等性・安全性*は、メソッドの定義に依らず、**サーバー/クライアントの実装に依存する**ことに注意したい。
</details>

## 実際にやってみる
実際にGETとPOSTを使用した通信を見てみる
まず、GETの通信を見てみる
1. 新しいタブを開いて開発者ツールを開く
- もしそのブラウザで既にgithubにログインしている場合はシークレットブラウザを起動
1. `Network`タブを開く
![Networkタブの参考画像](./image.png)
1. `github.com`にアクセス
1. 開発者ツールに表示されている項目から`github.com`と表示されているものを探し、メソッドがGETであることを確認。
1. ページをリロードしたり、左上のgithubのロゴマークを何度かクリックしてみてページの内容が変わらないことを確認(べき等であることを確認)
1. github中央上部の検索窓に`jigintern`など好きな文字列をいれて検索をしてみる
1. 結果が表示されたら開発者ツールの中から`search`を探し、GETメソッドで呼び出されていること、Request URLの中に`jigintern`などの自分が入力したの文字列が含まれていることを確認
1. 同じ文字列なら何度検索をかけても同じ内容が表示されることを確認

続いてPOSTの通信を見てみる
1. githubのログインを行う
1. ログイン完了後、開発者ツールに表示されている項目から`session`を探し、POSTで呼び出されていること、URLにはパスワードが入っていないが、payloadタブを開くとパスワードが入っていることを確認

## まとめ
- GETで情報を送るときは、URLのパスの後ろにクエリパラメータとして付与される
- GETで人に見られてはいけない情報は扱わない
- GETはべき等であると定義されているので何度呼び出されても変わらない処理のみに使う

- POSTは情報を送るとき、bodyの中にペイロードとして持つ
- POSTはパスワードなど人に知られてはいけない情報で使う
- ただし、POST自体は暗号化を行わない
- POSTはべき等ではないので呼び出されるたびにサーバ側のデータが更新されるような処理に使う

- HTTPメソッドは動作や性質が定義されているが、動作を保証するわけではない
- サーバ側が定義通りの実装をする必要がある

# 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になっているものをクリック
- ヘッダやペイロードを確認

## まとめ
- リクエストもレスポンスも、送受信したいデータ以外にもさまざまな情報を持っている
- ヘッダから通信の情報を得ることができる
- ヘッダを任意に追加できるが、基本的にやらなくて良い

# CORSとは
Cross-Origin Resource Sharingの略。
オリジンを跨いでデータの送受信を行える。
オリジンとは、スキーム(プロトコル)、ホスト(ドメイン)、ポート、この3つによって構成されるものである。
ブラウザにはCSRFやXSSといった攻撃を防ぐために、異なるオリジン間ではデータを送受信できないようにする同一オリジンポリシーが存在する。
しかし、HTMLやJavaScriptを取ってくるオリジンとアクセスするAPIのオリジンが異なることがあり、このような場合に不都合が出るためCORSが考案された。
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オブジェクトが使用されていないこと


## 実際に見てみる
0. `/communication-with-server`に移動
1. `deno run server.js`を実行
1. 別のターミナルで`deno run client.js`を実行
1. ブラウザで`http://localhost:8000`にアクセスして開発者ツールを開く
1. `try cors`ボタンを押して NewworkタブやConsoleに出力されたデータを確認する。
1. `try cors (simple request)`ボタンを押して、Newworkタブや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`を付与する必要がある。
これらのヘッダの結果により、安全性が確保できた場合、単純リクエストと同様のヘッダで通信を行う。

## 実際に見てみる
0. `/communication-with-server`に移動
1. `deno run server.js`を実行
1. 別のターミナルで`deno run client.js`を実行
1. ブラウザで`http://localhost:8000`にアクセスして開発者ツールを開く
1. `try cors (prefright request)`ボタンを押して Newworkタブや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にすれば基本的に問題なく動作する。

もし、より深く理解したい場合は参考文献等を利用して自学することをお勧めする。

# 参考文献
- [HTTPの概要](https://developer.mozilla.org/ja/docs/Web/HTTP/Overview)
- [HTTPメソッドについて](https://developer.mozilla.org/ja/docs/Web/HTTP/Methods)
- [GETとPOSTの違いについて](https://qiita.com/kanataxa/items/522efb74421255f0e0a1)
- [CORSについて](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)
- [CORSを絶対に理解する](https://zenn.dev/syo_yamamoto/articles/445ce152f05b02)
12 changes: 12 additions & 0 deletions communication-with-server/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { serve } from "https://deno.land/std@0.180.0/http/server.ts";
import { serveDir } from "https://deno.land/std@0.180.0/http/file_server.ts";

serve(async (req) => {

return serveDir(req, {
fsRoot: "public",
urlRoot: "",
showDirListing: true,
enableCors: true,
});
});
Binary file added communication-with-server/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions communication-with-server/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>

<body>
<div>
<h1 id="server_response"></h1>
<button id="cors">try cors</button>
</div>

<div>
<button id="cors2">try cors (simple request) </button>
</div>

<div>
<button id="cors3"> try cors (prefright request) </button>
</div>


<script type="module">

cors.onclick = async () => {
const response = await fetch("http://localhost:3000/cors");
server_response.innerText = await response.text();
};

cors2.onclick = async () => {
const response = await fetch("http://localhost:3000/cors-additional-header");
server_response.innerText = await response.text();
};

cors3.onclick = async () => {
const response = await fetch("http://localhost:3000/cors-additional-header",{
method: "POST",
headers: {"Content-Type": "application/json"},
body:JSON.stringify({name:"hoge"})
}
)
server_response.innerText = await response.text();
}

</script>
</body>

</html>
Loading

0 comments on commit 9525fde

Please sign in to comment.