web-dev-qa-db-ja.com

ResponseWriter.Writeとio.WriteStringの違いは何ですか?

HTTP応答にコンテンツを書き込む3つの方法を見てきました。

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

そして:

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

またあります:

fmt.Fprintf(w, "blabla")

それらの違いは何ですか?どちらを使用するのが好ましいですか?

27
laike9m

io.Writer

出力ストリームは、バイトシーケンスを書き込むことができるターゲットを表します。 Goでは、これは一般的な io.Writer インターフェイスによってキャプチャされます。

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

この単一のWrite()メソッドを持つすべてのものを出力として使用できます。たとえば、ディスク上のファイル( os.File )、ネットワーク接続( net.Conn )またはメモリ内バッファ( bytes.Buffer )。

HTTP応答を構成し、クライアントにデータを送信するために使用される http.ResponseWriter もそのようなio.Writerであり、送信するデータ(応答本文)が組み立てられます(必ずしも一度だけではなく)ResponseWriter.Write()(一般的なio.Writerを実装するため)を呼び出します。これは、http.ResponseWriterインターフェースの実装について(ボディの送信に関して)唯一の保証です。

WriteString()

次に、WriteString()に進みます。多くの場合、テキストデータをio.Writerに書き込みます。はい、それはstring[]byteに変換するだけで可能です。

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

期待どおりに動作します。ただし、これは非常に頻繁な操作であるため、 io.StringWriter インターフェイス( Go 1.11 、それが輸出されなかった前に):

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

このメソッドにより、[]byteの代わりにstring値を書き込むことができます。そのため、(io.Writerも実装する)何かがこのメソッドを実装する場合、[]byte変換なしでstring値を渡すことができます。 これはコードの単純な単純化のようですが、それ以上です。string[]byteに変換するには、stringコンテンツのコピーを作成する必要があります(stringの値はGoでは不変です。詳細についてはこちらをご覧ください: golang:[] byte(string)vs [] byte(* string)stringが「より大きい」場合、および/またはこれを何度も行う必要がある場合に顕著になります。

io.Writerの性質と実装の詳細に応じて、stringの内容を[]byteに変換せずに記述して、上記のオーバーヘッドを回避できる場合があります。

例として、io.Writerがメモリ内バッファーに書き込むものである場合(bytes.Bufferはそのような例です)、組み込みの copy() 関数を使用できます。

Copy組み込み関数は、ソーススライスからターゲットスライスに要素をコピーします。 (特別な場合として、文字列からバイトをスライスにコピーします。)

copy()は、string[]byteに変換せずに、stringの内容(バイト)を[]byteにコピーするために使用できます。例:

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

現在、「ユーティリティ」関数 io.WriteString() があり、stringio.Writerに書き込みます。しかし、最初に、渡されたio.WriterWriteString()メソッドを持っているかどうかをチェックすることでこれを行います。もしそうなら、それが使用されるでしょう(実装はおそらくより効率的です)。渡されたio.Writerにそのようなメソッドがない場合、一般的なconvert-to-byte-slice-and-write-thatメソッドが「フォールバック」として使用されます。

このWriteString()はメモリ内バッファの場合にのみ有効であると思われるかもしれませんが、そうではありません。 Web要求の応答もしばしば(メモリ内バッファを使用して)バッファされるため、http.ResponseWriterの場合もパフォーマンスが向上する可能性があります。そして、http.ResponseWriterの実装を見ると、それはWriteString()(現在の行#1212)を実装する、エクスポートされていない型http.responseserver.go 現在行#308)です。改善。

全体として、string値を記述するときは常に、io.WriteString()を使用することをお勧めします。より効率的(高速)になる可能性があるためです。

fmt.Fprintf()

これは、多少パフォーマンスが低下する代わりに、書き込みたいデータにさらに多くのフォーマットを追加する便利で簡単な方法と見なすべきです。

したがって、簡単な方法でフォーマットされたstringを作成する場合は、 fmt.Fprintf() を使用します。例:

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.

忘れてはならないことの1つは、fmt.Fprintf()にはformat stringが必要であるため、前処理され、そのまま出力に書き込まれないことです。簡単な例として:

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

"100 %%"が(2文字の%文字で)出力に書き込まれることを期待しますが、フォーマット文字列%は特殊文字であり、%%は出力に1つの%のみを送信するように送信されます。

stringパッケージを使用してfmtを記述したい場合は、フォーマットstringを必要としない fmt.Fprint() を使用します。

fmt.Fprint(w, "Hello")

fmt パッケージを使用するもう1つの利点は、stringsだけでなく、他のタイプの値も書き込むことができることです。

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

(もちろん、任意の値をstringに、そして最終的に一連のバイトに変換するルールは、fmtパッケージのドキュメントで明確に定義されています。)

「単純な」形式の出力の場合、fmtパッケージは問題ない場合があります。複雑な出力ドキュメントの場合、 text/template (一般的なテキストの場合)および html/template (出力がHTMLの場合)の使用を検討してください。 )。

http.ResponseWriterの受け渡し

完全を期すために、Webレスポンスとして送信するコンテンツは、結果の「ストリーミング」をサポートする「何か」によって生成されることが多いことに言及する必要があります。例は、構造体またはマップから生成されるJSON応答です。

そのような場合、http.ResponseWriterであるio.Writerをこれに渡すか、渡すのがより効率的であることがよくあります-somethingその場でio.Writerへの結果の書き込みをサポートしている場合。

これの良い例は、JSON応答の生成です。もちろん、 json.Marshal() を使用してオブジェクトをJSONにマーシャリングできます。これにより、バイトスライスが返され、ResponseWriter.Write()を呼び出すだけで送信できます。

ただし、jsonパッケージにio.Writerがあり、最終的には結果を送信したいことを知らせる方が効率的です。この方法では、最初にバッファーにJSONテキストを生成する必要はありません。これを応答に書き込んでから破棄します。新しい json.Encoder を作成するには、http.ResponseWriterio.Writerとして渡すことができる json.NewEncoder() を呼び出して、呼び出します。 Encoder.Encode() その後、JSONの結果が直接応答ライターに書き込まれます。

ここでの短所の1つは、JSON応答の生成に失敗した場合、部分的に送信/コミットされた応答が返される可能性があることです。これが問題になる場合は、バッファに応答を生成する以外の選択肢はありません。マーシャリングが成功した場合は、すぐに完全な応答を作成できます。

54
icza

here(ResponseWriter) からわかるように、これはWrite([]byte) (int, error)メソッドとのインターフェースです。

したがって、_io.WriteString_と_fmt.Fprintf_では、両方とも WriterWrite(p []byte) (n int, err error)メソッドをラップするインターフェイスでもある第1引数として取ります

_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
}
_

両方のメソッドでResponseWriter変数を渡すため、間接的にWriteメソッドを呼び出すだけです。

したがって、w.Write([]byte("blabla\n"))を使用して直接呼び出してはいけません。答えが得られれば幸いです。

PS:JSON応答として送信する場合は、それを使用する別の方法もあります。

_json.NewEncoder(w).Encode(wrapper)
//Encode take interface as an argument. Wrapper can be:
//wrapper := SuccessResponseWrapper{Success:true, Data:data}
_
6
hitesh_noty