CurlでPROTOCOL_ERROR
皆様こんにちは。tamです。
今回はCurlでWebサーバーの動作チェックを行おうとしたら予想外のエラーになった話をしたいと思います。
Curlとはなんぞや?という方はこちらの記事をご覧下さい。
Curlでファイルをダウンロードする
アップロードされたファイルを正しくダウンロードできるかCurlを使って検証する機会がありました。
実行したコマンドは以下のように非常に単純なものです(URLは架空です)
ubuntu@tam:~$ curl -o hoge.txt https://example.jp/xxx.yyy
結果はというと…
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
プロトコルエラー???
HTTP/2に問題がありそうなエラーメッセージなのでHTTP/1.1を指定してみます。
ubuntu@tam:~$ curl --http1.1 -o hoge.txt https://example.jp/xxx.yyy
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 35037 0 35037 0 0 143k 0 --:--:-- --:--:-- --:--:-- 143k
ダウンロード成功!
少なくともURLに間違いはないようです。
ダウンロードできない原因は?
CurlやApacheが古くHTTP/2非対応という可能性を探ります。
ubuntu@tam:~$ curl -V
curl 7.81.0 ~略~
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets zstd
そこには光り輝く「HTTP2」の文字が!
続いてApacheの設定を確認してみると以下の設定がありました。
LoadModule http2_module modules/mod_http2.so
サーバー、クライアントどちらもHTTP/2対応しているようです。
別の角度から調査
手詰まり感があるので今度はopensslを使って確認してみます。
ubuntu@tam:~$ openssl s_client -connect example.jp:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
~略~
GET / HTTP/2
HTTP/1.1 505 HTTP Version Not Supported
Server: nginx
Date: Thu, 11 Apr 2024 03:23:23 GMT
Content-Type: text/html
Content-Length: 180
Connection: close
「Server: nginx」
アッーーーーーーー!!!
この環境ではNginxがリバースプロキシとして存在していることを完全に忘れていました。
Nginxのログ
xxx.xxx.xxx.xxx - - [11/Apr/2024:12:23:23 +0900] "GET / HTTP/2.0" 200 34152 "-" "curl/7.81.0" "-"
Apacheのログ
xxx.xxx.xxx.xxx 127.0.0.1 - - [11/Apr/2024:12:23:23 +0900] "GET / HTTP/1.0" 200 34152 8969 "-" "curl/7.81.0"
これで以下のような通信になっていることがわかりました。
Curl→(HTTP/2)→Nginx→(HTTP/1.0)→Apache
どこに問題があるのか、通信を順番に追っていきます。
Apacheの設定ではHTTP/2が有効になっているため、HTTP/1.0で通信があった場合は以下のレスポンスヘッダを返します。
Upgrade: h2,h2c
Connection: Upgrade
これはRFC7540によるとMUST NOT(ダメ絶対!!!)とされています。
https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
超意訳すると
- HTTP/2はConnectionヘッダーフィールドを使用しません。
- それを含むメッセージを生成してはいけません(MUST NOT)
- 含まれていたら不正な形式として扱わなければなりません(MUST)
CurlはHTTP/2で通信しており、Nginx(Apache)からありえないレスポンスヘッダが返ってきたということになります。
つまりCurlは仕様通り不正なデータとして扱い、エラー処理を行ったというわけです。
設定を変更して再チャレンジ
原因がわかれば対応は簡単です。
ApacheのHTTP/2を無効にするだけです。
設定変更後、改めてCurlでダウンロードを試してみます。
ubuntu@tam:~$ curl -o hoge.txt https://https://example.jp/xxx.yyy
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 35037 0 35037 0 0 144k 0 --:--:-- --:--:-- --:--:-- 144k
無事、ダウンロード成功しました。
まとめ
今回は私がNginxの存在を完全に失念しており、予想外のエラーが起こりました。
どこまで仕様を守るかによって挙動が異なるようですが、試しに某ブラウザで試したところ、ApacheのHTTP/2が有効な状態でも問題なくダウンロード可能でした。
正常に動作していても実はRFC違反な状態になっていないか念のため確認してみてはいかがでしょうか。