Ra mắt trên HN: Lưu trữ website bằng máy chủ web C
(github.com/cozis)Công nghệ blog của tôi
Máy chủ web này là một máy chủ web tối giản được thiết kế để lưu trữ blog của tôi. Nó được xây dựng vững chắc ngay từ đầu để có thể chịu được Internet công cộng. Không cần reverse proxy. Bạn có thể xem nó hoạt động thực tế tại http://playin.coz.is/index.html. Tôi đã nhờ Reddit thử hack nó và thu thập được hàng gigabyte log các yêu cầu vừa thú vị vừa độc hại. Tôi đã lưu một phần trong attempts.txt và sẽ đào lại thêm sau này cho vui.
Nhưng... tại sao?
Tôi thích tự làm công cụ của riêng mình, và tôi đã chán ngấy việc cứ phải nghe rằng mọi thứ đều phải được "battle-tested". Nếu có sự cố thì sao? Bug thì có thể sửa.
Thông số
- Chỉ dành cho Linux
- Triển khai HTTP/1.1, pipelining và kết nối keep-alive
- Hỗ trợ HTTPS (dùng BearSSL, tối đa TLS 1.2)
- Phụ thuộc tối thiểu (libc và BearSSL khi dùng HTTPS)
- Timeout có thể cấu hình
- Log truy cập, log crash, xoay vòng log, giới hạn dung lượng đĩa
- Không có
Transfer-Encoding: Chunked(phản hồi411 Length Requiredđể buộc client gửi lại vớiContent-Length) - Một lõi đơn (sẽ thay đổi khi có VPS tốt hơn)
- Không có cache file tĩnh (hiện tại)
Benchmark
Dù trọng tâm của dự án là độ bền vững, nó hoàn toàn không chậm. So sánh nhanh với nginx (endpoint tĩnh, cả hai đều một luồng, giới hạn 1K kết nối):
-
(blogtech)
$ wrk -c 500 -d 5s http://127.0.0.1:80/hello- Độ trễ trung bình: 6.66ms
- Yêu cầu/giây: 76974.24
- Truyền/giây: 6.09MB
-
(nginx)
$ wrk -c 500 -d 5s http://127.0.0.1:8080/hello- Độ trễ trung bình: 149.11ms
- Yêu cầu/giây: 44227.78
- Truyền/giây: 8.27MB
Cấu hình nginx:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /hello {
add_header Content-Type text/plain;
return 200 "Hello, world!";
}
}
}
Build và chạy
Mặc định, bản build của máy chủ chỉ hỗ trợ HTTP:
$ make
Lệnh này tạo ra các file thực thi serve (bản release), serve_cov (bản coverage), serve_debug (bản debug). Bản release lắng nghe trên cổng 80, còn bản debug lắng nghe trên cổng 8080.
Để bật HTTPS, bạn cần clone và build BearSSL:
$ mkdir 3p
$ cd 3p
$ git clone https://www.bearssl.org/git/BearSSL
$ cd BearSSL
$ make -j
$ cd ../../
$ make -B HTTPS=1
Các file thực thi tương tự sẽ được tạo ra, nhưng sẽ có thể nhận kết nối bảo mật trên cổng 443 (release) hoặc 8081 (debug). Bạn phải đặt các file cert.pem và key.pem trong cùng thư mục với file thực thi. Để đổi tên và vị trí, hãy sửa:
#define HTTPS_KEY_FILE "key.pem"
#define HTTPS_CERT_FILE "cert.pem"
Để thử HTTPS cục bộ, hãy tạo chứng chỉ tự ký (và khóa riêng):
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365
Cách dùng
Máy chủ phục vụ nội dung tĩnh từ thư mục docroot/. Để thay đổi điều này, hãy sửa hàm respond:
typedef struct {
Method method;
string path;
int major;
int minor;
int nheaders;
Header headers[MAX_HEADERS];
string content;
} Request;
void respond(Request request, ResponseBuilder *b) {
if (request.major != 1 || request.minor > 1) {
status_line(b, 505); // HTTP Version Not Supported
return;
}
if (request.method != M_GET) {
status_line(b, 405); // Method Not Allowed
return;
}
if (string_match_case_insensitive(request.path, LIT("/hello"))) {
status_line(b, 200);
append_content_s(b, LIT("Hello, world!"));
return;
}
if (serve_file_or_dir(b, LIT("/"), LIT("docroot/"), request.path, NULLSTR, false))
return;
status_line(b, 404);
append_content_s(b, LIT("Nothing here :|"));
}
Tại đây bạn có thể thêm endpoint bằng cách phân nhánh theo trường request.path. Đường dẫn chỉ là một lát cắt của buffer yêu cầu. URI không được parse.
Kiểm thử
Tôi thường xuyên chạy máy chủ với valgrind và sanitizers (address, undefined) rồi dùng wrk để bắn tải vào. Tôi cũng đang thêm các bài kiểm thử tự động trong tests/test.py để kiểm tra mức độ tuân thủ đặc tả HTTP/1.1. Tôi duy trì áp lực bằng cách dùng nó để lưu trữ website của mình và đăng nó ở nhiều nơi. Tất cả các bot quét website dễ bị tấn công trên Internet đều là những fuzzer tuyệt vời.
Vấn đề đã biết
- Máy chủ phản hồi client HTTP/1.0 bằng HTTP/1.1
Đóng góp
Tôi chủ yếu làm việc trên nhánh DEV và thỉnh thoảng mới merge vào MAIN. Nếu mở pull request thì nhắm vào DEV sẽ dễ hơn.
Tóm tắt của GN⁺
- Dự án này là một máy chủ web hướng tới phụ thuộc tối thiểu và độ bền vững.
- Nó hỗ trợ HTTP/1.1 và HTTPS, đồng thời cung cấp nhiều tính năng log và timeout có thể cấu hình.
- Kết quả benchmark cho thấy thời gian phản hồi nhanh hơn nginx.
- Được thiết kế để các nhà phát triển có thể tận hưởng quá trình tự xây công cụ và sửa bug.
- Các dự án có chức năng tương tự gồm Nginx và Apache HTTP Server.
1 bình luận
Bình luận trên Hacker News
Không cần reverse proxy: Đã dùng Jetty để triển khai ứng dụng trực tiếp lên Internet mà không cần reverse proxy và không gặp vấn đề gì
Tự làm web server bằng C: Đã tự viết một web server bằng C từng vận hành website thương mại
Sự thỏa mãn khi tự xây dịch vụ: Việc xây các dịch vụ cơ bản bằng system API mang lại cảm giác rất thỏa mãn
poll()cho hiệu năng caoGiới thiệu dự án nhỏ: Giới thiệu một dự án thú vị bắt đầu vào thời gian rảnh
Đề xuất framework Kore: Nếu thấy bất tiện khi phải tự viết phần public-facing cho ứng dụng C thì có thể dùng framework Kore
Chia sẻ liên kết thú vị: Một instance althttpd của sqlite.org xử lý hơn 500.000 yêu cầu HTTP mỗi ngày
Niềm vui khi tự làm công cụ: Đã thấy mệt mỏi với quan điểm rằng mọi thứ đều phải được "battle-tested"
Bài nói chuyện tại Chaos Communication Congress: Gợi nhớ đến một bài nói về blog/web server viết bằng C có tích hợp các tính năng bảo mật
Website ổn định: Một website không bị crash ngay cả khi được đưa lên trang đầu
Quay về điều cơ bản: Thích cách tiếp cận quay về những gì cơ bản, chỉ dùng những gì cần thiết