概要: この記事では、アプリ開発者の役に立ちそうな TCP/IPネットワークに関する最低限の知識をまとめた。 あくまで視点は「アプリ開発者」なので、 現在の家庭・企業における一般的なネットワーク環境しか想定していないし、 セキュリティに関しても基本的なことしかカバーしていない。 ネットワーク機器の具体的な設定方法や診断については、より専門的な資料を参照のこと。
オリジナル版との違い: オリジナル版では、ネットワークの低水準な層 (データリンク層など) から説明していた。 反転バージョンでは高水準な層 (HTTP) から先に説明する。
ウェブはもっとも有名なインターネットの使い方 (アプリケーション) である。 これは HTML型式で書かれたデータ (ようするに、Webページ) を、 HTTP (Hyper Text Ternsfer Protocol) と 呼ばれる方式によってブラウザに転送する。
では、HTTPにおけるリクエストとレスポンスの正体は何か?
実はただの文字列である。
これは curl
コマンドを使うと明らかになる。
$ curl -v http://www.example.com/index.html * Trying 93.184.216.34:80... * Connected to www.example.com (93.184.216.34) port 80 (#0)> GET /index.html HTTP/1.1 > Host: www.example.com > User-Agent: curl/8.1.2 > Accept: */* >< HTTP/1.1 200 OK < Accept-Ranges: bytes < Age: 31773 < Cache-Control: max-age=604800 < Content-Type: text/html; charset=UTF-8 < Date: Tue, 10 Oct 2023 09:11:50 GMT < Etag: "3147526947" < Expires: Tue, 17 Oct 2023 09:11:50 GMT < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT < Server: ECS (laa/7B10) < Vary: Accept-Encoding < X-Cache: HIT < Content-Length: 1256 <<!doctype html> <html> <head> <title> Example Domain</title> <meta charset="utf-8" /> ...
ブラウザ (あるいはcurlコマンド) で
http://www.example.com/index.html
GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 6.1;) Gecko/20100101 ...
GET
で始まる最初の行がリクエストの本体を表す。
続く Host:
や User-Agent:
などの行は
付随情報で、リクエストヘッダと呼ばれる:
Host:
サーバの名前
User-Agent:
ブラウザの種類
サーバはHTTPリクエストを受け取ると、 以下のようなレスポンス文字列を返す:
HTTP/1.1 200 OK Accept-Ranges: bytes Age: 31773 Cache-Control: max-age=604800 Content-Type: text/html; charset=UTF-8 Date: Tue, 10 Oct 2023 09:11:50 GMT ...
HTTP/1.1
で始まる最初の行にある
200
という数字がステータスコードと呼ばれる重要な部分で、
続く Accept-Ranges:
や Content-Type:
などの行は
付随情報 (レスポンスヘッダ) と呼ばれる:
Date:
サーバの応答時刻。
Content-Type:
コンテンツの種類 (HTMLファイルか画像か)。
Content-Length:
コンテンツの大きさ (バイト数)。
ヘッダの後には、 ボディ (body) あるいは ペイロード (payload) と 呼ばれるコンテンツ本体のデータが続く。 これは通常 HTML ファイルや画像ファイルの内容そのものである。 ブラウザはこの内容を解析して表示すると、ページが表示されたことになる。
正常なレスポンスの場合、最初の一行は必ず
となっているが、これ以外のステータスコードが返ってくる場合もある。HTTP/1.1 200 OK
たとえば、指定されたファイルが存在しない (いわゆる「ページが見つかりません」エラー)
場合はステータスコードとして 404
が返される。
HTTP/1.1 404 Not Found
あるいは「別の場所を参照せよ」という意味の 301
が返されることもある。
これが返されると、ブラウザのアドレスバーが変化する。
HTTP/1.1 301 Moved
curlを使って以下のURLにリクエストを送り、どのようなステータスコードが返されるのか確認しよう。
curl -v http://ja.wikipedia.org/
curl -v http://www.example.com/notfound
通常、ひとつのページを表示するために、ブラウザが同一のサーバに複数のリクエストを送ることは多い。 HTTPの各リクエスト・レスポンスは、多くの場合、単体で完結しており、 各リクエスト・レスポンスの間に直接の関連はない。 しかし クッキー (cookie) という仕組みを使うことで、 一連のリクエストに「文脈」を追加することができる。 クッキーは実際にはブラウザ内に保存される文字列で、 HTTP のリクエストとレスポンス内のヘッダを使ってやりとりされる。
Set-Cookie:
ヘッダを使ってクッキーをブラウザに送る。
Cookie:
ヘッダを使ってクッキーをサーバに送る。
実際のクッキーにはそれを受け取ったサーバ名(正確にはドメイン名)や有効期限といった情報が 付属しており、クライアントは特定のサーバに対して有効なクッキーだけを送るようになっている。
curlを使って以下のURLにリクエストを送って Set-Cookie:
ヘッダを確認してみよう。
curl -v https://www.google.com/
curl -v https://www.yahoo.co.jp/
では、curlコマンドはいったいどうやってHTTPリクエストを送っているのか?
答えは TCP である。TCP (一般的には関連規格を含めて TCP/IP と呼ぶ) は
インターネット上のコンピュータが通信するための汎用的な規格で、
現在はほとんどのPC/スマートフォン/ゲーム機等で利用可能である。
nc
コマンドは、TCP/IP を使って原始的な通信をおこなうプログラムである。
$ nc -l 10000
$ nc localhost 10000
何が起こっているかというと、
ここでは localhost (自分のPC) 上の
2つのプロセス (nc
) が互いに TCP の
10000番ポートを使って通信している。
上で見たように、HTTPリクエストやレスポンスの本質はテキストである。 実は curl を使わなくても、人間が ncコマンドを使って 人手で HTTPリクエストを送ることができる。 つまり www.example.com の 80番に接続し、 文字列を入力すればよいのである。
nc コマンドを使って、 www.example.com サーバに 「人間HTTPクライアント」としてリクエストを送り、 サーバ側のレスポンスを確認せよ。
$ nc www.example.com 80GET /index.html HTTP/1.0 Host: www.example.com (空行)HTTP/1.0 200 OK Content-Type: text/html; charset=UTF-8 Date: Tue, 10 Oct 2023 10:31:27 GMT ...<!doctype html> <html> <head> <title> Example Domain</title> ...
80番は、HTTP で使われる「お約束の」ポート番号として 国際的に定められている。 よく知られている標準のポート番号として、以下のようなものがある (cf. TCPやUDPにおけるポート番号の一覧):
なお、これらのポート番号はあくまで慣例であり、 別のポートを使ってもかまわない。 たとえば、開発環境用の webサーバは、 通常 80番ポートではなく、8080番ポート (や 5132番ポート) で動いている。
そのため、このようなサーバにアクセスするときには 以下の URL がよく使われる。
http://localhost:8080/
人間が手動で HTTP リクエストを送れるのだから、 同様に ncコマンドを使って、人間が web サーバの「ふり」をすることもできるのではないか? 実際にやってみよう。
nc コマンドを使って、
「人間HTTPサーバ」としてブラウザからの
リクエストに返答せよ。
まず nc
をサーバとして起動し、
ブラウザから http://localhost:8080/index.html
という URLに接続してリクエストが送られるのを確認してから、
応答を入力せよ。
$ nc -l 8080GET /index.html HTTP/1.1 User-Agent: Mozilla/5.0 ...HTTP/1.0 200 OK Content-Type: text/html<div>Hello.</div> (Control-D を押して終了)
以上のように、難しく見える HTTPの仕組みも、 実はただのテキスト処理だということがわかる。
ほとんどのプログラミング言語では HTTP のリクエストやレスポンスを 処理する機能が標準で含まれている。
$ node server.js
let http = require('http'); let server = http.createServer((request, response) => { console.log("connected!"); response.writeHead(200, {'Content-Type': 'text/html'}); response.end('<h1>Hello world</h1>'); }); server.listen(8080);
$ node client.js
(async () => { let response = await fetch('http://localhost:8080/'); let text = await response.text(); console.log(text); })();
HTTP や TCP による通信は現代のシステムでは簡単に扱えるが、 実はその仕組みは非常に複雑である。 したがって、うまく動かない時のために、 ある程度の内部的な原理を知っておくことは重要である。
HTTP のエラーは、サーバが (一応) 動いてはいるが、 クライアントのリクエストを正しく処理できなかったときに起こる。 多くの場合、HTTP ステータスコードとして以下のような値を返す:
TCPのエラーでよくあるのは「接続先のサーバが起動していない」場合である。 たとえ PC の電源が入っていても、指定されたポート番号でサーバが 待ち受けていない (listenしていない) 場合は、接続は失敗する。
サーバが起動してない状態で以下のコマンドを実行し、 TCP接続に失敗することを確認せよ。
$ nc -v localhost 8080
TCPの通信は、IPパケットという細かい単位に分割されて 相手のコンピュータに送信されている。
しかし、そもそもこの IPパケットが送れない (到達できない) 場合がある。 このような場合は、以下のような原因が考えられる:
このような場合「IPパケットが相手に正しく到達しているか?」を知るために
ping
コマンドが使える。
ping コマンドを使って、ネットワークの導通を確認してみよう。 (pingコマンドは永久に終わらないので、途中で Control + C を押して止める)
$ ping www.example.com PING www.example.com (93.184.216.34) 56(84) bytes of data. 64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=51 time=104 ms 64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=51 time=104 ms 64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=51 time=104 ms ^C --- www.example.com ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 103.890/103.906/103.935/0.016 ms
$ ping 10.10.10.10 PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data. ^C --- 10.10.10.10 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1017ms
$ ping localhost PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.124 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.062 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.076 ms ^C --- localhost ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3017ms rtt min/avg/max/mdev = 0.062/0.088/0.124/0.023 ms
これまで PC の名前 (ホスト名) として "www.example.com" とか "localhost" という名前を使ってきたが、 実はインターネット上に接続されているコンピュータはすべて IPアドレス によって指定する必要がある。
つまり本当は
ではなく$ nc www.example.com 80
のようにする必要がある。$ nc 93.184.216.34 80
ではなぜ www.example.com や localhost といった名前が使えるのか? というと、それは DNS (Domain Name System) という仕組みのおかげである。
curl
や nc
など) は
デフォルトで DNS に対応しており、これらのコマンドには
IPアドレスの代わりにホスト名を指定してもよい。
DNSによる名前解決は世界中の誰でも利用できるが、 「誰でもDNSで名前解決できる」からといって、 「そのIPアドレスに誰でもアクセス可能である」とは限らない。 実際には、DNSが返す IPアドレスには 特定のネットワークからしかアクセスできないものや、 そもそも何も接続されていない (使われていない) ものもある。
このように DNS はインターネットの基幹をなす仕組みであり、 ほとんどの API やアプリやサービスは DNS に依存している。 DNS が使えなければ、相手の IPアドレスがわからないので、 (たとえケーブルがつながっていたとしても) 通信ができないことが多い。 そのため DNS がうまく動かない場合、 多くのサービスやサイトで大規模な障害が発生する。
host
コマンドを使って、
自分が知っているホスト名の IPアドレスを求めてみよう。
$ host www.example.com www.example.com has address 93.184.216.34 www.example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
$ host ojaflekf.aeiorfja.asldkfj Host ojaflekf.aeiorfja.asldkfj not found: 3(NXDOMAIN)
DNS の仕組みは複雑なので、ここでの解説は省略する。
万が一 DNS の設定に問題がある場合は、たとえば dig
コマンドを使って
原因を調査することができる:
$ dig +trace www.example.com ; <<>> DiG 9.18.19 <<>> +trace www.example.com ;; global options: +cmd . 27273 IN NS j.root-servers.net. . 27273 IN NS k.root-servers.net. . 27273 IN NS b.root-servers.net. ... www.example.com. 86400 IN A 93.184.216.34 www.example.com. 86400 IN RRSIG A 13 3 86400 20231124112747 20231103152748 2182 example.com. ... example.com. 86400 IN NS a.iana-servers.net. example.com. 86400 IN NS b.iana-servers.net. ;; Received 322 bytes from 199.43.133.53#53(b.iana-servers.net) in 106 ms
本講座で出てきた重要な用語一覧。