Http 多个response.WriteHeader调用在一个非常简单的示例中?

Http 多个response.WriteHeader调用在一个非常简单的示例中?,http,go,server,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) }) fm

我有一个最基本的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”。为什么让这个goroutine破坏它?

来自文档:

// 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).
在您的情况下,您正在从处理程序启动
GoHandleIndex
。 第一个处理程序完成。标准编写者向响应编写者写入。然后启动go例程HandleIndex,它还尝试写入头并写入


只需从HandleIndex中删除
go
,它就会工作

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

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只需要这样做:将响应状态设置为200HTTPOK


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

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

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

是的,使用
HandleIndex(w,r)
而不是
go HandleIndex(w,r)
将解决您的问题,我认为您已经解决了这个问题

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

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

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

 curl localhost:5678
为了确保可以在http.HandleFunc中添加端点

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

您已经收到了解决问题的正确答案,我将提供一些关于一般情况的信息(此类错误经常出现)

从中可以看到,
WriteHeader
发送http状态代码,并且不能发送超过1个状态代码。如果你写任何东西,这相当于先发送200个状态码,然后再写东西


因此,如果您多次明确地使用
w.WriteHeader
,或者在
w.WriteHeader
之前使用
w.WriteHeader

,则会显示您看到的消息。根本原因是您多次调用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")
        }
    }
}
因此,当您编写一次时,变量wroteHeader将为true,然后您再次编写header,它将无效,并发出警告“http:multiple response.WriteHeader calls”。 实际上函数Write也调用WriteHeader,因此将函数WriteHeader放在函数Write之后也会导致该错误,后面的WriteHeader不起作用


在您的案例中,go handleindex在另一个线程中运行,并且原始线程已经返回,如果您不执行任何操作,它将调用WriteHeader来设置200。运行handleindex时,它调用另一个WriteHeader,此时wroteHeader为true,则输出消息“http:multiple response.WriteHeader calls”。

“请注意,处理每个请求都在其自己的gorouting中运行。”我希望它能做到这一点,但如果这是真的,我从来没有看到过这样或那样的说法。@CoreyOgburn引用文档中的话:
“service在侦听器l上接受传入的HTTP连接,为每个连接创建一个新的服务goroutine。”