多个response.WriteHeader调用真的很简单的例子?

时间:2015-01-15 20:48:56

标签: http go server

我有最基本的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!"))
}

当我运行程序并连接到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,启动一个新的goroutine,写一次标题,并给它一个静态的Hello, World!正文似乎发生了两件事之一。幕后的任何东西都在写另一个标题,或者以某种方式HandleIndex为同一个请求调用两次。如何停止编写多个标题?

编辑:它似乎与go HandleIndex(w, r)行有关,因为如果我删除go并将其设为函数调用而不是goroutine,我就不会遇到任何问题并且浏览器获取它的数据。由于它是一个goroutine,我得到多个WriteHeader错误,浏览器没有显示" Hello World。"为什么要把这个打破呢?

6 个答案:

答案 0 :(得分:51)

查看您注册为传入请求处理程序的匿名函数:

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

它打印URL(到标准输出),然后在新的goroutine中调用HandleIndex()并继续执行。

如果你有一个处理程序函数,你没有在第一次调用Write之前设置响应状态,Go会自动将响应状态设置为200(HTTP OK)。如果处理程序函数没有向响应写入任何内容(并且没有设置响应状态并且正常完成),那么这也被视为对请求的成功处理,并且将发回响应状态200。你的匿名函数没有设置它,它甚至没有写任何响应。所以Go会这样做:将响应状态设置为200 HTTP OK。

请注意,处理每个请求都在其自己的goroutine中运行。

因此,如果您在新的goroutine中调用HandleIndex,您的原始匿名函数将继续:它将结束,因此响应标头将被设置 - 同时(同时)您启动的新goroutine也将设置响应标头 - 因此"multiple response.WriteHeader calls"错误。

如果你删除"go",你的HandleIndex函数会在你的处理函数返回之前在同一个goroutine中设置响应头,而“net / http”会知道这个并且不会尝试再次设置响应标头,因此您遇到的错误不会发生。

答案 1 :(得分:3)

您已经收到了解决问题的正确答案,我会提供一些有关一般情况的信息(这种错误经常出现)。

documentation,您会看到WriteHeader发送了http状态代码,但您无法发送超过1个状态代码。如果你Write这相当于发送200状态代码然后写东西。

因此,如果您多次明确地使用w.WriteHeader或在w.Write之前使用w.WriteHeader,则会显示您看到的消息。

答案 2 :(得分:1)

因为现代浏览器会发送 /favicon.ico 的额外请求,这也会在/ request处理程序中处理。

例如,如果您使用curl ping服务器,则只会看到一个请求被发送:

 curl localhost:5678

确保您可以在http.HandleFunc

中添加EndPoint
http.HandleFunc("/Home", func(w http.ResponseWriter, r *http.Request) 

答案 3 :(得分:1)

根本原因是您多次调用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:multiple respnse.WriteHeader calls”。 实际上函数Write也调用WriteHeader,因此在函数Write之后放置函数WriteHeader也会导致该错误,后来的WriteHeader不起作用。

从你的情况来看,go handleindex在另一个线程中运行并且原来已经返回,如果你什么都不做,它会调用WriteHeader设置200.当运行handleindex时,它调用另一个WriteHeader,那时writeHeader为true,那么输出消息“http:multiple response.WriteHeader calls”。

答案 4 :(得分:0)

来自文档:

// 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即可。

答案 5 :(得分:0)

是的,使用HandleIndex(w, r)代替go HandleIndex(w, r)会解决您的问题,我想您已经明白了这一点。

原因很简单,当同时处理多个请求时,http服务器将启动多个goroutine,并且您的处理函数将在每个goroutine中单独调用,而不会阻塞其他函数。 您不需要在处理程序中启动自己的goroutine,除非您实际需要它,但这将是另一个主题。

相关问题