
Hace cinco años, comencé a
desarrollar Gophish , lo que hizo posible aprender Golang. Me di cuenta de que Go es un lenguaje poderoso, cuyas capacidades se complementan con muchas bibliotecas. Go es universal: en particular, con su ayuda es posible desarrollar aplicaciones de servidor sin problemas.
Este artículo trata sobre cómo escribir un servidor en Go. Comencemos con cosas simples, como "¡Hola, mundo!", Y terminemos con una aplicación con las siguientes características:
- Usando Let's Encrypt para HTTPS.
- Trabaja como un enrutador API.
- Trabajar con middleware.
- Procesamiento de archivos estáticos.
- Cierre correcto.
Skillbox recomienda: el programa práctico Python Developer desde cero .
Le recordamos: para todos los lectores de "Habr": un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox con el código de promoción "Habr".
Hola mundo
Crear un servidor web en Go es muy rápido. Aquí hay un ejemplo del uso de un controlador que devuelve el "¡Hola, mundo!" Prometido anteriormente.
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) http.ListenAndServe(":80", nil) }
Después de eso, si inicia la aplicación y abre la página
localhost , verá inmediatamente el texto "¡Hola, mundo!" (por supuesto, si todo funciona correctamente).
A continuación, usaremos repetidamente el controlador, pero primero, comprendamos cómo funciona todo.
net / http
El ejemplo utilizó el paquete
net/http
, que es la herramienta principal en Go para desarrollar servidores y clientes HTTP. Para entender el código, veamos el significado de tres elementos importantes: http.Handler, http.ServeMux y http.Server.
Controladores HTTP
Cuando recibimos una solicitud, el controlador la analiza y forma una respuesta. Los controladores Go se implementan de la siguiente manera:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
El primer ejemplo usa la función auxiliar http.HandleFunc. Envuelve otra función, que a su vez acepta http.ResponseWriter y http.Request en ServeHTTP.
En otras palabras, los controladores de Golang están representados por una única interfaz, que ofrece muchas oportunidades para el programador. Entonces, por ejemplo, el middleware se implementa usando un controlador, donde ServeHTTP primero hace algo y luego llama al método ServeHTTP de otro controlador.
Como se mencionó anteriormente, los controladores simplemente generan respuestas a las solicitudes. Pero, ¿qué controlador en particular se debe utilizar en un momento determinado?
Solicitar enrutamiento
Para tomar la decisión correcta, use el multiplexor HTTP. Se llama muxer o enrutador en varias bibliotecas, pero es todo lo mismo. La función del multiplexor es analizar la ruta de solicitud y seleccionar el controlador apropiado.
Si necesita soporte para enrutamiento complejo, es mejor usar bibliotecas de terceros. Uno de los más avanzados es
gorilla / mux y
go-chi / chi , estas bibliotecas hacen posible implementar el procesamiento intermedio sin ningún problema. Con su ayuda, puede configurar el enrutamiento de comodines y realizar una serie de otras tareas. Su ventaja es la compatibilidad con los controladores HTTP estándar. Como resultado, puede escribir código simple con la posibilidad de modificarlo en el futuro.
Trabajar con marcos complejos en una situación normal requerirá soluciones no bastante estándar, y esto complica enormemente el uso de controladores predeterminados. Para crear la gran mayoría de las aplicaciones, una combinación de la biblioteca predeterminada y un enrutador simple es suficiente.
Procesamiento de solicitudes
Además, necesitamos un componente que "escuche" las conexiones entrantes y redirija todas las solicitudes al controlador correcto. Esta tarea puede ser realizada fácilmente por http.Server.
A continuación se muestra que el servidor es responsable de todas las tareas relacionadas con el procesamiento de la conexión. Esto, por ejemplo, funciona en el protocolo TLS. Para implementar la llamada http.ListenAndServer, se utiliza un servidor HTTP estándar.
Ahora veamos ejemplos más complejos.
Agregar Let's Encrypt
De forma predeterminada, nuestra aplicación se ejecuta sobre el protocolo HTTP, pero se recomienda usar el protocolo HTTPS. En Go, esto se puede hacer sin problemas. Si recibió un certificado y una clave privada, simplemente registre ListenAndServeTLS con el certificado y los archivos de clave correctos.
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)
Siempre puedes hacerlo mejor.
Let's Encrypt ofrece certificados gratuitos con la capacidad de renovar automáticamente. Para utilizar el servicio, necesita el paquete
autocert
.
La forma más fácil de configurarlo es usar el método autocert.NewListener en combinación con http.Serve. El método le permite recibir y renovar certificados TLS, mientras que el servidor HTTP procesa las solicitudes:
http.Serve(autocert.NewListener("example.com"), nil)
Si abrimos
example.com en el navegador, obtenemos la respuesta HTTPS "¡Hola, mundo!".
Si necesita una configuración más completa, debe usar el administrador autocert.Manager. Luego creamos nuestra propia instancia de http.Server (hasta ahora la usamos por defecto) y agregamos el administrador al servidor TLSConfig:
m := &autocert.Manager{ Cache: autocert.DirCache("golang-autocert"), Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"), } server := &http.Server{ Addr: ":443", TLSConfig: m.TLSConfig(), } server.ListenAndServeTLS("", "")
Esta es una manera fácil de implementar el soporte HTTPS completo con la renovación automática de certificados.
Agregar rutas personalizadas
El enrutador predeterminado incluido en la biblioteca estándar es bueno, pero es muy simple. La mayoría de las aplicaciones requieren rutas más complejas, incluidas rutas anidadas y comodines, o el procedimiento para establecer patrones y parámetros de ruta.
En este caso, debe usar los paquetes
gorilla / mux y
go-chi / chi . Aprenderemos cómo trabajar con este último; a continuación se muestra un ejemplo.
Se proporciona el archivo api / v1 / api.go que contiene las rutas para nuestra API:
/ HelloResponse is the JSON representation for a customized message type HelloResponse struct { Message string `json:"message"` }
Establecemos el prefijo api / vq para las rutas en el archivo principal.
Luego podemos montar esto en nuestro enrutador principal bajo el prefijo api / v1 / en nuestra aplicación principal:
La simplicidad de trabajar con rutas complejas en Go hace posible simplificar la estructuración al servir grandes aplicaciones complejas.
Trabajar con middleware
En el caso del procesamiento intermedio, se utiliza envolver un controlador HTTP con otro, lo que permite autenticar, comprimir, registrar y algunas otras funciones rápidamente.
Como ejemplo, considere la interfaz http.Handler, con su ayuda escribimos un controlador con autenticación de usuarios del servicio.
func RequireAuthentication(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !isAuthenticated(r) { http.Redirect(w, r, "/login", http.StatusTemporaryRedirect) return }
Hay enrutadores de terceros, por ejemplo, chi, que le permiten ampliar la funcionalidad del procesamiento intermedio.
Trabajar con archivos estáticos
La biblioteca estándar Go incluye funciones para trabajar con contenido estático, incluidas imágenes, así como archivos JavaScript y CSS. Se puede acceder a ellos a través de la función http.FileServer. Devuelve un controlador que distribuye archivos desde un directorio específico.
func NewRouter() http.Handler { router := chi.NewRouter() r.Get("/{name}", HelloName)
Vale la pena recordar que http.Dir muestra el contenido del directorio si no tiene el archivo index.html principal. En este caso, para evitar que el directorio se vea comprometido, vale la pena usar el paquete
unindexed
.
Cierre correcto
Go también tiene una característica como cerrar el servidor HTTP correctamente. Esto se puede hacer usando el método Shutdown (). El servidor se inicia en goroutine y luego se escucha el canal para recibir una señal de interrupción. Tan pronto como se recibe la señal, el servidor se apaga, pero no de inmediato, sino después de unos segundos.
handler := server.NewRouter() srv := &http.Server{ Handler: handler, } go func() { srv.Serve(autocert.NewListener(domains...)) }()
En conclusión
Go es un lenguaje poderoso con una biblioteca estándar casi universal. Sus capacidades predeterminadas son muy amplias, y puede fortalecerlas con la ayuda de interfaces: esto le permite desarrollar servidores HTTP verdaderamente confiables.
Skillbox recomienda: