[Linux] コマンドラインでHTTP通信を行う - Telnet編

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-Typeapplication/x-www-form-urlencodedを指定

Webブラウザやサーバによって異なりますが、GETは概ね数kbyte程度の情報を送信することしかできませんが、POSTの場合はWebサーバが許す限り大量のデータを送信することが可能になります。

ファイルが存在しているか、または更新日やファイル容量などの情報を知りたいだけの場合には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

サーバ側から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

複数の言語を同時に指定することも可能です。

参考ページ