◆ レスポンスの Status-Line すらなくて HTTP Header が途中からで最後には謎の数十バイト
◆ gzip されない 68 byte 以下で発生
  ◆ gzip されると起きないので 69 byte 以上では問題なし
  ◆ 数 KB のファイルだと gzip してないときでも発生しなかった
◆ DocumentRoot がネットワーク経由で mount したディレクトリだと発生
  ◆ ローカルのディレクトリを指定すると問題なし
◆ fedora 31 の httpd のみ
  ◆ 昔の fedora や CentOS では起きてない

この記事の続きです
やっぱり気になったのでもう少し詳しく調べてみました

レスポンスが壊れる条件

レスポンスが壊れる条件を調べてみると 68 byte まででは壊れて 69 byte からは正常でした

ファイル名や拡張子 やファイルの中身を変えても変化はありませんでした
gzip の影響ないのかと 全部同じ文字にしたり 全部違う文字にしたりもしましたが変化なしです

日本語の場合は 22 文字 (66 byte) では発生し 23 文字 (69 byte) では発生しませんでした

謎のバイナリ

また 壊れたレスポンスを受け取ったときにはファイルの末尾に謎の数十バイトがついてきます
この部分のバイナリデータは毎回バラバラで ときどき読める文字が部分的に含まれていました

ot-libs-5.3.0-30.fc31.i686
on this server.</p>
</body></html>

dnf で表示されそうなものや 404 とかのエラーでのレスポンスのテキストっぽいです
中途半端なのはそれ以前は読める形式になっていなかったからです

C などの低レイヤーの言語を使ってるとときどきあります
確保した文字列以上の長さを読み取ったせいで未初期化で前のデータが残った部分にアクセスしたような感じです

生のレスポンス

curl で

curl: (1) Received HTTP/0.9 when not allowed

になるのも気になりますし 生のレスポンスを見てみようと思います
とは言ったものの ほとんどの http client ってパースした結果を受け取るんですよね
みたいのは生のデータなのに

tcpdump して見るのは面倒な上 扱いづらいですし ということで TCP 通信でリクエストを直接送るものを準備して リクエストを送ってみることにしました

const net = require("net")
const client = new net.Socket()

const host = "fedora-server"
const port = 80
const path = "/1.html"
const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"

const req = [
`GET ${path} HTTP/1.1`,
`Host: ${host}`,
`User-Agent: ${ua}`,
"Accept: */*",
"Accept-Encoding: gzip, deflate",
].join("\r\n") + "\r\n\r\n"

client.on("data", buf => {
console.log("Response: \n" + buf.toString())
console.log("Hex: \n" + Array.from(buf).map(e => e.toString(16).padStart(2, "0")).join(" "))
})

client.on("error", err => {
console.log("Error: \n", err)
})

client.on("close", () => {
console.log("CLOSED")
})

client.on("connect", () => {
console.log("CONNECTED")
console.log("Request: \n" + req)
client.write(Buffer.from(req))
})

client.connect({ port, host })

Node.js だと割と簡単に作れます

69 byte 以上の場合

69 byte 以上のファイルのパスへリクエストを送ったときの結果はこうです

Response:
HTTP/1.1 200 OK
Date: Wed, 01 Apr 2020 13:56:15 GMT
Server: Apache/2.4.41 (Fedora) OpenSSL/1.1.1d
Last-Modified: Wed, 01 Apr 2020 13:56:13 GMT
ETag: "45-5a2316876c589-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 24
Content-Type: text/html; charset=UTF-8

body 部分は省略してます
Header は正常です

68 byte 以下の場合

次に 68 byte 以下のファイルのパスへリクエストを送信します
結果はこうでした

Response:
ed, 01 Apr 2020 13:56:22 GMT
ETag: "44-5a2316900766b"
Accept-Ranges: bytes
Content-Length: 68
Content-Type: text/html; charset=UTF-8

1 行目の status line で壊れています
200 OK くらいは届いてるのかと思いましたが それもなかったです

curl でなぜか HTTP/0.9 とおかしなエラーが出ていた理由もわかります

gzip してると大丈夫

壊れたレスポンスのヘッダーを見ていると

Content-Encoding: gzip

がついていません

gzip されてない場合におかしいのかもしれません

今は リクエストで

Accept-Encoding: gzip, deflate

を送っていたので外してみると 69 byte 以上でも壊れたレスポンスになりました

gzip していた場合はなぜか大丈夫だったみたいです
それなら常に gzip すればとりあえず対処できるのですが なぜ 68 byte と 69 byte なのかがわかりません
小さすぎると gzip する効果がほとんどないので 閾値があるのはおかしくないと思うのですが 1 KB みたいなキリが良いところでもないですしググってもそれらしい情報が見当たりませんでした

設定

設定を調べてみると gzip 自体がデフォルトでは有効ではないみたいです
自分でどこかからコピペしてきて色々追加した部分に含まれていました
ただそこでも有効にするくらいで 68 byte とかは設定していません

拾い物をそのままコピペしたものなので いったん自分で追加した設定ファイルを全部 .conf 以外の拡張子にリネームしました
.conf しかロード対象じゃないのでデフォルト設定と追加パッケージのモジュールをロードしただけの状態になるはずです

この状態で /var/www/html に 68 byte 以下と 69 byte 以上のファイルを置いてアクセスします
これだとどちらも正常にアクセスできました

gzip は有効にしていないので 69 byte の方も gzip されていません

原因調べる

設定ファイルを外すと問題ないあたり 設定が原因となってそうです
除外した設定で gzip 関連のものは

<IfModule mod_deflate.c>

のブロックでコピペしてきたものです
これだけ /etc/httpd/conf/http.conf の最後に追加してアクセスしてみました

やはり 68 byte と 69 byte が分かれ目になっていて 68 byte の方は gzip なしで 69 byte の方は gzip ありでした
しかし 68 byte の方もレスポンスは正常です

残る設定は virtualhost の指定くらいです
関係なさそうなのですが 入れてみると再現するようになりました

virtualhost の設定は余計なものを外してここまで減らしたのに これを入れるだけで再現します

<VirtualHost *:80>
DocumentRoot /path/to/docroot
ServerName fedora-server
<Directory /path/to/docroot>
Require all granted
</Directory>
</VirtualHost>

この状態で gzip のオプションを外すと 69 byte でもレスポンスが壊れました
数 KB あると gzip なしでも発生していません

ネットワーク経由がダメ?

今設定しているフォルダは Windows の共有フォルダを cifs mount しているものです
これが原因の可能性がありそうなのでローカルの別のところに変えてみました

すると発生しなくなりました
小さいファイルでも正常なレスポンスです

ネットワーク経由がダメそうです

ネットワーク経由と言っても cat コマンドなどで読み込む分にはファイルは壊れず正常です
apache がファイルを読み込むときにはネットワーク経由もローカルも大差ないような気がします


一応別環境でも試してみました
少し古めの fedora と CentOS です
fedora は 27 で Apache/2.4.34 (Fedora) です
CentOS は 7 でもっと古いので一応くらいの気持ちです

これらで Windows の共有フォルダを mount し virtualhost で document root に設定します
そして 68 byte 以下のファイルにアクセスしてみると……
特に問題ありませんでした
gzip される条件が 68 byte / 69 byte の境目なのも一緒でした

fedora 31 のバージョンでネットワーク経由のみ発生するようです



続き