Pubblicato originariamente nell'agosto 2021. Aggiornato a maggio 2025.
Vi capitano mai quelle settimane che sembrano seguire un filo conduttore? In qualità di consulente tecnico commerciale, lavoro con i nostri clienti per costruire e distribuire sistemi e la scorsa settimana ho notato un tema ricorrente.
Mi è capitato di imbattermi in quello che pensavo fosse un caso d'uso di nicchia, in cui ho utilizzato uno strumento specifico (cURL) in un modo specifico per testare la risposta di un server di origine (utile, ad esempio, per testare quando un'applicazione risponde con un messaggio di errore anomalo). E si è ripresentato più volte! Ho spiegato il processo ad alcuni colleghi che erano meno esperti, così come ad alcuni clienti che stavano risolvendo problemi di distribuzione, e ho pensato che sarebbe stato utile parlarne nel blog. Iniziamo.
Origini di cURL
Client URL (noto anche come cURL o curl) è stato rilasciato nel 1997 da Daniel Stenberg, che da allora ha mantenuto diligentemente il progetto. Il suo scopo iniziale era automatizzare l'acquisizione dei tassi di cambio per gli utenti IRC, ma nel tempo è stato esteso alla gestione del recupero di qualsiasi URL. Daniel ha continuato a svilupparlo e ad aggiungervi funzionalità facendolo diventare l'infrastruttura principale per altri progetti. Un vero eroe.
Curl è un utilità da riga di comando che permette di inviare una richiesta HTTP a un URL e ricevere il risultato. Viene distribuito di default su sistemi operativi come MacOS e molte distribuzioni Linux. Inoltre, dato che gran parte di Internet comunica tramite HTTP, questo strumento è ottimo per esaminare pagine web, API o qualsiasi altra interfaccia HTTP.
Per la nostra demo, useremo curl per simulare l'esperienza del browser nella richiesta di una pagina web. Questo ci permette di avere pieno controllo sulla richiesta, rendendo molto più semplice la risoluzione dei problemi.
Di seguito è riportato un semplice comando curl che ho eseguito dall'applicazione terminale sul mio MacBook. Questa richiede la home page di Fastly.com e mostra il codice HTML completo. Attenzione: si tratta di un output verboso che puliremo in seguito.
$ curl https://www.fastly.com/Bandiere
Il vero potere di curl è la possibilità di manipolare la richiesta usando i flag. Ad esempio, il passaggio a -I limita la risposta mostrata solo alle intestazioni del server remoto, non al contenuto.
Le flag che uso su quasi ogni comando curl sono -svo, che si legge come modalità silenziosa (s), verbosa (v), e scrittura su file (o), che indirizzo verso /dev/null.
Il comando curl completo è curl -svo /dev/null https://www.fastly.com. Questo mi permette di concentrarmi sulla richiesta dettagliata senza le distrazioni di un corpo della risposta verboso. Le intestazioni hanno un piccolo flag che mostra se sono state inviate o ricevute e la negoziazione SSL viene mostrata prima della richiesta principale.
Di seguito vediamo il comando così come è scritto ($), la connessione e la negoziazione SSL (*), la richiesta (>) e la risposta (<).
$ 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 URL e nome SSL
Lo scenario sopra riportato mostra una situazione in cui il nome del certificato SSL, l'intestazione host e il nome DNS hanno tutti lo stesso valore, ovvero www.fastly.com. A volta basta questo! Da qui in poi, però, separeremo questi elementi, con l'intento di ispezionare aspetti diversi per obiettivi di risoluzione dei problemi diversi.
L'uso attuale di https://www.fastly.com/ mostra l'URL richiesto. Qualunque nome host sia usato all'interno dell'URL (ad esempio www.fastly.com) sarà il valore che curl utilizza per richiedere e verificare il nome del certificato SSL. Questo è importante per verificare che il TLS funzioni nel modo previsto e che il sito sia sicuro con il certificato previsto. Ecco un esempio qui di seguito:
$ 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}<---
Percorso
Gli altri due elementi che rimangono nell'URL originale sono lo schema, come HTTP o HTTPS, e il percorso della risorsa specifica richiesta. Mi piace pensare all'URL come all'obiettivo e tutto il resto è semplicemente come o dove lo chiediamo.
Intestazione host
Un tipico server web può ospitare più siti con nomi di dominio diversi, come blog.example.com o docs.example.com. Anche se i siti potrebbero risiedere sullo stesso sistema, il codice sorgente e/o il percorso URL potrebbero essere diversi.
Se vogliamo cambiare l'intestazione dell'host, possiamo passarla definendo esplicitamente l'intestazione all'interno di curl. Le intestazioni possono essere dichiarate con un --header o, in breve, con il flag -H. Poi il valore viene incapsulato con le virgolette e viene definito il nome dell'intestazione. Il comando curl si presenta così:
$ curl -svo /dev/null https://www.fastly.com/ -H “host: blog.fastly.com”Questa richiesta richiede l'host di blog.fastly.com, utilizzando il certificato e la posizione di www.fastly.com. Ciò è particolarmente utile quando si desidera verificare la differenza tra l'apice di un dominio, ad esempio fastly.com e www.fastly.com. Qui vediamo che fornisce correttamente un 301 a www.
$ 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
Risolvere
Di tutte le opzioni all'interno di curl, --resolve è probabilmente la più sottovalutata. Si noti che all'inizio di ogni curl mostrato sopra, Trying X.X.X.X risulterà come Indirizzo IP, risolvendo il nome di dominio dal DNS. Ma ci sono casi in cui il nome DNS non è il vero obiettivo a cui puntare. Potrebbe trattarsi di una distribuzione iniziale da qualche parte e stiamo verificando che il servizio si risolva correttamente prima di cambiare il DNS per trasmettere questa modifica. O forse c'è una catena di reverse proxy, dove il DNS pubblico risolve solo all'inizio della catena, ma vogliamo sapere come risponde l'origine stessa.
Per aggirare questi problemi, possiamo risolvere noi stessi il nome di dominio di curl e fornire un indirizzo IP a piacere. Il procedimento da seguire è in due fasi:
1. A meno che non si conosca già l'IP di destinazione, potrebbe essere necessario eseguire una risoluzione DNS dell'host per ottenere l'IP. Mi piace utilizzare Dig, un'altra utility standard che esegue la query DNS dalla riga di comando.
$ dig www.fastly.com +short
prod.www-fastly-com.map.fastly.net.
151.101.185.57 2. Successivamente, possiamo assegnare questo IP affinché venga utilizzato da curl durante la richiesta HTTP utilizzando l'opzione --resolve, insieme al nome di dominio da sostituire, alla porta e all'IP da utilizzare. Il risultato è questo:
$ 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}<---
Alternativa Connect-to
Come alternativa alla funzione resolve vista sopra, è possibile usare --connect-to e specificare un IP o un host a cui connettersi. Questo è più semplice, ma c'è un caveat sulla precisione quando si utilizza un host. Connect-to eseguirà una risoluzione DNS, ma poi dipenderemo dalla risoluzione DNS per ottenere l'IP corretto. Questo non è un problema se si sa per certo che esiste una mappatura 1:1 tra host e IP. Tuttavia, a volte vi sono più record A e si vogliono testare tutti gli IP, oppure l'host utilizza un bilanciamento del carico basato su DNS; in questi casi, posizioni o tentativi diversi possono produrre risultati differenti. Utilizzando la funzione --resolve o --connect-to con un IP, si può essere espliciti. Ciò significa che condividendo questo curl con un collega, quest'ultimo avrà più probabilità di replicare gli stessi risultati.
Un piccolo vantaggio di connect-to è che permette di saltare la definizione esplicita del nome di dominio per il quale si vuole risolvere il DNS, come invece avviene con resolve, così come quella della porta. Quindi risulterà ::151.101.185.57 oppure è possibile usare ::target.host.fastly.com.
$ curl -svo /dev/null https://www.fastly.com/ -H "host: fastly.com" --connect-to ::151.101.185.57
* Connecting to hostname: 151.101.185.57
* Trying 151.101.185.57...
* TCP_NODELAY set
* Connected to 151.101.185.57 (151.101.185.57) port 443 (#0)
--->{Truncated for readability}<--- Metti tutto insieme
Vale la pena sottolineare che esistono molte tecniche per eseguire una richiesta HTTP. Curl offre metodi alternativi, ognuno con i suoi pro e contro, e sembra che ogni tecnico abbia il suo preferito. Questo è semplicemente il mio.
Per ricapitolare, scrivendo su /dev/null, manipolando l'intestazione host e usando le funzionalità di risoluzione, possiamo individuare esattamente dove vogliamo inviare una richiesta su Internet, quale certificato SSL verificare e quale host il server dovrebbe attendere con un output prevedibile e facile da gestire. In questo modo la risoluzione dei problemi è più semplice e verranno risparmiate innumerevoli ore di frustrazione. Da provare oggi stesso!
Il frammento qui di seguito mostra la forma completa, colorata per facilitarne la consultazione:

