Desarrollo de servidores web en Golang: de simple a complejo

Desarrollo de servidores web en Golang: de simple a complejo

Hace cinco años comencé desarrollar gophish, esto brindó la oportunidad de aprender Golang. Me di cuenta de que Go es un lenguaje poderoso, complementado por muchas bibliotecas. Go es versátil: en particular, se puede utilizar para desarrollar aplicaciones del lado del servidor sin ningún problema.

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 capacidades:

- Uso de Let's Encrypt para HTTPS.
— Trabajar como enrutador API.
— Trabajar con middleware.
— Procesamiento de archivos estáticos.
— Apagado correcto.

Skillbox recomienda: Curso práctico "Desarrollador Python desde cero".

Recordamos: para todos los lectores de "Habr": un descuento de 10 rublos al inscribirse en cualquier curso de Skillbox utilizando el código promocional "Habr".

Hola mundo

Puede crear un servidor web en Go muy rápidamente. A continuación se muestra un ejemplo del uso de un controlador que devuelve el mensaje "¡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 esto, si ejecuta la aplicación y abre la página localhost, inmediatamente verás el texto “¡Hola, mundo!” (si todo funciona correctamente, claro).

Usaremos el controlador varias veces más adelante, pero primero comprendamos cómo funciona todo.

net / http

El ejemplo utilizó el paquete. net/http, es la herramienta principal en Go para desarrollar servidores y clientes HTTP. Para comprender el código, comprendamos el significado de tres elementos importantes: http.Handler, http.ServeMux y http.Server.

Controladores HTTP

Cuando recibimos una solicitud, el controlador la analiza y genera una respuesta. Los controladores en Go se implementan de la siguiente manera:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

El primer ejemplo utiliza la función auxiliar http.HandleFunc. Envuelve otra función, que a su vez lleva http.ResponseWriter y http.Request a ServeHTTP.

En otras palabras, los controladores en Golang se presentan en una única interfaz, lo que brinda muchas opciones al programador. Entonces, por ejemplo, el middleware se implementa mediante 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 debería usarse en un momento determinado?

Solicitar ruta

Para tomar la decisión correcta, utilice un multiplexor HTTP. En varias bibliotecas se le llama muxer o enrutador, pero todos son lo mismo. La función del multiplexor es analizar la ruta de la solicitud y seleccionar el controlador apropiado.

Si necesita soporte para enrutamiento complejo, es mejor utilizar bibliotecas de terceros. Algunos de los más avanzados - gorila/mux и go-chi/chi, estas bibliotecas permiten implementar un procesamiento intermedio sin ningún problema. Con su ayuda, puede configurar el enrutamiento con comodines y realizar otras tareas. Su ventaja es la compatibilidad con los controladores HTTP estándar. Como resultado, puede escribir código simple que puede modificarse en el futuro.

Trabajar con marcos complejos en una situación normal requerirá soluciones no estándar, y esto complica significativamente el uso de controladores predeterminados. Para crear la gran mayoría de aplicaciones, será suficiente una combinación de la biblioteca predeterminada y un simple enrutador.

Procesamiento de consultas

Además, necesitamos un componente que "escuche" las conexiones entrantes y redirija todas las solicitudes al controlador correcto. http.Server puede manejar fácilmente esta tarea.

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 mediante 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 utilizar el protocolo HTTPS. Esto se puede hacer sin problemas en Go. Si ha recibido un certificado y una clave privada, basta con registrar ListenAndServeTLS con el certificado y los archivos de clave correctos.

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

Siempre puedes hacerlo mejor.

Vamos a cifrar proporciona certificados gratuitos con renovación automática. Para utilizar el servicio, necesita un paquete. autocert.

La forma más sencilla de configurarlo es utilizar el método autocert.NewListener en combinación con http.Serve. El método le permite obtener y actualizar certificados TLS mientras el servidor HTTP procesa las solicitudes:

http.Serve(autocert.NewListener("example.com"), nil)

Si abrimos en el navegador example.com, recibiremos una respuesta HTTPS "¡Hola, mundo!"

Si necesita una configuración más detallada, debe utilizar el administrador autocert.Manager. Luego creamos nuestra propia instancia http.Server (hasta ahora la usábamos 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 soporte HTTPS completo con renovación automática de certificados.

Agregar rutas personalizadas

El enrutador predeterminado incluido en la biblioteca estándar es bueno, pero muy básico. La mayoría de las aplicaciones requieren enrutamiento más complejo, incluidas rutas anidadas y comodín, o un procedimiento para configurar patrones y parámetros de ruta.

En este caso vale la pena usar paquetes. gorila/mux и go-chi/chi. Aprenderemos cómo trabajar con este último; a continuación se muestra un ejemplo.

Se muestra el archivo api/v1/api.go que contiene rutas para nuestra API:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

Configuramos 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:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

La facilidad de Go para trabajar con rutas complejas permite simplificar la estructuración y el mantenimiento de aplicaciones grandes y complejas.

Trabajar con software intermedio

La puesta en escena implica combinar un controlador HTTP con otro, lo que permite realizar rápidamente autenticación, compresión, registro y varias otras funciones.

Como ejemplo, veamos la interfaz http.Handler; la usaremos para escribir un controlador que autentique a los 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
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

Existen enrutadores de terceros, como chi, que le permiten ampliar la funcionalidad del middleware.

Trabajar con archivos estáticos

La biblioteca estándar de Go incluye capacidades para trabajar con contenido estático, incluidas imágenes, archivos JavaScript y CSS. Se puede acceder a ellos a través de la función http.FileServer. Devuelve un controlador que sirve archivos de un directorio específico.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Definitivamente vale la pena recordar que http.Dir muestra el contenido del directorio si no contiene el archivo principal index.html. En este caso, para evitar que el directorio se vea comprometido, deberá utilizar el paquete unindexed.

Apagado correcto

Go también tiene una función llamada cierre ordenado del servidor HTTP. Esto se puede hacer usando el método Shutdown(). El servidor se inicia en una rutina 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 inmediatamente, sino después de unos segundos.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Como conclusión

Go es un lenguaje poderoso con una biblioteca estándar casi universal. Sus capacidades predeterminadas son muy amplias y se pueden mejorar mediante interfaces; esto le permite desarrollar servidores HTTP verdaderamente confiables.

Skillbox recomienda:

Fuente: habr.com

Añadir un comentario