◆ HTTP ライブラリ使わず socket を使って HTTP リクエストを送る

前の記事で Apache の生のレスポンスを見るために Node.js で HTTP ライブラリを通さず直接 HTTP のリクエストを送信しました
Node.js だとイベントがベースになるので 1 回リクエストしてそのレスポンスを見たいだけだとあんまり向いていません

今後も使うシーンはありそうなので もっとシンプルに書ける別の言語で書いておこうと思います

言語

どの環境でも実行できることを考えると ほとんどの環境に入れている Node.js なのですが 今回はそれ以外なので .NET と Python です

PHP は高レイヤー向き過ぎてローカルのファイルでもネットのファイルでも URL だけ指定すれば中身受け取れるとか便利さはありますが ネットワークの低レイヤーでソケット通信みたいなことをするイメージがないです
できはするのでしょうけど あんまり楽な印象がないです
それに 個人的にはほぼ PHP 用途は Python に移行してるので入ってない環境が多いです

JVM 系なら .NET でいいやという気もしますし ネイティブ系の言語だと実装が難しくなりそうです

.NET では最近は PowerShell 使うこと増えてるので今回も PowerShell にします
C# にすると可変なオプションを引数で渡すのが面倒なのですよね
かと言って コードを変更すると毎回ビルド発生しますし
PowerShell はスクリプトのコード直接変えて実行しやすいのが良いところです

Python は WSL のおかげで Windows でもほぼ標準のように使えます
最初は PowerShell よりは Python かなと思ってたのですが 参考に urllib の HTTP ライブラリを見ると思ったより長くて複雑でした
それで PowerShell でいいかと思って PowerShell 版を作ったのですが その後によく見ると HTTP リクエスト送るだけなら Python で簡単にできそうだったので Python 版も作りました
複雑なのは HTTPS 対応とかいろいろやってるからですね

PowerShell

PowerShell 版のコードです
PowerShell だと $host が読み取り専用として最初から存在するので host 名の変数は $reqhost にしました

$reqhost = "feodra-server"
$port = 80
$path = "/"

$req = "GET $path HTTP/1.1`r`n" +
"Host: $reqhost`r`n" +
"Connection: close`r`n" +
"`r`n";

$socket = New-Object System.Net.Sockets.Socket @([System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
$socket.Connect($reqhost, $port)

if (!$socket.Connected)
{
echo "Failed to connect server"
exit
}

$bytes = [System.Text.Encoding]::ASCII.GetBytes($req)
$socket.Send($bytes) > $null

$receive_bytes = New-Object Byte[] 256
$response = ""

while (($len = $socket.Receive($receive_bytes)) -gt 0)
{
$response += [System.Text.Encoding]::UTF8.GetString($receive_bytes, 0, $len)
}

echo $response

受信方法が一気に全部受け取れず 事前に用意した byte[] に何回かにわけて受け取らないといけないのが面倒なところです

HTTP Header で使う CRLF が PowerShell だとエスケープ文字が違うので `r`n になります
パット見ではわかりづらいのですが \ がエスケープじゃないのは他言語のコードを埋め込んだりするときには便利かもしれません

Python

Python 版のコードです

import socket

host = "fedora-server"
port = 80
path = "/"
req = (
f"GET {path} HTTP/1.1\r\n" +
f"Host: {host}\r\n" +
"Connection: close\r\n" +
"\r\n"
)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.sendall(req.encode())

result = ""
while True:
bytes = s.recv(256)
result += bytes.decode()
if len(bytes) == 0:
break

print(result)

PowerShell よりは少しシンプルになりました
ただこっちでも受信が一度にできないのは不便です
recvall 関数とかあればいいのですけど