web-dev-qa-db-ja.com

複数のresponse.WriteHeaderは本当に簡単な例で呼び出しますか?

Goで名前空間を学習するために使用している最も基本的なnet/httpプログラムがあります。

_package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(r.URL)
        go HandleIndex(w, r)
    })

    fmt.Println("Starting Server...")
    log.Fatal(http.ListenAndServe(":5678", nil))
}

func HandleIndex(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Write([]byte("Hello, World!"))
}
_

プログラムを実行してChromeで_localhost:5678_に接続すると、コンソールで次のように表示されます。

_Starting Server...
/
2015/01/15 13:41:29 http: multiple response.WriteHeader calls
/favicon.ico
2015/01/15 13:41:29 http: multiple response.WriteHeader calls
_

しかし、それがどのように可能かはわかりません。 URLを出力し、新しいゴルーチンを起動し、ヘッダーを1回書き込み、_Hello, World!_の静的ボディを指定します。2つのことが行われているようです。背後で何かが別のヘッダーを書き込んでいるか、同じリクエストに対してHandleIndexが2回呼び出されています。複数のヘッダーの書き込みを停止するにはどうすればよいですか?

編集:goを削除してゴルーチンの代わりに関数呼び出しを行うと、問題が発生しないため、go HandleIndex(w, r)行と関係があるようです。ブラウザがデータを取得します。ルーチンであるため、複数のWriteHeaderエラーが発生し、ブラウザに「Hello World」と表示されません。なぜこれをゴルーチンにして壊すのですか?

28
Corey Ogburn

着信リクエストのハンドラーとして登録する匿名関数を見てください。

_func(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.URL)
    go HandleIndex(w, r)
}
_

URLを(標準出力に)出力し、新しいゴルーチンでHandleIndex()を呼び出して実行を継続します。

Writeへの最初の呼び出しの前に応答ステータスを設定しないハンドラー関数がある場合、Goは応答ステータスを自動的に200(HTTP OK)に設定します。ハンドラー関数が応答に何も書き込まない(および応答ステータスを設定せずに正常に完了する)場合、それは要求の正常な処理として扱われ、応答ステータス200が返されます。あなたの匿名関数はそれを設定せず、応答にも何も書き込みません。 Goはそれを実行します。応答ステータスを200 HTTP OKに設定します。

各リクエストの処理は独自のゴルーチンで実行されることに注意してください。

したがって、新しいゴルーチンでHandleIndexを呼び出すと、元の匿名関数は続行します。終了し、応答ヘッダーが設定されます-一方(同時に)開始された新しいゴルーチンも応答ヘッダーを設定します-したがって_"multiple response.WriteHeader calls"_エラー。

_"go"_を削除すると、HandleIndex関数はハンドラ関数が戻る前に同じゴルーチンで応答ヘッダーを設定し、「net/http」はこれを認識して設定しようとしません。応答ヘッダーが再び表示されるため、発生したエラーは発生しません。

53
icza

あなたはすでにあなたの問題に対処する正しい答えを受け取っています。私は一般的なケースに関するいくつかの情報を提供します(そのようなエラーはしばしば現れます)。

documentation から、WriteHeaderがhttpステータスコードを送信し、複数のステータスコードを送信できないことがわかります。 Write何かをすると、200ステータスコードを送信してから物事を書くことに相当します。

したがって、w.WriteHeaderを明示的に複数回使用するか、w.Writeの前にw.WriteHeaderを使用すると、表示されるメッセージが表示されます。

3
Salvador Dali

根本的な原因は、WriteHeaderを複数回呼び出したことです。ソースコードから

func (w *response) WriteHeader(code int) {
    if w.conn.hijacked() {
        w.conn.server.logf("http: response.WriteHeader on hijacked connection")
        return
    }
    if w.wroteHeader {
        w.conn.server.logf("http: multiple response.WriteHeader calls")
        return
    }
    w.wroteHeader = true
    w.status = code

    if w.calledHeader && w.cw.header == nil {
        w.cw.header = w.handlerHeader.clone()
    }

    if cl := w.handlerHeader.get("Content-Length"); cl != "" {
        v, err := strconv.ParseInt(cl, 10, 64)
        if err == nil && v >= 0 {
            w.contentLength = v
        } else {
            w.conn.server.logf("http: invalid Content-Length of %q", cl)
            w.handlerHeader.Del("Content-Length")
        }
    }
}

そのため、一度書いたとき、変数writtenHeaderはtrueになり、その後ヘッダーを再度書いたのですが、効果がなく、「http:multiple respnse.WriteHeader calls」という警告を出しました。実際には、関数WriteもWriteHeaderを呼び出すため、関数Writeの後に関数WriteHeaderを置くと、そのエラーも発生し、後のWriteHeaderは機能しません。

あなたの場合、handleindexを別のスレッドで実行し、元のコードはすでに戻ります。何もしない場合、WriteHeaderを呼び出して200を設定します。handleindexを実行すると、別のWriteHeaderを呼び出します。 :複数のresponse.WriteHeader呼び出し」が出力されます。

1
user7402259

ドキュメントから:

// WriteHeader sends an HTTP response header with status code. 
// If WriteHeader is not called explicitly, the first call to Write  
// will trigger an implicit WriteHeader(http.StatusOK).

あなたのケースで起こっているのは、あなたがgo HandleIndexハンドラーから。最初のハンドラーが終了します。標準のWriteHeaderはResponseWriterに書き込みます。次に、goルーチンHandleIndexが起動され、ヘッダーの書き込みと書き込みも試行されます。

HandleIndexからgoを削除するだけで機能します。

1
fabrizioM

最新のブラウザは/ favicon.icoに対する追加のリクエストを送信するため、これも/リクエストハンドラで処理されます。

たとえば、curlを使用してサーバーにpingを送信すると、送信されるリクエストは1つだけになります。

 curl localhost:5678

エンドポイントをhttp.HandleFuncに追加できることを確認してください

http.HandleFunc("/Home", func(w http.ResponseWriter, r *http.Request) 
0
abdel

はい、HandleIndex(w, r)の代わりにgo HandleIndex(w, r)を使用すると問題が解決します。すでにわかっていると思います。

その理由は簡単です。複数のリクエストを同時に処理する場合、httpサーバーは複数のゴルーチンを開始し、ハンドラ関数は他のゴルーチンをブロックせずに各ゴルーチンで個別に呼び出されます。実際に必要な場合を除き、ハンドラで独自のゴルーチンを開始する必要はありませんが、それは別のトピックになります。

0
Andy Xu