Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Costruzione di un Server Web Single-Threaded

Inizieremo creando un server web single-threaded. Prima di cominciare, diamo uno sguardo veloce ai protocolli coinvolti nella costruzione dei server web. I dettagli di questi protocolli sono oltre lo scopo di questo corso, ma una breve panoramica ti darà le informazioni necessarie.

I due principali protocolli coinvolti nei server web sono il Protocollo di Trasferimento di Testo Ipersincrono (HTTP) e il Protocollo di Controllo della Trasmissione (TCP). Entrambi i protocolli sono protocolli di richiesta-risposta, il che significa che un client inizia le richieste e un server ascolta le richieste e fornisce una risposta al client. I contenuti di tali richieste e risposte sono definiti dai protocolli.

Il TCP è il protocollo di livello inferiore che descrive i dettagli su come le informazioni vengono trasferite da un server all’altro ma non specifica quali siano tali informazioni. L’HTTP si basa sul TCP definendo i contenuti delle richieste e delle risposte. È tecnicamente possibile utilizzare l’HTTP con altri protocolli, ma nella stragrande maggioranza dei casi, l’HTTP invia i suoi dati tramite TCP. Lavoreremo con i byte grezzi di TCP e le richieste e le risposte HTTP. Ascolto della Connessione TCP

Il nostro server web deve ascoltare una connessione TCP, quindi questa è la prima parte su cui lavoreremo. La libreria standard offre un modulo std::net che ci permette di farlo. Creiamo un nuovo progetto nel solito modo:

$ cargo new hello Progetto di creazione binaria (applicazione) hello $ cd hello

Ora inserisci il codice nel Listato 20-1 in src/main.rs per iniziare. Questo codice ascolterà all’indirizzo locale 127.0.0.1:7878 per flussi TCP in ingresso. Quando riceve un flusso in ingresso, stamperà “Connessione stabilita!”.

Nome file: src/main.rs

rust

use std::net::TcpListener; fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); for stream in listener.incoming() {
let stream = stream.unwrap();

println!("Connessione stabilita!");
}
}

Listato 20-1: Ascolto dei flussi in ingresso e stampa di un messaggio quando riceviamo un flusso

Utilizzando TcpListener, possiamo ascoltare le connessioni TCP all’indirizzo 127.0.0.1:7878. Nell’indirizzo, la sezione prima dei due punti è un indirizzo IP che rappresenta il tuo computer (lo stesso su ogni computer e non rappresenta specificamente il computer degli autori), e 7878 è la porta. Abbiamo scelto questa porta per due ragioni: l’HTTP non è normalmente accettato su questa porta quindi il nostro server è improbabile che entri in conflitto con altri server web che potresti avere in esecuzione sul tuo computer, e 7878 è rust digitato su un telefono.

La funzione bind in questo scenario funziona come la funzione new nel senso che restituirà una nuova istanza TcpListener. La funzione è chiamata bind perché, in networking, connettersi a una porta da ascoltare è noto come “binding a una porta”.

La funzione bind restituisce un Result<T, E>, che indica che è possibile che il binding fallisca. Ad esempio, la connessione alla porta 80 richiede privilegi di amministratore (gli utenti non amministratori possono ascoltare solo su porte superiori a 1023), quindi se provassimo a connetterci alla porta 80 senza essere amministratori, il binding non funzionerebbe. Il binding non funzionerebbe anche, ad esempio, se eseguissimo due istanze del nostro programma e quindi avessimo due programmi in ascolto sulla stessa porta. Poiché stiamo scrivendo un server di base solo a scopo di apprendimento, non ci preoccuperemo di gestire questi tipi di errori; invece, usiamo unwrap per fermare il programma se si verificano errori.

Il metodo incoming su TcpListener restituisce un iteratore che ci fornisce una sequenza di flussi (più specificamente, flussi di tipo TcpStream). Un singolo flusso rappresenta una connessione aperta tra il client e il server. Una connessione è il nome per l’intero processo di richiesta e risposta in cui un client si connette al server, il server genera una risposta e il server chiude la connessione. Come tale, leggeremo dal TcpStream per vedere cosa ha inviato il client e quindi scriveremo la nostra risposta allo stream per inviare i dati al client. Nel complesso, questo ciclo for processerà ogni connessione in sequenza e produrrà una serie di flussi da gestire.

