Go HTTP işleyicilerinde, ResponseWriter neden bir değerdir, ancak İstek bir işaretçi?


84

GAE için bir uygulama yazarak Go öğreniyorum ve bu bir işleyici işlevinin imzası:

func handle(w http.ResponseWriter, r *http.Request) {}

Burada acemi işaretçi değilim, peki neden Requestnesne bir işaretçi, ama ResponseWriterdeğil? Bu şekilde olması gerekiyor mu yoksa bu sadece bir tür gelişmiş işaretçi tabanlı kodu mümkün kılmak için mi?

Yanıtlar:


66

Elde ettiğiniz şey w, dışa aktarılmayan tipe bir işaretçidir, http.responseancak ResponseWriterbir arayüz olduğu gibi , bu görünmez.

Gönderen server.go :

type ResponseWriter interface {
    ...
}

On the other hand, r is a pointer to a concrete struct, hence the need to pass a reference explicitly.

From request.go:

type Request struct {
    ...
}

28

The http.ResponseWriter is an interface, and the existing types implementing this interface are pointers. That means there's no need to use a pointer to this interface, as it's already "backed" by a pointer. This concept is describe a bit by one of the go develpers here Although a type implementing http.ResponseWriter didn't need to be a pointer, it would not be practical, at least not within the go http server.

http.Request is not an interface, it's just a struct, and since we want to change this struct and have the web server see those changes, it has to be a pointer. If it was just a struct value, we would just modify a copy of it that the web server calling our code could not see.


1
I don't think this is right. Values behind pointers can implement interfaces the same as values do, so there is no need for distinction here. A type with base type int can satisfy an interface without being a pointer or being backed by one.
nemo

2
Well, I didn't mean to imply that all things implementing an interface have to be pointers. Something like a ResponseWriter that would need to modify the state of the value behind the interface to do anything useful, and that will require that value is a pointer type (as the blog post I linked to also says). But yes, http.ResponseWriter could theoretically be implemented by an int (whose methods could never change that int value).
nos

This answer was super helpful. That link is priceless, with a clear explanation for those of us new to pointers. "That is, although there is no explicit marker, interface objects often act like pointers. This can be confusing until you understand what is really going on."
Cody Django

1
The part about Request is actually wrong, see my answer stackoverflow.com/a/56875204/989991
joakim

7

As correctly mentioned in many other answers here and elsewhere, ResponseWriter is an interface and the implications of this have beed described in detail in SO answers and blogs.

What I would like to address is what I feel is the big—and dangerous—misconception here, that the reason request is passed by "reference" (although such a thing does not really exist in Go) is that "we want to make changes to it visible to the server".

Quoting a couple of answers:

[..] it's just a struct, and since we want to change this struct and have the web server see those changes, it has to be a pointer [..] SO

[..] changes to Request by the handler need to be visible to the server, so we’re only passing it by reference instead of by value [..] SO

This is wrong; in fact the docs explicitly warn against tampering with / mutating the request:

Except for reading the body, handlers should not modify the provided Request.

Quite the opposite, no? :-)

If you want to change the request, e.g. append a tracing header before passing it on to the next handler in a middleware chain you have to copy the request and pass the copied version down the chain.

Requests to change the behavior to allow modifications of the incoming request have been raised with the Go team but changing something like this would probably lead to at least some existing code breaking unexpectedly.

Why use a pointer if we are explicitly telling people not to mutate the request? Performance, Request is a large struct and copying it can bring performance down, especially with long middleware chains in mind. The team had to strike a balance, definitely not an ideal solution, but the tradeoffs are clearly on the side of performance here (instead of API safety).


1
This is the answer that finally made sense to me.
Jimi

2

The reason why it’s a pointer to Request is simple: changes to Request by the handler need to be visible to the server, so we’re only passing it by reference instead of by value.

If you dig into the net/http library code, you’ll find that ResponseWriter is an interface to a nonexported struct response, and we’re passing the struct by reference (we’re passing in a pointer to response) and not by value. ResponseWriter is an interface that a handler uses to create an HTTP response. The actual struct backing up ResponseWriter is the nonexported struct http.response. Because it’s nonexported, you can’t use it directly; you can only use it through the ResponseWriter interface.

In other words, both the parameters are passed in by reference; it’s just that the method signature takes a ResponseWriter that’s an interface to a pointer to a struct, so it looks as if it’s passed in by value.


Makes more sense than the original answer to me
Venkata S S K M Chaitanya

The part about Request is actually wrong, see my answer stackoverflow.com/a/56875204/989991
joakim

0

I think that the main reason for the Request object to be passed as a pointer is the Body field. For a given HTTP request, the body can only we read once. If the Request object was cloned, as it would be if it wasn't passed as a pointer, we would have two objects with different information about how much has been read from the body.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.