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

EDIT: похоже, что-то связано с линией go HandleIndex(w, r), потому что если я удалю go и просто сделаю это вызовом функции вместо goroutine, я не получу никаких проблем, и браузер получит данные, Поскольку он является goroutine, я получаю множественную ошибку WriteHeader, и браузер не показывает "Hello World". Почему это нарушает его голотин?

Ответы

Ответ 1

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

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

Он печатает URL (на стандартный вывод), затем вызывает HandleIndex() в новом goroutine и продолжает выполнение.

Если у вас есть функция обработчика, в которой вы не задали статус ответа до первого вызова Write, Go автоматически установит статус ответа на 200 (HTTP OK). Если функция обработчика ничего не записывает в ответ (и не устанавливает статус ответа и завершается нормально), это также рассматривается как успешная обработка запроса, и ответный статус 200 будет отправлен обратно. Ваша анонимная функция не устанавливает его, он даже не пишет ничего ответа. Итак, Go будет делать именно это: установите статус ответа на 200 HTTP OK.

Обратите внимание, что обработка каждого запроса выполняется в своем собственном goroutine.

Итак, если вы вызываете HandleIndex в новом goroutine, ваша исходная анонимная функция будет продолжена: она закончится, и поэтому заголовок ответа будет установлен - между тем (одновременно) ваш запущенный новый goroutine также установит заголовок ответа - следовательно ошибка "multiple response.WriteHeader calls".

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

Ответ 2

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

Если вы ping ваш сервер с завиткой, например, вы увидите только один отправляемый запрос:

 curl localhost:5678

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

поэтому, когда вы написали один раз, переменная writHeader была бы правдой, тогда вы снова написали заголовок, это было бы неэффективно и дало бы предупреждение "http: multiple respnse.WriteHeader calls". на самом деле функция Write также вызывает WriteHeader, поэтому ставьте функцию WriteHeader после того, как функция Write также вызывает эту ошибку, а более поздняя запись WriteHeader не работает.

из вашего случая, go handleindex работает в другом потоке, и исходный файл уже возвращается, если вы ничего не сделаете, он вызовет WriteHeader для установки 200. При запуске handleindex он вызывает другой WriteHeader, в то время writeHeader является истинным, тогда появляется сообщение "http: multiple response.WriteHeader calls".