Escribir HTTP / 1.1 y HTTP / 2 Cliente y Servidor en Golang



Golang es un gran lenguaje de programación con una amplia gama de características. Este artículo muestra cómo puede escribir un cliente y un servidor en Go para los protocolos HTTP / 1.1 y HTTP / 2.

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

HTTP / 2 es la segunda versión principal del protocolo de red HTTP utilizado para acceder a la World Wide Web. El protocolo se basa en SPDY. Obtenga más información sobre esta versión en GitHub .

Servidor HTTP / 1.1


El siguiente código muestra cómo escribir un servidor HTTP / 1.1 en Go. La función principal inicia el servicio HTTP (S) con el puerto 9191 y la ruta / hello / sayHello. echoPayload es responsable de la lógica de eco, analiza el tráfico entrante y reacciona en consecuencia. Si es necesario, echoPayload se puede modificar.

package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main() { http.HandleFunc("/hello/sayHello", echoPayload) log.Printf("Go Backend: { HTTPVersion = 1 }; serving on https://localhost:9191/hello/sayHello") log.Fatal(http.ListenAndServeTLS(":9191", "./cert/server.crt", "./cert/server.key", nil)) } func echoPayload(w http.ResponseWriter, req *http.Request) { log.Printf("Request connection: %s, path: %s", req.Proto, req.URL.Path[1:]) defer req.Body.Close() contents, err := ioutil.ReadAll(req.Body) if err != nil { log.Fatalf("Oops! Failed reading body of the request.\n %s", err) http.Error(w, err.Error(), 500) } fmt.Fprintf(w, "%s\n", string(contents)) 

Dado que el servicio HTTP (S) ya se está ejecutando, debe proporcionar un certificado y una clave de servidor. Ambos objetos se almacenan en el directorio cert con los nombres server.crt y server.key.

A continuación se muestra un ejemplo de certificado y clave.

./cert/server.crt

----- COMIENCE EL CERTIFICADO -----
MIID + zCCAuOgAwIBAgIJAPsvGCCAC2i + MA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
VQQGEwJMSzEQMA4GA1UECAwHV2VzdGVybjEQMA4GA1UEBwwHQ29sb21ibzESMBAG
A1UECgwJTERDTEFLTUFMMRQwEgYDVQQLDAtFbmdpbmVlcmluZzESMBAGA1UEAwwJ
bG9jYWxob3N0MSIwIAYJKoZIhvcNAQkBFhNsZGNsYWttYWxAZ21haWwuY29tMB4X
DTE5MDQyMDA1MjczM1oXDTIwMDQxOTA1MjczM1owgZMxCzAJBgNVBAYTAkxLMRAw
DgYDVQQIDAdXZXN0ZXJuMRAwDgYDVQQHDAdDb2xvbWJvMRIwEAYDVQQKDAlMRENM
QUtNQUwxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
IjAgBgkqhkiG9w0BCQEWE2xkY2xha21hbEBnbWFpbC5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9PKAOlJcOBUI9CGnVjMjQHNRqYv01CaUdC4 / e
YFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK2WP3V0FLv5hM7Nxsgf25WNHa
zi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaHy5DNH0kPnSiPiy7PRKToEUn6
oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1vDCqrJbr3YwWOzFCq8kEJFslK
B7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lvSJZ5S / LZD5teDDg6Ax3Mvthj
kMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXjsmATEgtLAgMBAAGjUDBOMB0G
A1UdDgQWBBQ1CmWXmrHOv6b8f763 / bk80EpbajAfBgNVHSMEGDAWgBQ1CmWXmrHO
v6b8f763 / bk80EpbajAMBgNVHRMEBTADAQH / MA0GCSqGSIb3DQEBCwUAA4IBAQAH
D51Uoe2K4N9 / GxRgww5mMW2dUJ7Hc / tGsr / J1fNqHY8SXNAn5i + GwI + xBvwxFHL3
KZHbfq7eYDE5EItt3cZp5ySSscdTEay9ReH2 + 8k32gpH46CMwPV3XvtQuBVVAC4u
szrq1eWKhYI2zf4iUVpwvq89OynVGIp0atng + q3A2cBhi3NGo6Ho1s2rywQyqiq8
up4PUSVQ6WBoJFx5PEEDxD84VMS7Pan6dT34b9n56tq5R06retZTUZ8jMM88CGX4
88pSPU + XImp6DdNVBmW6Lz76jiSNHLkZGm4jumjeyUGzBjBEBOgSegeWlinMtWE9
gaVxeUHrqHk8xzwJ4oIu
----- FINALIZAR CERTIFICADO -----

./cert/server.key

----- COMIENCE LA CLAVE PRIVADA -----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9PKAOlJcOBUI9
CGnVjMjQHNRqYv01CaUdC4 / eYFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK
2WP3V0FLv5hM7Nxsgf25WNHazi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaH
y5DNH0kPnSiPiy7PRKToEUn6oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1v
DCqrJbr3YwWOzFCq8kEJFslKB7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lv
SJZ5S / LZD5teDDg6Ax3MvthjkMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXj
smATEgtLAgMBAAECggEAbaS2yDvn2cPKQTqit4y + vXY2zP1V4GkaNd4BGcOTZnRj
fOIg25EXoln8tEiadva888BpREKvkakUYlraxPDVcGIuiEOk42nd7Io97R0Q2cY7
ThxcJHb2ZxmTctdSUCBvFJTm1ySzve3pOb0ExRSfbGCOo7zs / kKzmZKK3qFlffGS
Ga9O7hyLOuXPU22CM + 5Lq0JPTER73z0DpAweZc0L14j6dzhcG3qUwk0K6K47VZgE
NhEORul7xDj91bh2iEoSbaQe8HxLaMQoMXOC / 9oey2UKKTe9WZE3 + XCvg + vkw / sS
biQ + b4EZ9LuhAhCZ0UE6 + y7PZY + 8G / YsbGg0Zo8cAQKBgQDyTuG47rWBgbdHsEB /
MSKGU6w + a1SdLk6jG + Enji5Q624 / h0xt5nF9ah2eRf3Rlhn9WEKM / uE9ouEODBKE
8rnIDsjufEMI8moPEloRBSsxPNw + fNMSSCZjL + qPtTJUbRio7WA23sfdnE57ygBa
wlPQ9UBBWSm2se4veEZtHjtngQKBgQDH7gnH5Att6ZYazRTgD72g0C5v1l4LYVEQ
jxdBcs6TJA8wHfifZ45F67W95QunmM813UxfS + ybdjytlb8 / lmi2BnK6lDx5HWIL
31jnbg2CxCrNv9oZLjKVDmkp4WUcEp5W33R1 / MGDTRfyARP + 6QYQO / ATMdqtm5Uu
cD6clrL4ywKBgQCQ0niy0WmGaAMlQ8CoxLM / 2c6 + 1 + OQtlalwkoGHEKudqhELBeQ
MAVw0fW13Vtg4vfRpejQ4J26 + xjMDocbEv / bBIsvjvF57XlaXLucJJy2Jwv0BSMa
cCkRa1gkYEYek74DaSzyXqDSYVO / RPKFTFRQNeUbqbD20s3rbVWablFPAQKBgB5y
zUCJJYh2w6qPQzegjhO4wOm9bxMyngL0l + ka0AUuv7VnSx8TyWIytLoX8P90UVJ1
wpTc3ksK5dDV9ot7n7ThJIXv34nehLkkKckNRLd + oro1FsUw + PkkebWsIxb0avL2
EymI9fvGOPhdW6s91 / OO / VAfDpvUDxNEevSkKtujAoGAcMOsXtn / UyT3Lssxgla3
K + DCaFhAQPSUXOmpZwEbQ0yQlksDe4flsam8bEDI5D5iHx1ziSfh583qJl3BEZ5u
VZTEO2YLvT9QRz7pv2qspqj7nzSyBU2BFAajq43 / G1b8FHfVgN + YdVtzVrigfql5
2a + JxOxFfpjnGQ7RfSxSb + Q =
----- FINALIZAR CLAVE PRIVADA -----

Es hora de probar el servicio con el siguiente comando:

 $ go run http_server.go 

La respuesta debería ser así:

 Go Backend: { HTTPVersion = 1 }; serving on https://localhost:9191/hello/sayHello 

Ahora iniciamos el servicio utilizando la solicitud HTTP / 1.1 POST (modos inseguros y seguros):

 $ curl -k -v https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

A continuación se muestra el resultado del programa. La primera parte son los detalles del protocolo de enlace del servidor y el cliente TLS. El segundo es los detalles de la solicitud y respuesta HTTP / 1.1. Al final está el mensaje de texto "¡Hola!".

* Intentando 127.0.0.1 ...
* Conectado al puerto localhost (127.0.0.1) 9191 (# 0)
* ALPN, que ofrece http / 1.1
* Selección de cifrado: TODOS: EXPORTAR: EXPORT40: EXPORT56: ANULAR: BAJO: RC4: @STRENGTH
* establecer correctamente ubicaciones de verificación de certificado:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: ninguno
* TLSv1.2 (OUT), encabezado TLS, estado del certificado (22):
* TLSv1.2 (OUT), protocolo de enlace TLS, saludo del cliente (1):
* TLSv1.2 (IN), protocolo de enlace TLS, Server hello (2):
* TLSv1.2 (IN), protocolo de enlace TLS, Certificado (11):
* TLSv1.2 (IN), protocolo de enlace TLS, intercambio de claves del servidor (12):
* TLSv1.2 (IN), protocolo de enlace TLS, servidor terminado (14):
* TLSv1.2 (OUT), protocolo de enlace TLS, intercambio de clave de cliente (16):
* TLSv1.2 (OUT), cifrado de cambio TLS, saludo del cliente (1):
* TLSv1.2 (OUT), apretón de manos TLS, terminado (20):
* TLSv1.2 (IN), cifrado de cambio TLS, Hola de cliente (1):
* TLSv1.2 (IN), apretón de manos TLS, terminado (20):
* Conexión SSL usando TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, servidor aceptado para usar http / 1.1
* Certificado del servidor:
* sujeto: C = LK; ST = occidental; L = Colombo; O = LDCLAKMAL; OU = Ingeniería; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
* fecha de inicio: 20 de abril 03:03:58 GMT de 2019
* Fecha de caducidad: 19 de abril 03:03:58 2020 GMT
* emisor: C = LK; ST = occidental; L = Colombo; O = LDCLAKMAL; OU = Ingeniería; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
* Resultado de verificación del certificado SSL: certificado autofirmado (18), continuando de todos modos.
> POST / hello / sayHello HTTP / 1.1
> Anfitrión: localhost: 9191
> Usuario-Agente: curl / 7.46.0
> Aceptar: * / *
> Contenido-Longitud: 9
> Tipo de contenido: application / x-www-form-urlencoded
>
* carga completamente enviada: 9 de 9 bytes
<HTTP / 1.1 200 OK
<Fecha: sáb, 20 abr 2019 06:56:19 GMT
<Contenido-Longitud: 10
<Tipo de contenido: texto / sin formato; charset = utf-8
<
Hola vamos
* La conexión n. ° 0 para alojar el host local se dejó intacta

Cliente HTTP / 1.1


El nuevo código es un ejemplo de cómo escribir un cliente HTTP / 1.1 simple en Go. Este cliente envía una solicitud POST HTTP (S) a localhost : 9191 / hello / sayHello con el mensaje "Hello Go!".

 package main import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" "net/http" ) func main() { client := &http.Client{} // Create a pool with the server certificate since it is not signed // by a known CA caCert, err := ioutil.ReadFile("./cert/server.crt") if err != nil { log.Fatalf("Reading server certificate: %s", err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Create TLS configuration with the certificate of the server tlsConfig := &tls.Config{ RootCAs: caCertPool, } // Use the proper transport in the client client.Transport = &http.Transport{ TLSClientConfig: tlsConfig, } // Perform the request resp, err := client.Post("https://localhost:9191/hello/sayHello", "text/plain", bytes.NewBufferString("Hello Go!")) if err != nil { log.Fatalf("Failed get: %s", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalf("Failed reading response body: %s", err) } fmt.Printf("Got response %d: %s %s", resp.StatusCode, resp.Proto, string(body)) 

Como el servicio HTTP (S) también se inicia aquí, debe proporcionar un certificado y una clave. Los datos necesarios se colocan en el directorio cert con los nombres server.crt y server.key.

Para iniciar el cliente, debe inicializar el servidor. Para hacer esto, realizamos las acciones descritas en la primera sección, o iniciamos cualquier servicio HTTP (S) con el puerto 9191 y la ruta / hello / sayHello.

 $ go run http_client.go 

La salida debería ser así:

 Got response 200: HTTP/1.1 Hello Go! 

El cliente muestra el funcionamiento del comando curl:

 $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

Después de que termine de trabajar con HTTP / 1.1, debe intentar repetir lo mismo para HTTP / 2.

Servidor HTTP / 2


Como en la sección anterior, primero debe crear un servidor echo.

 package main import ( "fmt" "golang.org/x/net/http2" "io/ioutil" "log" "net/http" ) func main() { var httpServer = http.Server{ Addr: ":9191", } var http2Server = http2.Server{} _ = http2.ConfigureServer(&httpServer, &http2Server) http.HandleFunc("/hello/sayHello", echoPayload) log.Printf("Go Backend: { HTTPVersion = 2 }; serving on https://localhost:9191/hello/sayHello") log.Fatal(httpServer.ListenAndServeTLS("./cert/server.crt", "./cert/server.key")) } func echoPayload(w http.ResponseWriter, req *http.Request) { log.Printf("Request connection: %s, path: %s", req.Proto, req.URL.Path[1:]) defer req.Body.Close() contents, err := ioutil.ReadAll(req.Body) if err != nil { log.Fatalf("Oops! Failed reading body of the request.\n %s", err) http.Error(w, err.Error(), 500) } fmt.Fprintf(w, "%s\n", string(contents)) 

Para verificar el servidor, debe enviar el comando:

 $ go run http2_server.go 

La respuesta debería ser así:

 Go Backend: { HTTPVersion = 2 }; serving on https://localhost:9191/hello/sayHello 

Ahora que el servidor se está ejecutando, puede inicializarlo utilizando solicitudes HTTP / 1.1 o HTTP / 2. Los comandos POST HTTP / 1.1 para probar el funcionamiento del servidor se detallan a continuación:

 $ curl -k -v https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

Y lo mismo, pero con HTTP / 2 POST:

 $ curl -k -v --http2 https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --http2 --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

A continuación se muestra un ejemplo de salida de operación del servidor. La primera parte es un protocolo de enlace TLS entre el servidor y el cliente, la segunda es los detalles de la solicitud y respuesta HTTP / 2.

* Intentando 127.0.0.1 ...
* Conectado al puerto localhost (127.0.0.1) 9191 (# 0)
* ALPN, que ofrece h2
* ALPN, que ofrece http / 1.1
* Selección de cifrado: TODOS: EXPORTAR: EXPORT40: EXPORT56: ANULAR: BAJO: RC4: @STRENGTH
* establecer correctamente ubicaciones de verificación de certificado:
* CAfile: src / hello / cert / server.crt
CApath: ninguno
* TLSv1.2 (OUT), encabezado TLS, estado del certificado (22):
* TLSv1.2 (OUT), protocolo de enlace TLS, saludo del cliente (1):
* TLSv1.2 (IN), protocolo de enlace TLS, Server hello (2):
* TLSv1.2 (IN), protocolo de enlace TLS, Certificado (11):
* TLSv1.2 (IN), protocolo de enlace TLS, intercambio de claves del servidor (12):
* TLSv1.2 (IN), protocolo de enlace TLS, servidor terminado (14):
* TLSv1.2 (OUT), protocolo de enlace TLS, intercambio de clave de cliente (16):
* TLSv1.2 (OUT), cifrado de cambio TLS, saludo del cliente (1):
* TLSv1.2 (OUT), apretón de manos TLS, terminado (20):
* TLSv1.2 (IN), cifrado de cambio TLS, Hola de cliente (1):
* TLSv1.2 (IN), apretón de manos TLS, terminado (20):
* Conexión SSL usando TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, servidor aceptado para usar h2
* Certificado del servidor:
* sujeto: C = LK; ST = occidental; L = Colombo; O = LDCLAKMAL; OU = Ingeniería; CN = localhost; emailAddress=ldclakmal@gmail.com
* fecha de inicio: 20 de abril a las 05:27:33 GMT del 2019
* Fecha de caducidad: 19 de abril 05:27:33 2020 GMT
* nombre común: localhost (coincidente)
* emisor: C = LK; ST = occidental; L = Colombo; O = LDCLAKMAL; OU = Ingeniería; CN = localhost; emailAddress=ldclakmal@gmail.com
* Certificado SSL verificar ok.
* Usando HTTP2, el servidor admite usos múltiples
* Estado de conexión cambiado (HTTP / 2 confirmado)
* Conjunto TCP_NODELAY
* Copia de datos HTTP / 2 en el búfer de flujo al búfer de conexión después de la actualización: len = 0
* Usando Stream ID: 1 (fácil manejo 0x10ddf20)
> POST / hello / sayHello HTTP / 1.1
> Anfitrión: localhost: 9191
> Usuario-Agente: curl / 7.46.0
> Aceptar: * / *
> Contenido-Longitud: 9
> Tipo de contenido: application / x-www-form-urlencoded
>
* Estamos completamente cargados y bien
<HTTP / 2.0 200
<tipo de contenido: texto / sin formato; charset = utf-8
<longitud del contenido: 10
<fecha: sáb, 20 abr 2019 06:54:50 GMT
<
Hola vamos
* La conexión n. ° 0 para alojar el host local se dejó intacta

Cliente HTTP / 2


La última parte es crear un cliente HTTP / 2. Aquí está la implementación de enviar una solicitud POST HTTP (S) para localhost : 9191 / hello / sayHello:

 package main import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "golang.org/x/net/http2" "io/ioutil" "log" "net/http" ) func main() { client := &http.Client{} // Create a pool with the server certificate since it is not signed // by a known CA caCert, err := ioutil.ReadFile("./cert/server.crt") if err != nil { log.Fatalf("Reading server certificate: %s", err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Create TLS configuration with the certificate of the server tlsConfig := &tls.Config{ RootCAs: caCertPool, } // Use the proper transport in the client client.Transport = &http2.Transport{ TLSClientConfig: tlsConfig, } // Perform the request resp, err := client.Post("https://localhost:9191/hello/sayHello", "text/plain", bytes.NewBufferString("Hello Go!")) if err != nil { log.Fatalf("Failed get: %s", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalf("Failed reading response body: %s", err) } fmt.Printf("Got response %d: %s %s", resp.StatusCode, resp.Proto, string(body)) 

Como antes, necesita una clave y un certificado para trabajar. Se almacenan en cert con los nombres server.crt y server.key.

Lanzamiento del cliente:

 $ go run http2_client.go 

Y la respuesta esperada:

 Got response 200: HTTP/2 Hello Go! 

Detallando el trabajo del cliente:

 $ curl -v --http2 --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

Eso es todo El trabajo es relativamente simple, pero da una idea de cómo implementar servicios básicos de red en Go.

Skillbox recomienda:

Source: https://habr.com/ru/post/451032/


All Articles