Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Why custom made Tokio HTTP server is slower than framework based like Axum, Actix, Ntex and so on?
So I just tried this
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
const MAX_HEADERS: usize = 32;
#[derive(Debug)]
struct HttpRequest<'a> {
method: &'a [u8],
path: &'a [u8],
version: &'a [u8],
headers: [(&'a [u8], &'a [u8]); MAX_HEADERS],
header_count: usize,
body: &'a [u8],
}
fn parse_http_request<'a>(raw: &'a [u8]) -> Option<HttpRequest<'a>> {
let mut i = 0;
let len = raw.len();
fn next_line(input: &[u8], start: usize) -> Option<(usize, usize)> {
let mut pos = start;
while pos + 1 < input.len() {
if input[pos] == b'\r' && input[pos + 1] == b'\n' {
return Some((start, pos));
}
pos += 1;
}
None
}
let (line_start, line_end) = next_line(raw, i)?;
let line = &raw[line_start..line_end];
i = line_end + 2;
let mut part_start = 0;
let mut parts: [&[u8]; 3] = [&[]; 3];
let mut part_index = 0;
for pos in 0..line.len() {
if line[pos] == b' ' && part_index < 2 {
parts[part_index] = &line[part_start..pos];
part_index += 1;
part_start = pos + 1;
}
}
parts[part_index] = &line[part_start..];
let method = parts[0];
let path = parts[1];
let version = parts[2];
let mut headers: [(&[u8], &[u8]); MAX_HEADERS] = [(&[], &[]); MAX_HEADERS];
let mut header_count = 0;
while i + 1 < len {
if raw[i] == b'\r' && raw[i + 1] == b'\n' {
i += 2;
break;
}
let (line_start, line_end) = next_line(raw, i)?;
let line = &raw[line_start..line_end];
i = line_end + 2;
if let Some(colon_pos) = line.iter().position(|&b| b == b':') {
let key = &line[..colon_pos];
let mut val_start = colon_pos + 1;
if val_start < line.len() && line[val_start] == b' ' {
val_start += 1;
}
let value = &line[val_start..];
if header_count < MAX_HEADERS {
headers[header_count] = (key, value);
header_count += 1;
}
}
}
let body = &raw[i..];
Some(HttpRequest {
method,
path,
version,
headers,
header_count,
body,
})
}
async fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0u8; 8192];
match stream.read(&mut buffer).await {
Ok(n) if n == 0 => return,
Ok(n) => {
let data = &buffer[..n];
if let Some(req) = parse_http_request(data) {
let response = match req.method {
b"GET" => b"HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!" as &[u8],
_ => b"HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n",
};
let _ = stream.write_all(response).await;
} else {
let _ = stream.write_all(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n").await;
}
}
Err(_) => {}
}
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
println!("Server running on 127.0.0.1:8080");
loop {
let (socket, _) = listener.accept().await.unwrap();
tokio::spawn(handle_connection(socket));
}
}
It has lower performance than framework based
[root@localhost ~]# wrk -c 250 -d 15 -t 8 http://127.0.0.1:8080
Running 15s test @ http://127.0.0.1:8080
8 threads and 250 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 70.84ms 67.03ms 508.20ms 75.74%
Req/Sec 109.73 68.04 330.00 69.81%
12214 requests in 15.09s, 620.24KB read
Socket errors: connect 0, read 12180, write 0, timeout 0
Requests/sec: 809.15
Transfer/sec: 41.09KB
With Axum on the same machine my android phone I can get this result :
[root@localhost axum]# wrk -c 250 -d 15 -t 8 http://127.0.0.1:8080
Running 15s test @ http://127.0.0.1:8080
8 threads and 250 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.50ms 2.05ms 36.43ms 70.96%
Req/Sec 5.41k 569.34 11.44k 74.41%
644716 requests in 15.09s, 79.93MB read
Requests/sec: 42723.13
Transfer/sec: 5.30MB
What are the reasons behind that slower performance result?
13 posts - 5 participants
🏷️ rust_feed