Anatomía de curl: cómo usar cURL para probar la respuesta de un servidor de origen

 Matt Torrisi 
 Manager, Sales Engineering - Enterprise, Fastly

¿Alguna vez has tenido una de esas semanas en las que todo parece seguir el mismo patrón? Como ingeniero de ventas, trabajo con nuestros clientes para crear y desplegar sistemas, y la semana pasada tuvo un claro patrón.
Me llegó lo que parecía en principio un caso de uso único, en el que usé una herramienta concreta (cURL) de una manera particular para probar cómo respondía un servidor de origen. Este procedimiento te puede servir, por ejemplo, si quieres hacer pruebas cuando una aplicación responde con mensajes de error raros. Bueno, ¡pues no paraban de llegar casos de ese tipo! Expliqué el funcionamiento a varios compañeros y a unos cuantos clientes que intentaban resolver problemas con sus despliegues, y pensé que estaría bien hacer una entrada en el blog para todo el mundo. Empecemos.
### El trasfondo de cURL
Client URL (también llamado cURL o *curl* ) [fue lanzado en 1997 por Daniel Stenberg](https://en.wikipedia.org/wiki/CURL), que se ha mantenido al frente del proyecto desde entonces. En su momento se creó para automatizar la obtención del tipo de cambio de divisas para usuarios de IRC, pero en la actualidad se usa para todo tipo de recuperaciones de URL. Desde su lanzamiento, Daniel ha seguido desarrollando y añadiendo funcionalidades al proyecto a medida que se convertía en una pieza básica de otros proyectos. Es todo un héroe.
*Curl* es una utilidad de línea de comandos que permite enviar una petición HTTP a una URL y recibir el resultado. Viene por defecto en sistemas operativos como macOS y muchas distribuciones de Linux. Como la gran mayoría de Internet gira en torno a HTTP, es una herramienta ideal para rebuscar en páginas web, API o cualquier otro elemento con una interfaz HTTP.
En nuestra demostración, utilizaremos *curl* para simular la experiencia de solicitar una página web desde el navegador. Así podemos tener un control total de la petición y resolver problemas con mucha más facilidad.
A continuación tenemos un comando *curl* sencillo que ejecuté desde la aplicación Terminal de mi MacBook. Este comando solicita la página de inicio de fastly.com y muestra el HTML completo. Atención, porque este resultado incluye mucho ruido que ya limpiaremos luego.
    curl https://www.fastly.com/
### Marcas
*Curl* abre muchas posibilidades al poder manipularse la petición mediante marcas. Por ejemplo, enviar `-I` limita la respuesta a solamente los encabezados del servidor remoto, sin el contenido.
Las marcas que uso en casi todos los *curl* son `-svo`, que significan *modo silencioso* (s), *modo detallado* (v) y *escribiendo en un archivo* (o), que escribo en `/dev/null`. El comando *curl* completo queda como `curl -svo /dev/null https://www.fastly.com`. Así, puedo centrarme en la petición detallada sin las distracciones de un cuerpo de respuesta lleno de ruido. Los encabezados tienen una pequeña marca que indica si se han enviado o recibido, y la negociación SSL se muestra antes de la petición principal.
A continuación puedes ver el comando tal como va escrito ($), la negociación de conexión y SSL (*), la petición (>) y la respuesta (<).
```shell
$ curl -svo /dev/null https://www.fastly.com/
*  Trying 199.232.77.57...
* TCP_NODELAY set
* Connected to www.fastly.com (199.232.77.57) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [228 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [102 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [2828 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [300 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [37 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Fastly, Inc.; CN=www.fastly.com
* start date: Mar  3 21:56:03 2021 GMT
* expire date: Apr  4 21:56:03 2022 GMT
* subjectAltName: host "www.fastly.com" matched cert's "www.fastly.com"
* issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fe1f980aa00)
> GET / HTTP/2
> Host: www.fastly.com
> User-Agent: curl/7.64.1
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200 
< alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
< etag: "5c770df920f8c90e4c4532c32aea6ec3"
< content-type: text/html
< accept-ranges: bytes
< date: Mon, 09 Aug 2021 15:38:35 GMT
< x-served-by: cache-pwk4963-PWK
< x-cache: HIT
< x-cache-hits: 2
< x-timer: S1628523515.208976,VS0,VE0
< vary: Accept-Encoding
< x-xss-protection: 1; mode=block
< x-frame-options: DENY
< x-content-type-options: nosniff
< cache-control: max-age=0, private, must-revalidate
< server: Artisanal bits
< strict-transport-security: max-age=31536000
< content-length: 777219
< 
{ [1113 bytes data]
* Connection #0 to host www.fastly.com left intact
* Closing connection 0
```
### Nombre de SSL y URL
En el ejemplo anterior, el nombre del certificado SSL, el encabezado del host y el nombre del DNS tienen el mismo valor: `www.fastly.com`. A veces, eso es todo lo que necesitas. A partir de aquí, sin embargo, separaremos estos elementos para repasar aspectos de distintos objetivos de resolución de problemas.
El uso actual de `https://www.fastly.com/` muestra la URL que se solicita. El nombre de host utilizado dentro de la URL, sea cual sea (por ejemplo, `www.fastly.com`), será el valor que *curl* utiliza para solicitar y verificar el nombre del certificado SSL. Esto es importante para validar que TLS funcione como esperas, y que tu sitio esté protegido por el certificado previsto. Veamos el ejemplo siguiente:
$ curl -svo /dev/null https://www.fastly.com/ *  Trying 199.232.77.57... * TCP_NODELAY set * Connected to www.fastly.com (199.232.77.57) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: *  CAfile: /etc/ssl/cert.pem  CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): } [228 bytes data] * TLSv1.2 (IN), TLS handshake, Server hello (2): { [102 bytes data] * TLSv1.2 (IN), TLS handshake, Certificate (11): { [2828 bytes data] * TLSv1.2 (IN), TLS handshake, Server key exchange (12): { [300 bytes data] * TLSv1.2 (IN), TLS handshake, Server finished (14): { [4 bytes data] * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): } [37 bytes data] * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): } [1 bytes data] * TLSv1.2 (OUT), TLS handshake, Finished (20): } [16 bytes data] * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): { [1 bytes data] * TLSv1.2 (IN), TLS handshake, Finished (20): { [16 bytes data] * SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305 * ALPN, server accepted to use h2 * Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=Fastly, Inc.; CN=www.fastly.com
*  start date: Mar  3 21:56:03 2021 GMT *  expire date: Apr  4 21:56:03 2022 GMT *  subjectAltName: host "www.fastly.com" matched cert's "www.fastly.com" *  issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018 *  SSL certificate verify ok. --->{Truncated for readability}<---
$ curl -svo /dev/null https://www.fastly.com/ -H "host: fastly.com" *  Trying 199.232.77.57... * TCP_NODELAY set * Connected to www.fastly.com (199.232.77.57) port 443 (#0) --->{Truncated for readability}<--- * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7fa15480aa00) > GET / HTTP/2
> Host: fastly.com
> User-Agent: curl/7.64.1 > Accept: */* >  * Connection state changed (MAX_CONCURRENT_STREAMS == 100)! < HTTP/2 301 < retry-after: 0 < accept-ranges: bytes < date: Mon, 09 Aug 2021 16:26:07 GMT < x-served-by: cache-pwk4938-PWK < x-cache: HIT < x-cache-hits: 0 < cache-control: max-age=0, private, must-revalidate < server: Artisanal bits < strict-transport-security: max-age=31536000 < location: https://www.fastly.com/ < content-length: 0 <  { [0 bytes data] * Connection #0 to host www.fastly.com left intact * Closing connection 0
$ curl -svo /dev/null https://www.fastly.com/ -H "host: fastly.com" --resolve www.fastly.com:443:151.101.185.57
* Added www.fastly.com:443:151.101.185.57 to DNS cache * Hostname www.fastly.com was found in DNS cache *  Trying 151.101.185.57...
* TCP_NODELAY set * Connected to www.fastly.com (151.101.185.57) port 443 (#0) --->{Truncated for readability}<---
curl -svo /dev/null https://www.certificate-name.com/path/to/resource/ -H "host: www.expected-host.com" --resolve www.certificate-name.com:443:1.2.3.4