Поддерживает ли HTTP/1.1 двустороннюю связь и как это реализуется
Что проверяют
Понимаете ли вы разницу между моделью запрос-ответ и полным дуплексом на уровне соединения, и знаете ли, чем это закрывают в Go. Вопрос обычно ведёт к WebSocket, SSE и gRPC, поэтому голое «нет, не поддерживает» ответом не считается.
Как отвечать
HTTP/1.1 построен на схеме «запрос, затем ответ» в рамках одного соединения, хотя сам TCP полнодуплексный. Спецификация не запрещает серверу начать отдавать тело ответа, пока он ещё читает тело запроса, но полагаться на это нельзя: клиенты, прокси и балансировщики обычно ждут завершения запроса. Поэтому двусторонность строят обходными путями:
- долгий опрос: клиент отправляет запрос, сервер держит его открытым до появления данных и отвечает уже событием;
- потоковый ответ через chunked-кодировку, в Go это
http.Flusherилиhttp.NewResponseController(w).Flush(); - Server-Sent Events: клиент открывает обычный
GET, сервер дописывает в него события. Связь односторонняя, от сервера к клиенту; - WebSocket: запрос с заголовком
Upgrade, ответ со статусом 101, дальше по тому же TCP-соединению идёт кадровый двунаправленный протокол. В Go под капотом это перехват соединения черезhttp.Hijacker.
Честный дуплекс поверх HTTP появляется в HTTP/2: внутри одного потока обе стороны шлют данные независимо, на этом и построен двунаправленный стриминг в gRPC. В Go серверный обработчик умеет читать тело и писать ответ одновременно, но для HTTP/1 это нужно разрешить явно через EnableFullDuplex у контроллера ответа.
Где спотыкаются
Говорят, что соединение при SSE инициирует сервер. Открывает его всегда клиент, обычным запросом, а сервер просто не закрывает поток. Отсюда и практическое следствие: переподключение после обрыва тоже забота клиента.
Путают дуплекс с мультиплексированием. Несколько параллельных соединений или конвейеризация запросов не дают серверу возможности заговорить первым: без запроса он не отправит ничего.
Забывают про промежуточные узлы. Потоковый ответ буферизуется обратным прокси, соединение режется по таймауту простоя, и работавший локально стрим рассыпается в проде. Для долгих потоков нужны периодические пустые события и отключённая буферизация.