В чем разница между ResponseWriter.Писать и Ио.WriteString?

Я видел три способа написания контента для ответа HTTP:

func Handler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "blabla.n")
}

и:

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("blablan"))
}

и здесь:

fmt.Fprintf(w, "blabla")

в чем разница между ними? Какой из них предпочтительнее использовать?

2 ответов


io.Writer

выходной поток представляет цель, в которую можно записать последовательность байтов. В Go это захвачено генералом io.Writer интерфейс:

type Writer interface {
    Write(p []byte) (n int, err error)
}

все, что имеет этот один Write() метод может использоваться в качестве вывода, например файл на диске (os.File), сетевое подключение (net.Conn) или буфер в памяти (bytes.Buffer).

в http.ResponseWriter, который используется для настройки HTTP-ответа и отправки данных клиенту, также является таким io.Writer, данные, которые вы хотите отправить (тело ответа) собирается путем вызова (не обязательно только один раз) ResponseWriter.Write() (который должен реализовать общее io.Writer). Это единственная гарантия, которую вы имеете о реализации http.ResponseWriter интерфейс.

WriteString()

теперь WriteString(). Часто мы хотим написать текстовые данные в io.Writer. Да, мы можем сделать это, просто преобразовав string до []byte, например,

w.Write([]byte("Hello"))

, который работает, как ожидалось. Однако это очень частая операция, и поэтому для этого есть" общепринятый " метод, захваченный io.stringWriter интерфейс неэкспортируется:

type stringWriter interface {
    WriteString(s string) (n int, err error)
}

этот метод дает возможность написать string значение вместо []byte. Так что если что-то (что реализует io.Writer) реализует этот метод, вы можете просто пройти string значения без []byte преобразования. это кажется незначительным упрощением кода, но это больше, чем это. преобразование string to []byte должен сделать копию string содержание (потому что string значения неизменяемы в Go), поэтому есть некоторые накладные расходы, которые становятся заметными, если string "больше" и/или вы должны сделать это много раз.

в зависимости от характера и детали реализации io.Writer, возможно, что можно написать содержание string без преобразования его в []byte и таким образом избежать указанных накладных.

в качестве примера, если io.Writer - это то, что записывается в буфер памяти (bytes.Buffer является таким примером), он может использовать встроенный copy() функция:

встроенная функция копирования копирует элементы из исходного среза в целевой срез. (в частном случае он также будет копировать байты из строки в кусочек байта.)

на copy() может использоваться для копирования содержимого (байтов)string на []byte без преобразования string to []byte, например:

buf := make([]byte, 100)
copy(buf, "Hello")

теперь есть функция "утилита"io.WriteString() что пишет string на io.Writer. Но он делает это, сначала проверяя, прошел ли (динамический тип)io.Writer есть WriteString() метод, и если да, то это будет использоваться (чья реализация, вероятно более эффективный.) Если прошло io.Writer не имеет такого метода, тогда генерал преобразовать-в-байт-ломтик-и-писать-это-то сторону метод будет использоваться как"запасной вариант".

