http3.cr

A Crystal shard for establishing QUIC connections and exchanging fast, asynchronous HTTP/3 requests/responses using Cloudflare's libquiche.

http3.cr

Crystal CI GitHub release GitHub stars GitHub issues License: MIT Crystal Version

http3.cr è una shard Crystal per stabilire connessioni QUIC e scambiare richieste/risposte HTTP/3 veloci ed asincrone. La libreria si interfaccia a basso livello alla libreria ad alte prestazioni libquiche di Cloudflare tramite binding FFI a zero overhead.

Prerequisiti (libquiche)

Per compilare ed eseguire progetti con http3.cr, è necessario che la libreria nativa libquiche di Cloudflare sia compilata e disponibile nel percorso di ricerca del linker del tuo sistema operativo.

0. Installazione tramite Package Manager (Opzionale - Senza Rust)

Se non vuoi compilare la libreria dai sorgenti, puoi installare libquiche pre-compilata tramite i package manager ove disponibile:

  • macOS (Homebrew):
    brew install cloudflare-quiche
    
  • Arch Linux (pacman):
    sudo pacman -S quiche
    

Nota per Debian/Ubuntu e Windows: Attualmente Cloudflare non rilascia pacchetti binari ufficiali preconfezionati per queste piattaforme nei repository predefiniti. Su tali sistemi si consiglia di compilare tramite Rust come descritto nei passaggi successivi.


1. Installazione delle dipendenze di compilazione (Se compili da sorgente)

Il processo richiede Rust (cargo), CMake e Git installati sulla tua macchina solo se decidi di compilare libquiche partendo dai sorgenti.

macOS

Usa Homebrew per installare le dipendenze:

brew install rust cmake git

Linux Debian / Ubuntu

Installa i pacchetti necessari tramite apt:

sudo apt update
sudo apt install -y build-essential cmake git
# Installa Rust via rustup (raccomandato):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Arch Linux

Installa i pacchetti tramite pacman:

sudo pacman -S --needed base-devel cmake git rust

Windows

  1. Installa Build Tools per Visual Studio selezionando il carico di lavoro "Sviluppo di applicazioni desktop con C++".
  2. Installa CMake e Git (ad esempio tramite winget install git.git cmake.cmake).
  3. Installa Rust tramite rustup.

2. Compilazione di libquiche FFI

Clona il repository ufficiale di quiche e compila il target FFI in modalità release:

git clone --recursive https://github.com/cloudflare/quiche.git
cd quiche
cargo build --package quiche --release --features ffi

I file generati saranno situati in target/release/:

  • Linux: libquiche.so e libquiche.a
  • macOS: libquiche.dylib e libquiche.a
  • Windows: quiche.dll e quiche.lib

3. Configurazione dell'ambiente

Configura le variabili d'ambiente per consentire al compilatore di Crystal e al sistema di trovare la libreria FFI.

macOS

Esporta le variabili d'ambiente nel tuo terminale:

export LIBRARY_PATH="/percorso/a/quiche/target/release:$LIBRARY_PATH"
export DYLD_LIBRARY_PATH="/percorso/a/quiche/target/release:$DYLD_LIBRARY_PATH"

Linux (Debian, Arch, ecc.)

Esporta le variabili d'ambiente nel tuo terminale:

export LIBRARY_PATH="/percorso/a/quiche/target/release:$LIBRARY_PATH"
export LD_LIBRARY_PATH="/percorso/a/quiche/target/release:$LD_LIBRARY_PATH"

(In alternativa, copia libquiche.so in /usr/local/lib ed esegui sudo ldconfig)

Windows (PowerShell)

Aggiungi la cartella contenente quiche.dll al PATH e al percorso del linker:

$env:PATH = "C:\percorso\a\quiche\target\release;" + $env:PATH
$env:LIB = "C:\percorso\a\quiche\target\release;" + $env:LIB

Installazione

  1. Aggiungi la dipendenza nel file shard.yml del tuo progetto:

    dependencies:
      http3:
        github: eltony81/http3.cr
    
  2. Esegui shards install


Esempio Server HTTP/3

Il server HTTP/3 si mette in ascolto su una porta UDP reale, gestisce le nuove connessioni QUIC ed elabora le richieste HTTP/3 in arrivo tramite il gestore degli eventi (poll):

