En los controladores de HTTP de Go, ¿por qué ResponseWriter es un valor y Request un puntero?

82

Estoy aprendiendo Go escribiendo una aplicación para GAE, y esta es la firma de una función de controlador:

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

Soy un novato de puntero aquí, entonces, ¿por qué el Requestobjeto es un puntero, pero ResponseWriterno? ¿Es necesario hacerlo de esta manera o es solo para hacer posible algún tipo de código avanzado basado en punteros?

Sudhir Jonathan
fuente

Respuestas:

64

Lo que obtiene wes un puntero al tipo no exportado, http.responsepero como ResponseWriteres una interfaz, no es visible.

Desde server.go :

type ResponseWriter interface {
    ...
}

Por otro lado, res un puntero a una estructura concreta, de ahí la necesidad de pasar una referencia explícitamente.

De request.go :

type Request struct {
    ...
}
Denys Séguret
fuente
28

El http.ResponseWriteres una interfaz y los tipos existentes que implementan esta interfaz son punteros. Eso significa que no es necesario utilizar un puntero a esta interfaz, ya que ya está "respaldado" por un puntero. Este concepto es descrito un poco por uno de los desarrolladores de go aquí. Aunque un tipo que implementa http.ResponseWriter no necesita ser un puntero, no sería práctico, al menos no dentro del servidor http de go.

http.Requestno es una interfaz, es solo una estructura, y como queremos cambiar esta estructura y que el servidor web vea esos cambios, tiene que ser un puntero. Si fuera solo un valor de estructura, simplemente modificaríamos una copia que el servidor web que llama a nuestro código no podría ver.

nos
fuente
1
No creo que esto sea correcto. Los valores detrás de los punteros pueden implementar interfaces de la misma manera que lo hacen los valores, por lo que no hay necesidad de distinción aquí. Un tipo con el tipo base int puede satisfacer una interfaz sin ser un puntero o estar respaldado por uno.
nemo
2
Bueno, no quise dar a entender que todas las cosas que implementan una interfaz tienen que ser punteros. Algo como un ResponseWriter que necesitaría modificar el estado del valor detrás de la interfaz para hacer algo útil, y que requerirá que el valor sea un tipo de puntero (como también dice la publicación de blog a la que me vinculé). Pero sí, http.ResponseWriter teóricamente podría implementarse mediante un int (cuyos métodos nunca podrían cambiar ese valor int).
n.
Esta respuesta fue muy útil. Ese vínculo no tiene precio, con una explicación clara para aquellos de nosotros que somos nuevos en los punteros. "Es decir, aunque no hay un marcador explícito, los objetos de la interfaz a menudo actúan como punteros. Esto puede resultar confuso hasta que comprenda lo que realmente está sucediendo".
Cody Django
1
La parte sobre la solicitud es realmente incorrecta, vea mi respuesta stackoverflow.com/a/56875204/989991
joakim
6

Como se mencionó correctamente en muchas otras respuestas aquí y en otros lugares, ResponseWriteres una interfaz y las implicaciones de esto se han descrito en detalle en las respuestas y blogs de SO .

Lo que me gustaría abordar es lo que creo que es el gran y peligroso concepto erróneo aquí, de que la razón por la que la solicitud se pasa por "referencia" (aunque tal cosa realmente no existe en Go ) es que "queremos hacer cambios para que sea visible para el servidor ".

Citando un par de respuestas:

[..] es solo una estructura, y como queremos cambiar esta estructura y que el servidor web vea esos cambios, tiene que ser un puntero [..] SO

[..] los cambios en la Solicitud por parte del controlador deben ser visibles para el servidor, por lo que solo los pasamos por referencia en lugar de por valor [..] SO

Esto está mal ; de hecho, los documentos advierten explícitamente contra la manipulación / mutación de la solicitud :

Excepto para leer el cuerpo, los controladores no deben modificar la Solicitud proporcionada.

Todo lo contrario, ¿no? :-)

Si desea cambiar la solicitud, por ejemplo, agregar un encabezado de seguimiento antes de pasarlo al siguiente controlador en una cadena de middleware, debe copiar la solicitud y pasar la versión copiada en la cadena.

Las solicitudes para cambiar el comportamiento para permitir modificaciones de la solicitud entrante se han planteado con el equipo Go pero cambiando algo como esto probablemente daría lugar a por lo menos algo de código existente romper inesperadamente.

¿Por qué usar un puntero si le decimos explícitamente a las personas que no modifiquen la solicitud? El rendimiento , Requestes una gran estructura y la copia que puede aportar el rendimiento abajo, especialmente con cadenas largas de middleware en mente. El equipo tuvo que encontrar un equilibrio, definitivamente no es una solución ideal, pero las compensaciones están claramente del lado del rendimiento aquí (en lugar de la seguridad de la API).

joakim
fuente
Esta es la respuesta que finalmente tuvo sentido para mí.
Jimi
1

La razón por la que es un puntero a Solicitud es simple: los cambios a Solicitud por parte del controlador deben ser visibles para el servidor, por lo que solo lo pasamos por referencia en lugar de por valor.

Si profundiza en el código de la biblioteca net / http, encontrará que ResponseWriter es una interfaz para una respuesta de estructura no exportada, y estamos pasando la estructura por referencia (estamos pasando un puntero a la respuesta) y no por valor . ResponseWriter es una interfaz que utiliza un controlador para crear una respuesta HTTP. La estructura real que respalda ResponseWriter es la estructura http.response no exportada. Debido a que no se exporta, no puede usarlo directamente; solo puede usarlo a través de la interfaz ResponseWriter.

En otras palabras, ambos parámetros se pasan por referencia; es solo que la firma del método toma un ResponseWriter que es una interfaz a un puntero a una estructura, por lo que parece que se pasa por valor.

X. Wang
fuente
Tiene más sentido que la respuesta original para mí
Venkata SSKM Chaitanya
La parte sobre Solicitud es realmente incorrecta, vea mi respuesta stackoverflow.com/a/56875204/989991
joakim
0

Creo que la razón principal por la que el Requestobjeto se pasa como puntero es el Bodycampo. Para una solicitud HTTP determinada, el cuerpo solo se puede leer una vez. Si el Requestobjeto fue clonado, como sería si no se pasara como puntero, tendríamos dos objetos con información diferente sobre cuánto se ha leído del cuerpo.

md2perpe
fuente