paint-brush
Escribir un proxy inverso en una sola línea con Gopor@bnchrch
74,643 lecturas
74,643 lecturas

Escribir un proxy inverso en una sola línea con Go

por Ben Church6m2018/04/02
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Un proxy inverso es una forma sencilla de decir un reenviador 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 tráfico y cuándo. Si el dominio proxy es igual a A, envíe el tráfico a la URL 1 o B a la URL predeterminada. El concepto es tan simple que se puede aplicar para ayudar en muchos casos diferentes: equilibrio de carga, pruebas A/B, almacenamiento en caché, autenticación, etc.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Escribir un proxy inverso en una sola línea con Go
Ben Church HackerNoon profile picture

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:

  • Servir solicitudes HTTP
  • Analizar el cuerpo de una solicitud
  • Servir tráfico a otro servidor utilizando un proxy inverso

Nuestro proyecto de proxy inverso

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.

requisitos previos

  • Ir para la programación con.
  • servidor http para crear servidores simples con.

Configurando nuestro entorno

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.

Sentando las bases de nuestro proyecto

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 consola

2. 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

Analizar el cuerpo de la solicitud

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).

Usar
 proxy_condition
para determinar a dónde enviamos el tráfico

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

  1. Si
     proxy_condition
    es igual a
     A
    luego enviamos tráfico a
     A_CONDITION_URL
  2. Si
     proxy_condition
    es igual a
     B
    luego enviamos tráfico a
     B_CONDITION_URL
  3. De lo contrario, envíe el tráfico a
     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... }

Proxy inverso a esa URL

¡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):

  1.  source .env && go install && $GOPATH/bin/reverse-proxy-demo
  2.  http-server -p 1331
  3.  http-server -p 1332
  4.  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!!!)

Envolver

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.