CBLT — A Safe, Fast, and Minimalistic Web Server in Rust Programming Language
To learn a new programming language, I use the following approach. First, I read a tutorial for that programming language, which explains the syntax, idioms, philosophy, and principles of how the language works. After that, I write a small pet project in that programming language. In the pet project, I practice a bit with the new language, its standard libraries, and popular frameworks.
To immerse myself deeper into the language, instead of a pet project, I start writing my own libraries for working with databases (ORM), JSON, actors, MVC web frameworks, logging, etc. These libraries are unlikely to be needed by anyone, but they help me better understand the programming language. Surprisingly, with Rust, I managed to write my own web server. I hadn’t done this before. I think it’s because Rust is a systems programming language, and it’s not a sin to try optimizing performance with it.
In the end, I encountered that Rust does not have equivalents to Nginx, Lighttpd, Caddy, HAProxy, Apache, Tomcat, Jetty, etc. All these web servers are written in C, Go, Java, etc. There are only web frameworks: Actix, Axum, Rocket, Hyper, etc.
Overall, I figured out that I usually use Nginx for the following purposes:
- TLS for domains
- Proxying requests to the backend
- Serving static files
As a result, I decided to write my own implementation of a web server in Rust.
The server supports a configuration file in the KDL (KDL Document Language) format. Here are examples of the “Cbltfile” configuration file for the Cblt web server:
Server File
"*:80" {
root "*" "/path/to/folder"
file_server
}
File Server & Proxying
"127.0.0.1:8080" {
reverse_proxy "/test-api/*" "http://10.8.0.3:80"
root "*" "/path/to/folder"
file_server
}
TLS Support
"example.com" {
root "*" "/path/to/folder"
file_server
tls "/path/to/your/domain.crt" "/path/to/your/domain.key"
}
Currently, the Cblt web server can be launched in two ways: via Cargo or Docker.
Cargo
cargo run --release
Docker
docker build -t cblt:0.0.3 .
docker run -d -p 80:80 --restart unless-stopped --name cblt cblt:0.0.3
At the moment, I have achieved an acceptable speed for proxying static files.
I conducted a test with Apache Benchmark (ab) for 300 requests with 100 concurrent connections. Loading an image sized 5 MB from example.com/logo_huge.png.
ab -c 100 -n 300 http://example.com/logo_huge.png
Percent | Cblt | Nginx | Caddy |
---|---|---|---|
50% | 1956 | 1941 | 1768 |
75% | 2101 | 2065 | 1849 |
100% | 2711 | 2360 | 2270 |
Cblt
igumn@lenovo MINGW64 ~/cblt (main)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0589d8f26d91 cblt:0.0.1 "./cblt" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp cblt
igumn@lenovo MINGW64 ~/cblt (main)
$ ab -c 100 -n 300 http://example.com/logo_huge.png
This is ApacheBench, Version 2.3 <$Revision: 1913912 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking example.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 300 requests
Server Software:
Server Hostname: example.com
Server Port: 80
Document Path: /logo_huge.png
Document Length: 5122441 bytes
Concurrency Level: 100
Time taken for tests: 6.020 seconds
Complete requests: 300
Failed requests: 0
Total transferred: 1536745500 bytes
HTML transferred: 1536732300 bytes
Requests per second: 49.83 [#/sec] (mean)
Time per request: 2006.721 [ms] (mean)
Time per request: 20.067 [ms] (mean, across all concurrent requests)
Transfer rate: 249283.62 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 2
Processing: 1293 1926 262.3 1956 2711
Waiting: 1 118 139.1 63 645
Total: 1293 1926 262.3 1956 2711
Percentage of the requests served within a certain time (ms)
50% 1956
66% 2027
75% 2101
80% 2127
90% 2213
95% 2394
98% 2544
99% 2597
100% 2711 (longest request)
Nginx
igumn@lenovo MINGW64 ~/cblt/benchmark/nginx (main)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37fbf1dac42b nginx_srv "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp nginx_srv
igumn@lenovo MINGW64 ~/cblt/benchmark/nginx (main)
$ ab -c 100 -n 300 http://example.com/logo_huge.png
This is ApacheBench, Version 2.3 <$Revision: 1913912 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking example.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 300 requests
Server Software: nginx/1.27.2
Server Hostname: example.com
Server Port: 80
Document Path: /logo_huge.png
Document Length: 5122441 bytes
Concurrency Level: 100
Time taken for tests: 6.043 seconds
Complete requests: 300
Failed requests: 0
Total transferred: 1536804300 bytes
HTML transferred: 1536732300 bytes
Requests per second: 49.65 [#/sec] (mean)
Time per request: 2014.267 [ms] (mean)
Time per request: 20.143 [ms] (mean, across all concurrent requests)
Transfer rate: 248359.28 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 2
Processing: 1387 1940 168.4 1941 2360
Waiting: 1 115 84.5 98 301
Total: 1387 1940 168.4 1941 2360
Percentage of the requests served within a certain time (ms)
50% 1941
66% 2024
75% 2065
80% 2088
90% 2152
95% 2201
98% 2263
99% 2317
100% 2360 (longest request)
Caddy
igumn@lenovo MINGW64 ~/cblt (main)
$ ab -c 100 -n 300 http://example.com/logo_huge.png
This is ApacheBench, Version 2.3 <$Revision: 1913912 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking example.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 300 requests
Server Software: Caddy
Server Hostname: example.com
Server Port: 80
Document Path: /logo_huge.png
Document Length: 5122441 bytes
Concurrency Level: 100
Time taken for tests: 5.440 seconds
Complete requests: 300
Failed requests: 0
Total transferred: 1536804000 bytes
HTML transferred: 1536732300 bytes
Requests per second: 55.14 [#/sec] (mean)
Time per request: 1813.469 [ms] (mean)
Time per request: 18.135 [ms] (mean, across all concurrent requests)
Transfer rate: 275858.99 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 2
Processing: 1264 1749 191.1 1767 2270
Waiting: 1 96 104.7 67 467
Total: 1265 1749 191.1 1768 2270
Percentage of the requests served within a certain time (ms)
50% 1768
66% 1821
75% 1849
80% 1877
90% 1955
95% 2152
98% 2226
99% 2241
100% 2270 (longest request)
I also plan to conduct tests for backend proxying; in general, I haven’t yet tested the reverse proxy for performance.
Maybe this time my mini-project will interest someone? And this hobby will grow into something bigger?
If you’re interested in looking at the code or contributing, here’s the link to the repository: https://github.com/evgenyigumnov/cblt