Per ora, il nostro trattamento del flusso consiste nel chiamare unwrap per terminare il nostro programma se il flusso ha degli errori; se non ci sono errori, il programma stampa un messaggio. Aggiungeremo ulteriori funzionalità per il caso di successo nel prossimo elenco. Il motivo per cui potremmo ricevere errori dal metodo incoming quando un client si connette al server è che non stiamo effettivamente iterando sulle connessioni. Invece, stiamo iterando sui tentativi di connessione. La connessione potrebbe non essere riuscita per una serie di motivi, molti dei quali specifici del sistema operativo. Ad esempio, molti sistemi operativi hanno un limite al numero di connessioni aperte simultaneamente che possono supportare; nuovi tentativi di connessione oltre tale numero produrranno un errore finché alcune delle connessioni aperte non verranno chiuse.

Proviamo a eseguire questo codice! Invoca cargo run nel terminale e quindi carica 127.0.0.1:7878 in un browser web. Il browser dovrebbe mostrare un messaggio di errore come “Connessione resettata”, perché il server attualmente non sta inviando alcun dato. Ma quando guardi il tuo terminale, dovresti vedere diversi messaggi che sono stati stampati quando il browser si è connesso al server!

bash

Esecuzione di `target/debug/hello`

Connessione stabilita! Connessione stabilita! Connessione stabilita!

A volte, vedrai stampati più messaggi per una richiesta del browser; il motivo potrebbe essere che il browser sta facendo una richiesta per la pagina così come una richiesta per altre risorse, come l’icona favicon.ico che appare nella scheda del browser.

Potrebbe anche essere che il browser stia cercando di connettersi al server più volte perché il server non sta rispondendo con alcun dato. Quando lo stream esce dallo scope e viene rilasciato alla fine del ciclo, la connessione viene chiusa come parte dell’implementazione del drop. I browser a volte gestiscono le connessioni chiuse riprovando, perché il problema potrebbe essere temporaneo. Il fattore importante è che abbiamo ottenuto con successo un handle a una connessione TCP!

Ricorda di interrompere il programma premendo ctrl-c quando hai finito di eseguire una particolare versione del codice. Quindi riavvia il programma invocando il comando cargo run dopo aver apportato ogni insieme di modifiche al codice per assicurarti di eseguire il codice più recente. Lettura della Richiesta

Implementiamo la funzionalità per leggere la richiesta dal browser! Per separare i compiti di prima ottenere una connessione e poi prendere qualche azione con la connessione, inizieremo una nuova funzione per elaborare le connessioni. In questa nuova funzione handle_connection, leggeremo i dati dallo stream TCP e li stamperemo in modo da poter vedere i dati inviati dal browser. Cambia il codice in modo che assomigli al Listato 20-2.

Nome file: src/main.rs

rust

use std::{
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); for stream in listener.incoming() {
let stream = stream.unwrap(); handle_connection(stream);
}
} fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let http_request: Vec<_> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();

println!("Richiesta: {:#?}", http_request);
}

Listato 20-2: Lettura dal TcpStream e stampa dei dati

Portiamo in scope std::io::prelude e std::io::BufReader per ottenere accesso a trait e tipi che ci consentono di leggere dallo stream. Nel ciclo for nella funzione principale, invece di stampare un messaggio che dice che abbiamo stabilito una connessione, ora chiamiamo la nuova funzione handle_connection e le passiamo lo stream.

Nella funzione handle_connection, creiamo una nuova istanza BufReader che avvolge un riferimento mutabile allo stream. BufReader aggiunge il buffering gestendo le chiamate ai metodi del trait std::io::Read per noi.

Creiamo una variabile chiamata http_request per raccogliere le righe della richiesta che il browser invia al nostro server. Indichiamo che vogliamo raccogliere queste righe in un vettore aggiungendo l’annotazione di tipo Vec<_>.

BufReader implementa il trait std::io::BufRead, che fornisce il metodo lines. Il metodo lines restituisce un iteratore di Result<String, std::io::Error> suddividendo lo stream di dati ogni volta che vede un byte di nuova riga. Per ottenere ogni String, mappiamo e unwrap ogni Result. Il Result potrebbe essere un errore se i dati non sono validi UTF-8 o se c’è stato un problema nella lettura dallo stream. Di nuovo, un programma di produzione dovrebbe gestire questi errori in modo più elegante, ma scegliamo di fermare il programma nel caso di errore per semplicità.

Il browser segnala la fine di una richiesta HTTP inviando due caratteri di nuova riga di seguito, quindi per ottenere una richiesta dallo stream, prendiamo le righe fino a quando non otteniamo una riga che è una stringa vuota. Una volta raccolte le righe nel vettore, le stamperemo usando una formattazione debug piuttosto dettagliata in modo da poter dare un’occhiata alle istruzioni che il browser web sta inviando al nostro server.

Proviamo questo codice! Avvia il programma e fai nuovamente una richiesta in un browser web. Nota che otterremo comunque una pagina di errore nel browser, ma l’output del nostro programma nel terminale ora assomiglierà a questo:

$ cargo run Compilazione di hello v0.1.0 (file:///projects/hello) Finito dev [non ottimizzato + debuginfo] target(s) in 0.42s Running target/debug/hello Richiesta: [ “GET / HTTP/1.1”, “Host: 127.0.0.1:7878”, “User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0”, “Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8″, “Accept-Language: en-US,en;q=0.5”, “Accept-Encoding: gzip, deflate, br”, “DNT: 1”, “Connection: keep-alive”, “Upgrade-Insecure-Requests: 1”, “Sec-Fetch-Dest: document”, “Sec-Fetch-Mode: navigate”, “Sec-Fetch-Site: none”, “Sec-Fetch-User: ?1”, “Cache-Control: max-age=0”, ]

A seconda del tuo browser, potresti ottenere un output leggermente diverso. Ora che stiamo stampando i dati della richiesta, possiamo vedere perché otteniamo connessioni multiple da una richiesta del browser guardando il percorso dopo GET nella prima riga della richiesta. Se le connessioni ripetute stanno tutte richiedendo /, sappiamo che il browser sta cercando di ottenere / ripetutamente perché non sta ricevendo una risposta dal nostro programma.

Analizziamo questi dati della richiesta per capire cosa chiede il browser al nostro programma.

Esame Approfondito di una Richiesta HTTP

Benvenuto! In questo corso, esamineremo da vicino il funzionamento di un server web in Rust. Cominciamo con un’introduzione al Protocollo di Trasferimento di Testo Ipersincrono (HTTP), il protocollo di comunicazione principale utilizzato su Internet per il trasferimento di dati tra client e server.

HTTP: È un protocollo testuale che definisce il formato delle richieste e delle risposte scambiate tra client e server. Ogni richiesta HTTP segue un formato standard:

css

Metodo URI-Request Versione-HTTP CRLF
Intestazioni CRLF
Corpo del messaggio

La prima riga è detta “linea di richiesta” e contiene informazioni sul tipo di richiesta effettuata dal client. Il metodo indica l’azione da eseguire, come ad esempio GET o POST. L’URI-Request specifica la risorsa richiesta sul server. La Versione-HTTP indica la versione del protocollo HTTP utilizzata. Il CRLF indica il termine della riga.

Dopo la linea di richiesta, seguono le intestazioni, che forniscono ulteriori informazioni sulla richiesta. Infine, c’è il corpo del messaggio, che può contenere dati aggiuntivi.

Ad esempio, una richiesta GET potrebbe apparire così:

bash

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Questa richiesta indica al server di restituire il file “index.html” dalla radice del sito.

Ora che abbiamo una panoramica di base di come funzionano le richieste HTTP, passiamo a implementare un server web in Rust!

Implementazione di un Server Web in Rust

Prima di tutto, dobbiamo creare un server che ascolti le richieste HTTP. Utilizzeremo la libreria standard di Rust per gestire la comunicazione di rete.

Ascolto delle Connessioni TCP

Il nostro server deve essere in grado di ascoltare le connessioni TCP in ingresso. Utilizzeremo il modulo std::net per questo scopo. Ecco un esempio di codice che avvia un server TCP in ascolto sull’indirizzo locale 127.0.0.1 e sulla porta 7878:

rust

use std::net::{TcpListener, TcpStream}; fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); for stream in listener.incoming() {
let stream = stream.unwrap();

// Gestisci la connessione
}
}

Questo codice crea un TcpListener che si mette in ascolto sull’indirizzo 127.0.0.1 (localhost) e sulla porta 7878. Successivamente, entra in un ciclo che accetta le connessioni in ingresso e le gestisce.

Lettura della Richiesta

Una volta stabilita una connessione con il client, dobbiamo leggere la richiesta inviata dal client. Utilizzeremo un BufReader per leggere i dati dallo stream TCP.

rust

use std::io::{prelude::*, BufReader}; fn handle_connection(stream: TcpStream) {
let mut buf_reader = BufReader::new(stream); let mut request = String::new();
buf_reader.read_to_string(&mut request).unwrap();

// Ora `request` contiene la richiesta HTTP
}

In questo codice, leggiamo i dati dallo stream TCP in un buffer e quindi li convertiamo in una stringa. Ora la variabile request contiene la richiesta HTTP inviata dal client.

Continueremo ad aggiungere funzionalità al nostro server web, incluso l’invio di risposte HTTP e la gestione delle richieste in arrivo in modo più sofisticato. Resta sintonizzato per ulteriori aggiornamenti sul nostro corso!

1 Comment

Leave a Reply

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *