-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Expected Behavior
HTTP/2 performance should be at least comparable to HTTP/1.1, if not better due to protocol features like multiplexing and header compression.
Current Behavior
HTTP/2 is substantially slower than HTTP/1.1 when using actix-web with rustls. Furthermore, a comparable Node.js http2 server significantly outperforms the actix-web HTTP/2 implementation. Server logs show that endpoint processing times are nearly identical across all implementations, suggesting the performance difference lies in the HTTP/2 protocol handling layer rather than application logic.
I'm using reqwest with custom certificates so this might have an effect on the result, but the same client code doesn't show any large performance drop when testing against a NodeJS server.
Performance Results (1000 requests)
Average time per request in milliseconds using the reqwest client in the reproduction repo.
| Protocol | Node.js | Actix-web |
|---|---|---|
| HTTP/1.1 | 0.32ms | 0.14ms |
| HTTP/2 | 0.58ms | 41.45ms |
Possible Solution
Potential areas to investigate:
- Configuration parameters for HTTP/2 in
actix-webthat might need tuning - Interaction between
actix-webandreqwestspecifically - Buffer sizes, connection pooling, or other transport-level settings
- Potential optimizations in the
listen_rustls_0_23binding - Comparison with other TLS backends (e.g., native-tls, openssl)
Steps to Reproduce
- Clone the reproduction repository: https://github.com/ThomasVille/reqwest-actix-web-slow-http2.
- Generate SSL certificates using the provided script:
./generate_certs.sh - Start the actix-web server with HTTP/2:
cd actix-web-server cargo run --release - Run benchmarks using the reqwest client:
cd ../reqwest_client cargo run --release - Compare with HTTP/1.1 by stopping the server and restarting with:
cd actix-web-server USE_HTTPS=false cargo run --release - Run HTTP/1.1 benchmarks:
cd ../reqwest_client USE_HTTPS=false cargo run --release
Reproduction Code
Here is the entire repo: https://github.com/ThomasVille/reqwest-actix-web-slow-http2.
And here is some sample code in case you don't want to go through the repo:
actix-web server (Rust)
use actix_web::{get, App, HttpRequest, HttpResponse, HttpServer};
use rustls::ServerConfig;
use std::fs::File;
use std::io::BufReader;
fn server_config() -> ServerConfig {
rustls::crypto::ring::default_provider().install_default().ok();
let cert_file = File::open("./cert.pem").expect("cert file");
let key_file = File::open("./key.pem").expect("key file");
let certs = rustls_pemfile::certs(&mut BufReader::new(cert_file))
.collect::<Result<Vec<_>, _>>()
.expect("certs");
let key = rustls_pemfile::pkcs8_private_keys(&mut BufReader::new(key_file))
.next()
.expect("key")
.expect("key parse");
ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, rustls::pki_types::PrivateKeyDer::Pkcs8(key))
.expect("config")
}
#[get("/data")]
async fn get_random_data() -> HttpResponse {
// Generate 1-8KB of random JSON data
let data = generate_random_content(); // implementation details omitted
HttpResponse::Ok().json(data)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let server = HttpServer::new(|| App::new().service(get_random_data));
// For HTTP/2
let listener = std::net::TcpListener::bind("127.0.0.1:8443")?;
server.listen_rustls_0_23(listener, server_config())?.run().await
// For HTTP/1.1 comparison
// server.bind("127.0.0.1:8080")?.run().await
}Node.js http2 server (for comparison)
const http2 = require('http2');
const fs = require('fs');
function handleRequest(req, res) {
if (req.url === '/data') {
const content = generateRandomContent(); // identical to Rust version
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(content));
}
}
const options = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem'),
allowHTTP1: true
};
const server = http2.createSecureServer(options, handleRequest);
server.listen(8443, '127.0.0.1');reqwest client (Rust) - for testing
use reqwest::Client;
use std::time::Instant;
fn build_client() -> Client {
reqwest::ClientBuilder::new()
.use_rustls_tls()
.add_root_certificate(
reqwest::Certificate::from_pem(include_bytes!("./rootCA.pem"))
.expect("Failed to load certificate"),
)
.build()
.expect("Failed to build client")
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = build_client();
let start = Instant::now();
let mut total_requests = 0;
for _ in 0..1000 {
let response = client
.get("https://127.0.0.1:8443/data")
.send()
.await?;
let _body = response.text().await?;
total_requests += 1;
}
let duration = start.elapsed();
println!("Total requests: {}", total_requests);
println!("Total time: {:?}", duration);
println!("Avg per request: {:?}", duration / total_requests);
Ok(())
}Cargo.toml:
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [
"http2",
"json",
"rustls-tls",
"rustls-tls-native-roots"
] }
tokio = { version = "1", features = ["full"] }Your Environment
- Rust Version (output of
rustc -V): 1.89.0 - Actix Web Version: 4.9.0
- rustls version: 0.23 with ring crypto provider
- actix-rt: 2
- Operating System: Linux