HTTP/1.1まではサーバクライアント間の通信内容がテキストでのやり取りのため、人間が手作業でサーバと直接やるとりすることが可能です。HTTPの勉強はもちろんですが、開発時にGoogleChromeなどWebブラウザや便利ライブラリによってブラックボックス化されないため純粋な通信状況を確認する際に役に立ちます。
今回は古来から伝わるtelnetコマンドで行う方法を試してみます。
環境準備
telnetコマンドが入っていない環境が増えてきましたが、yumなどで簡単にインストールすることが可能です。
$ sudo yum install telnet
サーバに接続する
接続先のドメインとポート番号を指定します。サーバによりますが接続したことを表すメッセージが表示されます。
$ telnet google.com 80 Trying 172.217.26.14... Connected to google.com. Escape character is '^]'.
ここまではTelnetの利用方法であってHTTPは関係ありません。この後、接続したサーバに対して何をしたいのか入力していきます。逆に言えばHTTP以外のPOP3やSMTPなど他のプロトコルを利用する場合もここまでの手順は同じです。
ファイルを取得する
GET
ここからHTTPをサーバと話していきます。以下のように入力することでGETメソッドでファイルを取得することができます。
GET /foo.html HTTP/1.0 (改行)
HTTP/1.1はHost
ヘッダの指定が必須です。また1つのサーバ内で複数ドメインの運用が行われている場合(VirtualHost)も同様にHostを指定しないと予期せぬ動作になる場合があります…が、外側からだとそんなんわからんので、自分が管理していないサーバに接続する際は原則付けます。
GET /foo.html HTTP/1.1 Host: example.com (改行)
クエリーを付与することももちろん可能です。URLエンコードをした後の値を入力する必要があります。
GET /foo.html?q1=hello&q2=%91%E5%8D%AA HTTP/1.1 Host: example.com (改行)
POST
POSTメソッドもGETと同様にTelnetからリクエスト可能ですが、いくつか異なる点があります。
POST /bar.php HTTP/1.1 Host: example.com Content-Length: 24 Content-Type: application/x-www-form-urlencoded (改行) q1=hello&q2=%91%E5%8D%AA
- クエリー文字列はリクエストボディ(空行の向こう側)に記述
- クエリー文字列の文字列長(byte)を
Content-Length
で指定 - GETのときと同様に単純なクエリー文字列を送信する場合は、
Content-Type
にapplication/x-www-form-urlencoded
を指定
Webブラウザやサーバによって異なりますが、GETは概ね数kbyte程度の情報を送信することしかできませんが、POSTの場合はWebサーバが許す限り大量のデータを送信することが可能になります。
HEAD
ファイルが存在しているか、または更新日やファイル容量などの情報を知りたいだけの場合にはHEAD
メソッドを利用します。
HEAD /foo.html HTTP/1.1 Host: example.com (改行)
すると以下のようにレスポンスボディは返却されず、レスポンスヘッダのみを確認することができます。
HTTP/1.1 200 OK Date: Sun, 26 May 2019 08:04:05 GMT Server: Apache Content-Type: text/html; charset=UTF-8
ファイルをアップロードする
POSTメソッドではファイルを送信することも可能です。
ちょっとしたテキストファイルを送信する程度ならTelnetでも十分ですが、例えば画像などのバイナリファイルや巨大なデータを送信する場合は素直にcurl
などのツールを利用するか、コードを書くことをおすすめします。
テキスト
以下の例ではupload.php
に対してexample.txt
をアップロードしています。
POST /upload.php HTTP/1.1 Host: example.com Content-Length: 149 Content-Type: multipart/form-data;boundary="cutting-1558859932" (改行) --cutting-1558859932 Content-Disposition: form-data; name="file1"; filename="example.txt" Content-Type: text/plain (改行) Hello --cutting-1558859932--
HTMLで書き直すと以下とほぼ同義です。example.txt
の中身はHello
とだけ記してあります。
<form method="POST" action="http://example.com/upload.php" enctype="multipart/form-data"> <!-- example.txtを選択 --> <input type="file" name="file1"> <button>送信</button> <form>
いわゆるマルチパート形式で送信しています。
- ここではファイルを1つだけ送信していますが、
boundary=
で指定した文字列で区切ることにより、同時に複数のファイルを送信したり、クエリー文字列を同時に送信することも可能です。 - 区切り文字列(この例だと
cutting-1558859932
)は好きな文字列を指定することができます。ランダムに生成された意味のない文字列で構いません。ファイル中のデータと重複すると正常に動作しなくなるのでご注意を。
バイナリ
正直なところバイナリを送信するのであれば何らかのツールを使ってくださいw Telnetで通信中にバイナリを入力するのは困難なため、BASE64にエンコードした上で送信しています。この場合はサーバ側でデコードする必要があります。
POST /upload.php HTTP/1.1 Host: example.com Content-Length: 1410 Content-Type: multipart/form-data;boundary="cutting-1558859932" (改行) --cutting-1558859932 Content-Disposition: form-data; name="file1"; filename="example.png" Content-Type: image/png Content-Transfer-Encoding: base64 (改行) iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAACxiD++AAAA/1BMVEVHcEz/iIX/ iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/ iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/ iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/ iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/ iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/iIX/ iIX/iIX/iIVtanNTAAAAVHRSTlMA/PoD/vv9AQIEBRSa9sfuoGWOC85gDQf4CVv5 wCXm69+pDh0wf9tFcZZjGhccr7GJVT8K3NG8y0RDkZiMpTfM2gbe3RlGSVai6lNd M1r1g4J2dQiuuUmcAAAB3ElEQVQoz41T13LcMAxcUixq133dvffee++JnQT//y0B dbJO8uUhGM2I2F2RWBAC/jv8ILCDlQ0C/x+8YxLct2n6jZ9fXl0Hcxbrq8vz3xU+ FshQZRe+j90KLxeKCg9LZKSk/QbQ2CcpDS0xOIwAKySJNJ0AJ/ziZIXBvCB2sKBH 4JFfnMSZgE0FAXZIakkvERC9JMsdONwVnZjy8ew+fIPn4c1t9eywAdtobvf22Pxr XH9HaG2I93r8yi3Z6203uei1MdZfRYWaE1/RFRNja9jkEzt0Bi9o48/TxdHRxVMX 7cDDGXW4lE38JOK+3IVc9MM1KeLn+oGT8I5hUiWUlBPc3lh8khLaGC0UfcDe3KaC LerzETFwSpoRF0bTKbgzHd2nLWwcuFru8YukojSUpN+4d9UfbACt2nmzhUutMp4V Sl+i1TyvtdLb93Do+jQMQYeJcTch3Oo2jslQIQwds9mvGfNQdXeVD03VYe8swsXR HRZD2Gyc5kqkigJFpblsqHxMiFGBmMgEFlFlVFCJsiN4MV506XyOD3kut0d9oZQR mkMYpYSkH/kJsOzTFE1UcxskHauVJ6emyzP1+kx5emqyXPMLfNLS7mw4SMLZbnoF +Uj+V5tOMv/JX/hfwdduZlTSLzsAAAAASUVORK5CYII= --cutting-1558859932--
BASE64にエンコードするには、そのままですがbase64
コマンドを利用すると手っ取り早いですね。以下では64byte毎に改行を入れています。
$ base64 -b 64 example.png
Cookie
サーバ側からWebブラウザなどのクライアントに情報を保存する機能をCookieと呼びます。Webブラウザは次回以降同一のドメインへアクセスする際にはこのCookieを毎回付与してアクセスしてくれます。TelnetからもこのCookieの値を付与した状態でリクエストすることが可能です。
GET /foo.html HTTP/1.1 Host: example.com Cookie: name1=value; name2=value; (改行)
よく利用されるケースとしては、例えばクライアント・サーバ間でセッション管理を行う場合、サーバ側からユーザー(クライアント)を特定するための一意なIDが送られて来ます。次回以降このIDを付与してアクセスすることで同一のユーザーだとサーバが認識してくれます。
GET /foo.html HTTP/1.1 Host: example.com Cookie: PHPSESSID=5phh94houm4vskst6rqem3rovl; (改行)
ブラウザによって異なりますが、ひとつのドメインに対して発行できるCookieの数やデータ量に制限がありますので、大きなデータを保存することはできません。またユーザーが自由に書き換えや削除を行うことができますので、Cookieには更新されて困るような情報を保存してはなりません。
BASIC認証
手軽に利用できるBASIC認証ですが、Authorization
を付加することによりTelnetから突破することも可能です。
GET /foo.html HTTP/1.1 Host: example.com Authorization: Basic YWRtaW46Zm9vYmFyC (改行)
Authorization
ヘッダの横についている文字列は以下のような意味を持ちます。
- Basic
- BASIC認証を行うことを意味します。
- YWRtaW46Zm9vYmFyC
- 単純にIDとパスワードをBASE64でエンコードした物です。アカウントによって異なりますので都度作成する必要があります。ここではIDは"admin", パスワードは"foobar"としています。
具体的にはIDとパスワードを半角のコロンで結合し、base64コマンドなどでエンコードしてやります。こうやってみるとHTTPなど通信内容が暗号化されていない場合はパスワードが漏れ放題なのがわかりますね。BASIC認証は本番サービスや外部に漏れると損害が発生するようなケースで利用するのは避けた方が良いでしょう。
$ echo -n 'id:password' | base64
- echoに
-n
オプションを指定しないと、改行コードもエンコードされます
なおBASIC認証はログイン/ログアウトのような概念はありません。GoogleChromeのようなWebブラウザでは一度正しいアカウント情報を入力するとしばらくの間(ブラウザを終了させるまで)、自動的に認証を突破してくれますが、Telnetのようにすべて手動で行う場合には毎回Authorizationヘッダを付与し認証する必要があります。
リクエストヘッダ
ブラウザの種類 - User-Agent
ブラウザの種類によってサーバ側で処理を変更したい場合がありますよね。そんなときにはUser-Agent
ヘッダを指定します。
GET /foo.html HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36 (改行)
上記はmacOS版GoogleChromeのUserAgent(以降UA)です。ここからおわかりの通りUAはクライアントが任意で指定する項目です。いくらでも虚偽の内容を指定することができますので、信頼性は低いというのが一般的なエンジニアの認識です。
参照元 - Referer
どこから当該ページにアクセスしてきたか参照元を指定するにはReferer
ヘッダを用います。
GET /foo.html HTTP/1.1 Host: example.com Referer: http://example.com/from-page (改行)
こちらもUAと同様に虚偽の内容を指定することができますので信頼性は非常に低いデータです。アクセス解析などで用いる分には構いませんが、参照元によって処理を切り分けるなどクリティカルなケースの場合には別の手段を利用するのが安全です。
エンコーディング - Accept-Encoding
WebブラウザやクライアントがGZipなどの圧縮方式に対応している場合、Webサーバは生データをGZipなどに圧縮して送信することが可能です。サーバもクライアントも通常よりCPUリソースを消費することになりますが、特にテキストファイルなどは通信量がかなり削減されるため料金はもちろん、ユーザーの体感速度の向上も期待できます。
通常、クライアントから「自分はこのエンコーディングに対応している」とWebサーバに対し宣言をすることになります。
GET /foo.html HTTP/1.1 Host: example.com Accept-Encoding: gzip
gzipの部分には次のエンコーディングを指定することが可能です。
- gzip
- compress
- deflate
- br
- identity
- *
アスタリスク(*)はすべてOK的な意味になり、大抵のブラウザでのデフォルト値です。また複数のエンコーディングを同時に指定することも可能です。
言語 - Accept-Language
ブラウザを利用する方の母語に合わせ、ページ内で表示される言語を自動的に切り替えるといった処理を行う場合などに利用します。
GET /foo.html HTTP/1.1 Host: example.com Accept-Language: ja-JP
複数の言語を同時に指定することも可能です。