множественные вызовы response.WriteHeader в действительно простом примере?

У меня есть самая простая программа net/http, которую я использую для изучения пространства имен в Go:

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!"))
}

Когда я запускаю программу и подключаюсь к localhost:5678 в Chrome, я получаю это в консоли:

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-адрес, запускаю новую горутину, пишу заголовок один раз и даю ему статическое тело Hello, World!. Похоже, происходит одно из двух. Либо что-то за кадром пишет другой заголовок, либо как-то дважды вызывается HandleIndex для одного и того же запроса. Что я могу сделать, чтобы перестать писать несколько заголовков?

РЕДАКТИРОВАТЬ: Кажется, это как-то связано со строкой go HandleIndex(w, r), потому что, если я удалю go и просто сделаю это вызовом функции вместо горутины, у меня не возникнет никаких проблем, и браузер получит свои данные. Поскольку это горутина, я получаю множественную ошибку WriteHeader, и браузер не показывает «Hello World». Почему это ломает горутина?


person Corey Ogburn    schedule 15.01.2015    source источник


Ответы (6)


Взгляните на анонимную функцию, которую вы регистрируете в качестве обработчика входящих запросов:

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» узнает об этом и не будет пытаться снова установить заголовок ответа, поэтому ошибка, которую вы опытных не бывает.

person icza    schedule 15.01.2015
comment
Обратите внимание, что обработка каждого запроса выполняется в собственной маршрутизации. Я надеялся, что это так, но я никогда не видел ничего, что говорило бы так или иначе, если это правда. - person Corey Ogburn; 16.01.2015
comment
@CoreyOgburn Цитата из документации http.Serve(): "Serve accepts incoming HTTP connections on the listener l, creating a new service goroutine for each." - person icza; 16.01.2015

Вы уже получили правильный ответ, который решает вашу проблему, я дам некоторую информацию об общем случае (такая ошибка появляется часто).

Из документации видно, что WriteHeader отправляет код состояния http, и вы не можете отправить более 1 кода состояния. Если вы Write что-нибудь, это эквивалентно отправке кода состояния 200, а затем записи.

Таким образом, сообщение, которое вы видите, появляется, если вы используете w.WriteHeader более одного раза явно или используете w.Write перед w.WriteHeader.

person Salvador Dali    schedule 12.06.2016

Из документации:

// 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, которая также пытается записать заголовок и записать.

Просто удалите go из HandleIndex, и все заработает.

person fabrizioM    schedule 15.01.2015

основная причина в том, что вы вызывали 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")
        }
    }
}

поэтому, когда вы написали один раз, переменная writeHeader была бы истинной, затем вы снова написали заголовок, это не будет эффективно и выдаст предупреждение «http: несколько вызовов respnse.WriteHeader». на самом деле функция Write также вызывает WriteHeader, поэтому размещение функции WriteHeader после функции Write также вызывает эту ошибку, а более поздний WriteHeader не работает.

из вашего случая go handleindex запускается в другом потоке и оригинал уже возвращается, если вы ничего не сделаете, он вызовет WriteHeader для установки 200. при запуске handleindex он вызывает другой WriteHeader, в это время writeHeader является истинным, затем сообщение "http : выводится множественный ответ. Вызовы WriteHeader».

person user7402259    schedule 11.01.2017

Да, используйте HandleIndex(w, r) вместо go HandleIndex(w, r), чтобы решить вашу проблему, я думаю, вы уже поняли это.

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

person Andy Xu    schedule 16.01.2015

Потому что современные браузеры отправляют дополнительный запрос для /favicon.ico, который также обрабатывается в вашем обработчике запросов /.

Например, если вы пропингуете свой сервер с помощью curl, вы увидите только один отправленный запрос:

 curl localhost:5678

Чтобы убедиться, что вы можете добавить EndPoint в свой http.HandleFunc

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