вы можете подумать, что это WriteString() будет преобладать только в случае буферов в памяти, но это не так. Ответы веб-запросов также часто буферизуются (с использованием буфера в памяти), поэтому он может повысить производительность в случае http.ResponseWriter тоже. И если вы посмотрите на реализацию http.ResponseWriter: это несообщаемый тип http.response (server.go в настоящее время строка #308), которая реализует WriteString() (в настоящее время строка #1212), поэтому это означает улучшение.

в целом, когда вы пишите string значения, рекомендуется использовать io.WriteString() как это может быть более эффективным (быстрее).

fmt.Fprintf()

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

так что используйте fmt.Fprintf() если вы хотите отформатировать string создано простым способом, например:

name := "Bob"
age := 23
fmt.Fprintf(w, "Hi, my name is %s and I'm %d years old.", name, age)

что приведет к следующему string будет написано:

Hi, my name is Bob and I'm 23 years old.

одна вещь, вы не должны забывать: fmt.Fprintf() ждет формат строки, поэтому он будет предварительно обработан и не будет записан как есть на выходе. В качестве быстрого примера:

fmt.Fprintf(w, "100 %%")

этого следовало ожидать "100 %%" было бы написано в выход (с 2 % символов), но только один будет отправлен как в строке формата % является специальным символом и %% только в выходной.

если вы просто хотите написать string С помощью fmt пакета, используйте fmt.Fprint() который не требует формат string:

fmt.Fprint(w, "Hello")

еще одно преимущество использования fmt пакет заключается в том, что вы можете писать значения других типов, а не только strings, например,

fmt.Fprint(w, 23, time.Now())

(конечно, правила преобразования любого значения в string - и серии байтов в конечном итоге-хорошо определен, в doc fmt пакета.)

для "простых" форматированный вывод fmt пакет может быть в порядке. Для сложных выходных документов рассмотрите возможность использования text/template (для общего текста) и html/template (всякий раз, когда вывод HTML).

передача / передача http.ResponseWriter

для полноты следует отметить, что часто контент, который вы хотите отправить в качестве веб-ответа, генерируется "чем-то", поддерживающим" потоковое " результат. Примером может быть ответ JSON, который генерируется из структуры или карты.

в таких случаях часто более эффективно передать / передать ваш http.ResponseWriter что это io.Writer этой что-то если он поддерживает запись результата io.Writer на ходу.

хорошим примером этого является генерация ответов JSON. Конечно, вы можете маршалировать объект в JSON с помощью json.Marshal(), который возвращает вам байтовый срез, который вы можете просто отправить, вызвав ResponseWriter.Write().

тем не менее, это более эффективно, чтобы json пакет знайте, что у вас есть io.Writer, и в конечном итоге вы хотите отправить результат на это. Таким образом, нет необходимости сначала генерировать текст JSON в буфере, который вы просто пишете в ваш ответ, а затем отбросить. Вы можете создать новый json.Encoder по телефону json.NewEncoder() к которому вы можете пройти ваш http.ResponseWriter как io.Writer, а вызов Encoder.Encode() после этого будет непосредственно записывать результат JSON в ваш писатель ответов.

одним из недостатков здесь является то, что при сбое генерации ответа JSON у вас может быть частично отправленный / зафиксированный ответ, который вы не можете вернуть. Если это проблема для вас, вы на самом деле у вас нет выбора, кроме как генерировать ответ в буфере, и если маршалинг удастся, вы можете написать полный ответ сразу.


как вы можете видеть из здесь(ResponseWriter), что это интерфейс с Write([]byte) (int, error) метод.

так io.WriteString и fmt.Fprintf как принимают писатель как 1-й аргумент, который также является оберткой интерфейса Write(p []byte) (n int, err error) метод

type Writer interface {
    Write(p []byte) (n int, err error)
}

поэтому, когда вы вызываете io.WriteString (w,"blah") регистрация здесь

func WriteString(w Writer, s string) (n int, err error) {
  if sw, ok := w.(stringWriter); ok {
      return sw.WriteString(s)
  }
  return w.Write([]byte(s))
}

или fmt.Fprintf (w, "blabla") регистрация здесь

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
   p := newPrinter()
   p.doPrintf(format, a)
   n, err = w.Write(p.buf)
   p.free()
   return
}

вы просто вызов метода Write косвенно при передаче ResponseWriter переменной в обоих методах.

так почему бы не вызвать его напрямую с помощью w.Write([]byte("blabla\n")). Надеюсь, ты получил ответ.

PS: есть также другой способ использовать это, если вы хотите отправить его как ответ JSON.

json.NewEncoder(w).Encode(wrapper) //Encode take interface as arg. wrapper can be wrapper := SuccessResponseWrapper{Success:true, Data:data}