require "http3"
require "socket"

# 1. Configurazione QUIC e HTTP/3
config = Quic::Config.new
config.set_application_protos(["h3"])
config.load_cert_chain_from_pem_file("spec/fixtures/cert.pem")
config.load_priv_key_from_pem_file("spec/fixtures/key.pem")

# Configurazione dei limiti degli stream H3
config.set_initial_max_streams_bidi(1000_u64)
config.set_initial_max_streams_uni(1000_u64)
config.set_initial_max_stream_data_bidi_local(1048576_u64)
config.set_initial_max_stream_data_bidi_remote(1048576_u64)
config.set_initial_max_stream_data_uni(1048576_u64)
config.set_initial_max_data(1048576_u64)
config.set_max_idle_timeout(30000_u64)

# 2. Bind del socket UDP e avvio SocketHandler
server_socket = UDPSocket.new
server_socket.bind("127.0.0.1", 4433)
server_socket.blocking = false

handler = Quic::SocketHandler.new(server_socket)
handler.start_server_loop(config)

h3_config = Quic::H3::Config.new
h3_connections = {} of Bytes => Quic::H3::Connection

puts "Server HTTP/3 in ascolto su udp://127.0.0.1:4433..."

# 3. Loop principale per elaborare le connessioni e le richieste
spawn do
  buf = Bytes.new(4096)
  loop do
    # Controlla tutte le connessioni QUIC attive nel server
    handler.connections.each do |scid, conn|
      next unless conn.established?

      # Inizializza lo stato HTTP/3 per la connessione
      h3_conn = h3_connections[scid] ||= Quic::H3::Connection.new(conn, h3_config)

      # Polling degli eventi HTTP/3 sulla connessione
      loop do
        stream_id, ev = h3_conn.poll(conn)
        break if ev.nil?

        case ev.event_type
        when 0 # HEADERS (Richiesta ricevuta)
          puts "[Server] Ricevuti header sulla stream #{stream_id}:"
          ev.headers.each { |k, v| puts "  #{k}: #{v}" }

          # Invia gli header di risposta
          resp_headers = [
            {":status", "200"},
            {"content-type", "text/plain"},
            {"server", "CrystalHTTP3Server"}
          ]
          h3_conn.send_response(conn, stream_id.to_u64, resp_headers, false)

          # Invia il body di risposta e chiudi lo stream (fin = true)
          body = "Hello from Crystal HTTP/3 Server!".to_slice
          h3_conn.send_body(conn, stream_id.to_u64, body, true)
          
          # Invia immediatamente i pacchetti generati sul socket
          handler.send_pending(conn)
        when 1 # DATA
          body_len = h3_conn.recv_body(conn, stream_id.to_u64, buf)
          puts "[Server] Ricevuto DATA: #{String.new(buf[0, body_len])}"
        end
      end
    end
    sleep 1.millisecond
  end
end

sleep # Mantiene attivo il programma principale

Esempio Client HTTP/3

Il client si connette al server UDP via QUIC, istanzia il client HTTP/3 ed invia una richiesta GET leggendo la risposta:

require "http3"
require "socket"

# 1. Configurazione client
config = Quic::Config.new
config.set_application_protos(["h3"])
config.verify_peer(false) # Per certificati auto-firmati
config.set_initial_max_streams_bidi(1000_u64)
config.set_initial_max_streams_uni(1000_u64)
config.set_initial_max_data(1048576_u64)

client_socket = UDPSocket.new
client_socket.bind("127.0.0.1", 0)
client_socket.blocking = false

scid = Bytes.new(16)
Random.new.random_bytes(scid)

server_addr = Socket::IPAddress.new("127.0.0.1", 4433)
client_conn = Quic::Connection.connect("localhost", scid, client_socket.local_address, server_addr, config)

handler = Quic::SocketHandler.new(client_socket)
handler.register_connection(scid, client_conn)

# Avvia loop di lettura asincrono per il client
spawn do
  buf = Bytes.new(65535)
  while !client_socket.closed?
    begin
      bytes_read, from_addr = client_socket.receive(buf)
      if bytes_read > 0
        client_conn.recv(buf[0, bytes_read], from_addr, client_socket.local_address)
        handler.send_pending(client_conn)
      end
    rescue IO::Error
      break
    end
  end
