Ультра простой сервер HTTP-сокетов, написанный на PHP, ведет себя неожиданно

tldr;

  1. очень минимальный сервер сокетов потока в PHP
  2. действует странно, поскольку иногда он успешно обслуживает HTTP запрос и иногда терпит неудачу в рамках одного и того же процесса
  3. действует странно в разных браузерах – почти каждый раз сбой в Chrome и никогда в IE11

код:

 $server = stream_socket_server("tcp://0.0.0.0:4444", $errno, $errorMessage); if ($server === false) throw new UnexpectedValueException("Could not bind to socket: $errorMessage"); $e = "\r\n"; $headers = array( "HTTP/1.1 200 OK", "Date: " . date('D') . ', ' . date('m') . ' ' . date('M') . ' ' . date('Y') . ' ' . date('H:i:s') . ' GMT' , 'Server: MySpeedy', 'Connection: close', 'Content-Type: text/plain', 'Content-Length: 2' ); $headers = implode($e, $headers) . $e . $e .'ok'; for (;;) { $client = stream_socket_accept($server); if ($client) { echo 'Connection accepted from '.stream_socket_get_name($client, false) . $e; fwrite($client, $headers); fclose($client); } } 

дает мне этот ответ http (результаты telnet):

 HTTP/1.1 200 OK Date: Fri, 11 Nov 2015 20:09:02 GMT Server: MySpeedy Connection: close Content-Type: text/plain Content-Length: 2 ok 

И это приводит меня к этим результатам:

  • ERR_CONNECTION_RESET в Chrome, почти каждый раз (возможно, 1 из 20-30 запросов получает ожидаемый ответ)
  • The connection was reset в Firefox, примерно 1 из 2-3 запросов
  • Правильный, ожидаемый ответ в Internet Explorer 11 каждый раз (yay, IE лучше всего во что-то).

Что я делаю не так? Является ли это до заголовков http (я не мог сказать, если я отформатировал их неправильно) или сокет или …?

Вы не читаете HTTP-запрос от клиента, а просто отправляете ответ и закрываете соединение. Но закрытие сокета, пока есть еще данные для чтения, приведет к возврату соединения обратно клиенту, и это то, что вы увидите в Chrome с помощью ERR_CONNECTION_RESET. Другие браузеры могут вести себя по-разному, и это также проблема синхронизации, если браузер может отобразить ответ перед обработкой сброса.

Чтобы исправить это, сначала прочитайте полный запрос от клиента, прежде чем закрыть сокет.