Deje su lenguaje de programación colgado en la puerta y venga a admirar la mejor biblioteca estándar que he conocido.
Este es todo el código que realmente necesita...
Elegir un lenguaje de programación para un proyecto no debería ser como declarar quién es tu equipo favorito. debería ser una cuestión de pragmatismo y de elegir la herramienta adecuada para el trabajo adecuado.
En este post quiero mostrarte cuándo y por qué Go brilla como lenguaje. Específicamente, quiero mostrarles lo sólida que es su biblioteca estándar para la programación básica de Internet. Aún más específicamente... ¡vamos a escribir un proxy inverso!
"Go tiene mucho a su favor, pero donde realmente se destaca es en estas tareas de plomería de red de nivel inferior, no hay mejor lenguaje".
¿Qué es un proxy inverso? Una gran forma elegante de decir un promotor de tráfico. Recibo un envío de solicitud de un cliente, envío esa solicitud a otro servidor, recibo una respuesta del servidor y la reenvío al cliente. La parte inversa de esto simplemente significa que el propio proxy determina dónde enviar el tráfico y cuándo
(Simplemente hermoso 😍…)
¿Por qué es útil? Debido a que el concepto es tan simple, se puede aplicar para ayudar en muchos casos diferentes: equilibrio de carga, pruebas A/B, almacenamiento en caché, autenticación, etc.
Al final de esta breve publicación, habrá aprendido a:
Sumerjámonos en el proyecto real. Lo que vamos a hacer es tener un servidor web que:
1. Acepta solicitudes
2. Lee el cuerpo de una solicitud, específicamente el campo proxy_condition
3. Si el dominio proxy es igual a A, envíe el tráfico a la URL 1
4. Si el dominio proxy es igual a B, envíe el tráfico a la URL 2
5. Si el dominio proxy no es ninguno, envíe el tráfico a la URL predeterminada.
Lo primero que queremos hacer es ingresar todas las variables de configuración requeridas en nuestro entorno para que podamos usarlas en nuestra aplicación y mantenerlas fuera del código fuente.
Creo que el mejor enfoque es crear un archivo .env que contenga las variables de entorno deseadas.
A continuación se muestra lo que tengo para este proyecto específico:
export PORT= 1330 export A_CONDITION_URL= "http://localhost:1331" export B_CONDITION_URL= "http://localhost:1332" export DEFAULT_CONDITION_URL= "http://localhost:1333"
Este es un hábito que aprendí de la aplicación 12 Factor
Después de guardar su
.env
archivo que puede ejecutar: source . env
para configurar, cargue la configuración en su entorno en cualquier momento.
A continuación vamos a crear un archivo llamado
main.go
que hace lo siguiente:1. Cuando se inicia registra el
PORT
, A_CONDITION_URL
, B_CONDITION_URL
, y DEFAULT_CONDITION_URL
variables de entorno a la consola2. Escuche las solicitudes en la ruta:
/
package main import ( "bytes" "encoding/json" "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" "os" "strings" ) // Get env var or default func getEnv(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value } return fallback } // Get the port to listen on func getListenAddress() string { port := getEnv( "PORT" , "1338" ) return ":" + port } // Log the env variables required for a reverse proxy func logSetup() { a_condtion_url := os.Getenv( "A_CONDITION_URL" ) b_condtion_url := os.Getenv( "B_CONDITION_URL" ) default_condtion_url := os.Getenv( "DEFAULT_CONDITION_URL" ) log.Printf( "Server will run on: %s\n" , getListenAddress()) log.Printf( "Redirecting to A url: %s\n" , a_condtion_url) log.Printf( "Redirecting to B url: %s\n" , b_condtion_url) log.Printf( "Redirecting to Default url: %s\n" , default_condtion_url) } // Given a request send it to the appropriate url func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { // We will get to this... } func main() { // Log setup values logSetup() // start server http.HandleFunc( "/" , handleRequestAndRedirect) if err := http.ListenAndServe(getListenAddress(), nil); err != nil { panic(err) } }
(💀Saquemos los esqueletos del armario para que podamos pasar a las cosas divertidas).
Ahora deberías poder ejecutar
Ahora que tenemos el esqueleto de nuestro proyecto juntos, queremos comenzar a crear la lógica que manejará el análisis del cuerpo de la solicitud. Empiece por actualizar
handleRequestAndRedirect
para analizar el proxy_condition
valor del cuerpo de la solicitud. type requestPayloadStruct struct { ProxyCondition string `json:"proxy_condition"` } // Get a json decoder for a given requests body func requestBodyDecoder(request *http.Request) *json.Decoder { // Read body to buffer body, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf( "Error reading body: %v" , err) panic(err) } // Because go lang is a pain in the ass if you read the body then any susequent calls // are unable to read the body again.... request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) return json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(body))) } // Parse the requests body func parseRequestBody(request *http.Request) requestPayloadStruct { decoder := requestBodyDecoder(request) var requestPayload requestPayloadStruct err := decoder.Decode(&requestPayload) if err != nil { panic(err) } return requestPayload } // Given a request send it to the appropriate url func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { requestPayload := parseRequestBody(req) // ... more to come }
(Análisis básico de un blob JSON a una estructura en Go).
proxy_condition
para determinar a dónde enviamos el tráficoAhora que tenemos el valor de la
proxy_condition
a partir de la solicitud, la usaremos para decidir hacia dónde dirigimos nuestro proxy inverso.Recuerda de antes que tenemos tres casos:
proxy_condition
es igual a A
luego enviamos tráfico a A_CONDITION_URL
proxy_condition
es igual a B
luego enviamos tráfico a B_CONDITION_URL
DEFAULT_CONDITION_URL
// Log the typeform payload and redirect url func logRequestPayload(requestionPayload requestPayloadStruct, proxyUrl string) { log.Printf( "proxy_condition: %s, proxy_url: %s\n" , requestionPayload.ProxyCondition, proxyUrl) } // Get the url for a given proxy condition func getProxyUrl(proxyConditionRaw string) string { proxyCondition := strings.ToUpper(proxyConditionRaw) a_condtion_url := os.Getenv( "A_CONDITION_URL" ) b_condtion_url := os.Getenv( "B_CONDITION_URL" ) default_condtion_url := os.Getenv( "DEFAULT_CONDITION_URL" ) if proxyCondition == "A" { return a_condtion_url } if proxyCondition == "B" { return b_condtion_url } return default_condtion_url } // Given a request send it to the appropriate url func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { requestPayload := parseRequestBody(req) url := getProxyUrl(requestPayload.ProxyCondition) logRequestPayload(requestPayload, url) // more still to come... }
¡Finalmente estamos en el proxy inverso real! En tantos idiomas, un proxy inverso requeriría mucho pensamiento y una buena cantidad de código o al menos tener que importar una biblioteca sofisticada.
Sin embargo, la biblioteca estándar de Golang hace que la creación de un proxy inverso sea tan simple que es casi increíble. A continuación se muestra esencialmente la única línea de código que necesita:
httputil. NewSingleHostReverseProxy( url ) . ServeHTTP( res , req )
Tenga en cuenta que en el siguiente código agregamos un poco más para que pueda admitir completamente la redirección SSL (aunque no es necesario):
// Serve a reverse proxy for a given url func serveReverseProxy(target string, res http.ResponseWriter, req *http.Request) { // parse the url url, _ := url.Parse(target) // create the reverse proxy proxy := httputil.NewSingleHostReverseProxy(url) // Update the headers to allow for SSL redirection req.URL.Host = url.Host req.URL.Scheme = url.Scheme req.Header.Set( "X-Forwarded-Host" , req.Header.Get( "Host" )) req.Host = url.Host // Note that ServeHttp is non blocking and uses a go routine under the hood proxy.ServeHTTP(res, req) } // Given a request send it to the appropriate url func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { requestPayload := parseRequestBody(req) url := getProxyUrl(requestPayload.ProxyCondition) logRequestPayload(requestPayload, url) serveReverseProxy(url, res, req) }
La única vez en el proyecto sentí que Go realmente se estaba quitando de mi camino.
Ok, ahora que tenemos todo esto conectado, configure nuestra aplicación en el puerto
1330
y nuestros 3 servidores simples en puertos 1331–1333
(todo en terminales separados): source .env && go install && $GOPATH/bin/reverse-proxy-demo
http-server -p 1331
http-server -p 1332
http-server -p 1333
Con todo esto en funcionamiento, podemos comenzar a enviar solicitudes con un cuerpo json en otra terminal de la siguiente manera:
curl --request GET \ --url http: //localhost:1330/ \ --header 'content-type: application/json' \ --data '{ "proxy_condition": "a" }'
Si está buscando un gran cliente de solicitud HTTP, no puedo recomendar Insomnia lo suficiente.
y listo, podemos comenzar a ver nuestro proxy inverso dirigiendo el tráfico a uno de nuestros 3 servidores en función de lo que configuramos en el
proxy_condition
¡campo! (¡¡¡Está vivo!!!)
Go tiene mucho a su favor, pero donde realmente se pavonea es en estas tareas de "plomería" de red de nivel inferior, no hay mejor lenguaje. Lo que hemos escrito aquí es simple, eficaz, confiable y muy listo para usar en producción.
Para servicios simples, puedo verme buscando Go nuevamente en el futuro.
🧞 ¡Esto es de código abierto! puedes encontrarlo aquí en Github
❤️ Solo escribo sobre programación y trabajo remoto. Si me sigues en Twitter no te haré perder el tiempo.