end

# Invia pacchetto di handshake iniziale
handler.send_pending(client_conn)

puts "Connessione in corso..."
while !client_conn.established?
  sleep 10.milliseconds
end
puts "Connessione QUIC Stabilita!"

# 2. Inizializza HTTP/3
h3_config = Quic::H3::Config.new
h3_client = Quic::H3::Connection.new(client_conn, h3_config)
sleep 50.milliseconds # Attesa negoziazione settings H3 iniziali

# 3. Invia la richiesta HTTP/3
req_headers = [
  {":method", "GET"},
  {":scheme", "https"},
  {":authority", "localhost"},
  {":path", "/"}
]
stream_id = h3_client.send_request(client_conn, req_headers, true)
handler.send_pending(client_conn)

puts "Richiesta inviata sulla stream #{stream_id}."

# 4. Leggi la risposta
response_done = false
buf = Bytes.new(4096)

while !response_done
  loop do
    status, ev = h3_client.poll(client_conn)
    break if ev.nil?

    case ev.event_type
    when 0 # HEADERS
      puts "[Client] Ricevuti header di risposta:"
      ev.headers.each { |k, v| puts "  #{k}: #{v}" }
    when 1 # DATA
      body_len = h3_client.recv_body(client_conn, status.to_u64, buf)
      puts "[Client] Ricevuto Body: #{String.new(buf[0, body_len])}"
      response_done = true
    end
  end
  sleep 1.millisecond
end

client_socket.close

Compatibilità con la Libreria Standard HTTP di Crystal

La libreria http3.cr fornisce moduli ad alto livello (HTTP3::Client e HTTP3::Server) progettati specificamente per essere compatibili con le classi standard di Crystal (HTTP::Client, HTTP::Server, HTTP::Request, HTTP::Client::Response, HTTP::Server::Context e HTTP::Handler), consentendo di integrare HTTP/3 con il minimo sforzo e supportando anche il middleware di Crystal.

Client HTTP/3 ad Alto Livello

Il client HTTP3::Client espone lo stesso set di metodi della libreria standard (get, post, put, delete, ed exec) e restituisce una risposta di tipo HTTP::Client::Response standard:

require "http3"

# Istanzia un client HTTP/3 connesso via QUIC
client = HTTP3::Client.new("localhost", 4433)

# Effettua richieste GET o POST usando classi standard
response = client.get("/percorso")
puts response.status_code # => 200
puts response.body        # => "..."

# Supporta l'invio di istanze HTTP::Request complesse
request = HTTP::Request.new("POST", "/upload", HTTP::Headers{"Content-Type" => "application/json"}, "{\"data\": 123}")
response = client.exec(request)

client.close

Server HTTP/3 ad Alto Livello

Il server HTTP3::Server accetta i medesimi tipi di handler e middleware della libreria standard di Crystal. È possibile passare un blocco che riceve un HTTP::Server::Context standard (con request e response), oppure definire una catena di HTTP::Handler:

require "http3"

# 1. Configura il certificato TLS per il server HTTP/3
config = Quic::Config.new
config.load_cert_chain_from_pem_file("spec/fixtures/cert.pem")
config.load_priv_key_from_pem_file("spec/fixtures/key.pem")
h3_config = Quic::H3::Config.new

# 2. Definisce un handler o middleware compatibile con la libreria standard
server = HTTP3::Server.new("127.0.0.1", 4433, config, h3_config) do |context|
  context.response.content_type = "text/plain"
  context.response.print "Hello World from HTTP/3 Server!"
end

# 3. Avvia l'ascolto UDP per le connessioni HTTP/3
puts "Server HTTP/3 avviato..."
server.listen

Sviluppo e Test

Per far girare i test unitari di questa shard:

LIBRARY_PATH=./usr/lib LD_LIBRARY_PATH=./usr/lib crystal spec

Licenza

Il progetto è rilasciato sotto licenza MIT.

Repository

http3.cr

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 1
  • about 4 hours ago
  • June 14, 2026
License

MIT License

Links
Synced at

Sun, 14 Jun 2026 20:08:34 GMT

Languages