Ручная промывка golang http.ResponseWriter

Первоначально опубликовано на blog.simon-frey.eu (подсветка синтаксиса кода там лучше: D)

В недавнем проекте я хотел начать рендеринг HTML-страницы, даже если сервер все еще работал над длительной задачей. (Для отображения экрана загрузки https://unshort.link без использования JavaScript)

Мой код выглядел примерно так:

func h(rw http.ResponseWriter, req *http.Request) {       io.Copy(rw,loadingHTMLByteReader) 
tResult = performLongRunningTask() 
rw.Write(tResult) 
}

loadingHTMLByteReader - это HTML-код, который я уже хотел отобразить, даже если сервер еще не может записать все результаты.

Но, к сожалению, это не дало ожидаемого результата. Страница по-прежнему была полностью отрисована сразу после завершения полного процесса.
Очевидно, go буферизует там писатель ответов до тех пор, пока обработчик не вернется или буфер (по умолчанию 4 КБ) не будет заполнен.

Буфер определяется через http.Transport:

type Transport struct { 
... 
// WriteBufferSize specifies the size of the write buffer used 
// when writing to the transport. 
// If zero, a default (currently 4KB) is used. 
WriteBufferSize int 
... 
}

Итак, есть два варианта достижения желаемого результата:

  1. установите WriteBufferSize на что-то очень маленькое
  2. ручная очистка буфера http.ResponseWriter

Вариант 1, очевидно, является неудовлетворительным решением, поскольку я не смог бы точно контролировать, что полный loadingHTMLByteReader будет отправлен, если он перекрывает границы буфера.

Например. loadingHTMLByteReader имеет размер 3 КБ, если размер буфера установлен на 2 КБ, последние 2 КБ не будут записаны до тех пор, пока буфер не будет заполнен дополнительными данными или не будет возвращен обработчик. Итак, я выбрал вариант 2

Как сбросить http.ResponseWriter

Изучая сеть, я нашел следующее решение, как вручную промыть http.ResponseWriter:

if f, ok := rw.(http.Flusher); ok { 
f.Flush() 
}

Моя проблема решена и сайт работает как задумано:

func h(rw http.ResponseWriter, req *http.Request) { io.Copy(rw,loadingHTMLByteReader) 
if f, ok := rw.(http.Flusher); ok { 
f.Flush() 
} 
tResult = performLongRunningTask() rw.Write(tResult) 
}

Важные заметки

Http.ResponseWriter не всегда реализует интерфейс Flusher.

Как указано в godoc, ResponseWriter не всегда реализует интерфейс Flusher, особенно когда вы используете оболочки для ResponseWriter.
Важно всегда проверять, работает ли утверждение типа, и только затем использовать Flusher для предотвращения паники сервера в крайние случаи

В сети есть другая буферизация

Очистка модуля записи ответов контролирует только буферизацию уровня приложения. У вас нет контроля над тем, что делают операционная система, сеть и клиент. Все эти объекты могут сами вводить дополнительное кэширование.
Ручную промывку следует использовать только для повышения производительности и наличия полезных функций. Не думайте, что вы можете использовать его для какого-либо критического поведения процесса, это только хорошо, чтобы иметь.

Источники:
https://stackoverflow.com/questions/19292113/not-buffered-http-responsewritter-in-golang

Первоначально опубликовано на https://blog.simon-frey.eu 15 января 2020 г.