HTTPメッセージの区切り文字はCRLFと定義されているが、実装によってはLFでも通る

## 事象

example.comとpythonで立てたローカルサーバに対して、それぞれhttpリクエストを送ったところ下のような違いが見られた。

```bash
# example.com
# 正常パターン
$ printf "GET / HTTP/1.1\r\nHost: example.com\r\nAccept: text/html\r\nConnection: close\r\n\r\n" | nc example.com 80
# 異常パターン
# 結果が返ってこず、コマンドが終わらない (\nを\r\nに一部変更したとしても同じ結果)
$ printf "GET / HTTP/1.1\nHost: example.com\nAccept: text/html\nConnection: close\n\n" | nc example.com 80

# pythonで立てた簡易サーバ
$ python3 -m http.server 8000
# 正常パターン
$ printf "GET / HTTP/1.1\r\nHost: example.com\r\nAccept: text/html\r\nConnection: close\r\n\r\n" | nc localhost 8000
# 正常パターン (こちらも正常に結果が返ってくる)
$ printf "GET / HTTP/1.1\nHost: example.com\nAccept: text/html\nConnection: close\n\n" | nc localhost 8000
```

この違いが気になったので調査した。

## RFCを確認

### RFC9112

https://datatracker.ietf.org/doc/html/rfc9112#name-message-format
ここにリクエスト、レスポンスのフォーマットが定義されている
```
HTTP-message   = start-line CRLF
                   *( field-line CRLF )
                   CRLF
                   [ message-body ]
[¶](https://datatracker.ietf.org/doc/html/rfc9112#section-2.1-2)
```

行の区切り文字はCRLFと明記されている。

### RFC2606

https://datatracker.ietf.org/doc/html/rfc2606#section-3
example.comは予約ドメイン。テスト用途として使われる。

example.comはRFCで定められているドメインなので、ルールに厳格なのかもしれない。
つまりRFC9112で定められているようにLF区切りのリクエストは受け付けないのかもしれない。

なお、example.comで立てられているhttpサーバの実装は確認できなかった。
私が見つけることができなかっただけなのか単に非公開になっているだけかは不明。

## Pythonのhttpサーバを確認

Pythonのhttpサーバの実装はGitHubにて確認できた。

下記はリクエストメッセージをパースしている部分。ここで`split()`を使用している。
https://github.com/python/cpython/blob/v3.13.3/Lib/http/server.py#L267
```python
    def parse_request(self):
        """Parse a request (internal).

        The request should be stored in self.raw_requestline; the results
        are in self.command, self.path, self.request_version and
        self.headers.

        Return True for success, False for failure; on failure, any relevant
        error response has already been sent back.

        """
        self.command = None  # set in case of error on the first line
        self.request_version = version = self.default_request_version
        self.close_connection = True
        requestline = str(self.raw_requestline, 'iso-8859-1')
        requestline = requestline.rstrip('\r\n')
        self.requestline = requestline
        words = requestline.split()
        # 省略
```

コマンドで実験してみると`split()`はCRLFでもLFでも分割することを確認できた。
```python
>>> "abc\r\ndef\r\nghi".split()
['abc', 'def', 'ghi']
>>> "abc\ndef\nghi".split()
['abc', 'def', 'ghi']
>>> "abc\ndef\r\nghi".split()
['abc', 'def', 'ghi']
>>> 
```

さらに調べてみると`split()`の実装はここで見られることがわかった。
https://github.com/python/cpython/blob/v3.13.3/Objects/stringlib/split.h#L363
実験通りCRLF, LFどちらにも対応している。

## 結論

HTTPメッセージの区切り文字はRFC9112にてCRLFと定められている。
実際は各httpサーバの実装によって、LFでも処理されるように柔軟さを持っていることがある。

## 調査のきっかけ

「作って学ぶブラウザのしくみ」という書籍がある。
https://direct.gihyo.jp/view/item/000000003560

この書籍では実際にhttpサーバを実装していくのだが、リクエストメッセージを`\n`区切りとして実装している。
https://github.com/d0iasm/sababook/blob/main/ch3/saba/net/wasabi/src/http.rs#L45
```rust
        let mut request = String::from("GET /");
        request.push_str(&path);
        request.push_str(" HTTP/1.1\n");

        // ヘッダの追加
        request.push_str("Host: ");
        request.push_str(&host);
        request.push('\n');
        request.push_str("Accept: text/html\n");
        request.push_str("Connection: close\n");
        request.push('\n');
```

そして本書p90-p95では、実装したhttpサーバを用いてリクエストを送る実験を載せている。

リクエスト先は、冒頭に書いた`example.com`とpythonで立てたローカルサーバ。本書にしたがって実験してみたところ、実際には`example.com`ではコマンドから復帰せずに待ちの状態が続いてしまった。これがきっかけで調査をした。