在一个非常简单的示例中,多次调用response.WriteHeader会发生什么情况?

huangapple go评论83阅读模式
英文:

multiple response.WriteHeader calls in really simple example?

问题

我有一个最基本的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,只写了一次header,并给了一个固定的body内容为Hello, World!。看起来有两种可能的情况。要么是在幕后有其他东西写了另一个header,要么是HandleIndex在同一个请求中被调用了两次。我该怎么做才能停止写入多个header呢?

编辑:似乎与go HandleIndex(w, r)这一行有关,因为如果我删除go,只将其作为函数调用而不是goroutine,就不会出现任何问题,浏览器也能正常接收数据。但是当它作为goroutine时,我会得到多个WriteHeader错误,并且浏览器不会显示"Hello World"。为什么将其作为goroutine会导致问题呢?

英文:

I have the most basic net/http program that I'm using to learn the namespace in 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!"))
}

When I run the program and connect to localhost:5678 in Chrome, I get this in the console:

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

But I don't see how that's possible. I print the URL, start up a new goroutine, write the header once, and give it a static body of Hello, World! It seems like one of two things is happening. Either something behind the scenes is writing another header or somehow HandleIndex is called twice for the same request. What can I do to stop writing multiple headers?

EDIT: It seems to have something to do with the go HandleIndex(w, r) line because if I remove go and just make it a function call instead of a goroutine, I don't get any issues and the browser gets it's data. With it being a goroutine, I get the multiple WriteHeader error and the browser doesn't show "Hello World." Why is making this a goroutine breaking it?

答案1

得分: 60

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

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"包将知道这一点,并且不会尝试再次设置响应头,因此你遇到的错误将不会发生。

英文:

Take a look at the anonymous function you register as the handler of incoming requests:

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

It prints the URL (to the standard output) then calls HandleIndex() in a new goroutine and continues execution.

If you have a handler function where you do not set the response status before the first call to Write, Go will automatically set the response status to 200 (HTTP OK). If the handler function does not write anything to the response (and does not set the response status and completes normally), that is also treated as a successful handling of the request and the response status 200 will be sent back. Your anonymous function does not set it, it does not even write anything to the response. So Go will do just that: set the response status to 200 HTTP OK.

Note that handling each request runs in its own goroutine.

So if you call HandleIndex in a new goroutine, your original anonymous function will continue: it will end and so the response header will be set - meanwhile (concurrently) your started new goroutine will also set the response header - hence the "multiple response.WriteHeader calls" error.

If you remove the "go", your HandleIndex function will set the response header in the same goroutine before your handler function returns, and the "net/http" will know about this and will not try to set the response header again, so the error you experienced will not happen.

答案2

得分: 5

你已经收到了一个正确回答来解决你的问题,我将给出一些关于一般情况的信息(这种错误经常出现)。

根据文档,你可以看到WriteHeader发送了一个HTTP状态码,你不能发送多个状态码。如果你使用Write发送任何内容,这相当于发送了200状态码,然后再写入内容。

所以,如果你明确地多次使用了w.WriteHeader,或者在w.WriteHeader之前使用了w.Write,你会看到这个错误消息。

英文:

You already received a correct answer which addresses your problem, I will give some information about the general case (such error appears often).

From the documentation, you see that WriteHeader sends an http status code and you can't send more than 1 status code. If you Write anything this is equivalent to sending 200 status code and then writing things.

So the message that you see appears if you either user w.WriteHeader more than once explicitly or uses w.Write before w.WriteHeader.

答案3

得分: 2

根据文档:

// WriteHeader 使用状态码发送HTTP响应头。
// 如果没有显式调用WriteHeader,第一次调用Write将触发隐式的WriteHeader(http.StatusOK)。

在你的情况下,发生的是你从处理程序中启动了go HandleIndex
第一个处理程序完成后,标准的WriteHeader会写入ResponseWriter。然后启动go例程HandleIndex,它也尝试写入头部和写入。

只需从HandleIndex中移除go,它就会正常工作。

英文:

From the documentation:

// 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).

What is happening in your case is that you are launching go HandleIndex from the handler.
The first handler finishes. The standard WriteHeader writes to the ResponseWriter. Then the go routine HandleIndex is launched and it also tries to write a header and write.

Just remove the go from HandleIndex and it will work.

答案4

得分: 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时,变量wroteHeader会变为true。然后你再次调用WriteHeader时,它不会生效,并输出警告信息"http: multiple response.WriteHeader calls"。实际上,函数Write也会调用WriteHeader,所以将函数WriteHeader放在函数Write之后也会导致这个错误,后面的WriteHeader不起作用。

在你的情况中,go handleindex在另一个线程中运行,并且原始的返回已经完成。如果你不做任何处理,它将调用WriteHeader来设置状态码200。当运行handleindex时,它会再次调用WriteHeader,此时wroteHeader为true,然后输出信息"http: multiple response.WriteHeader calls"。

英文:

the root cause is that you called WriteHeader more than once. from the source codes

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

so when you wrote once, the variable wroteHeader would be true, then you wrote header again, it wouldn't be effective and gave a warning "http: multiple respnse.WriteHeader calls".
actually the function Write also calls WriteHeader, so putting the function WriteHeader after the function Write also causes that error, and the later WriteHeader doesn't work.

from your case, go handleindex runs in another thread and the original already returns, if you do nothing, it will call WriteHeader to set 200. when running handleindex, it calls another WriteHeader, at that time wroteHeader is true, then the message "http: multiple response.WriteHeader calls" is output.

答案5

得分: 0

是的,使用HandleIndex(w, r)而不是go HandleIndex(w, r)将解决你的问题,我想你已经弄清楚了。

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

英文:

Yes, use HandleIndex(w, r) instead of go HandleIndex(w, r) will fix your issue, I think you have already figured that out.

The reason is simple, when handling multiple requests at the same time, the http server will start multiple goroutines, and your handler function will be called separately in each of the goroutines without blocking others.
You don't need to start your own goroutine in the handler, unless you practically need it, but that will be another topic.

答案6

得分: -2

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

如果你使用 curl 对服务器进行 ping,你会看到只发送了一个请求:

 curl localhost:5678

为了确保,你可以在你的 http.HandleFunc 中添加一个 EndPoint。

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

Because modern browsers send an extra request for /favicon.ico which is also handled in your / request handler.

If you ping your server with curl for example, you'll see only one request being sent:

 curl localhost:5678

To be sure you can add an EndPoint in your http.HandleFunc

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

huangapple
  • 本文由 发表于 2015年1月16日 04:48:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/27972715.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定