2 điểm bởi GN⁺ 2024-09-26 | 1 bình luận | Chia sẻ qua WhatsApp

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ồi 411 Length Required để buộc client gửi lại với Content-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.pemkey.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

 
GN⁺ 2024-09-26
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ì

    • Có nhiều ý kiến cho rằng nên dùng reverse proxy nhưng không đưa ra lý do cụ thể về bảo mật hay hiệu năng
    • Đặt câu hỏi liệu reverse proxy có thực sự cần thiết hay không
  • 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

    • Xử lý lượng truy cập lớn với 128MB RAM và 1 CPU
    • Nhắc đến việc môi trường Internet 20 năm trước ít thù địch hơn
    • Bot là những fuzzer rất tốt, nhưng vẫn cần fuzzing thực sự
  • 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

    • Bất ngờ vì hàm poll() cho hiệu năng cao
    • Các hàm theo từng kết nối, struct liên quan và mảng có nét tương tự nginx, redis, memcached
    • Đây là một công việc rất tuyệt vời
  • Giớ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

    • Tích hợp sẵn các tính năng như quản lý chứng chỉ ACME, Pgsql, curl, WebSocket
    • Có thể build và chạy module bằng cách kết hợp Lua/Python với C
  • 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

    • Phục vụ 200GB nội dung trên gói Linode $40/tháng
    • 19% yêu cầu HTTP truy cập kho mã nguồn Fossil thông qua CGI
  • 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"

    • Bug thì có thể sửa được
  • 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

    • Bao gồm các tính năng như lưu trữ bất biến, giảm đặc quyền, không thể truy cập chứng chỉ TLS
  • 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

    • Đặt câu hỏi về tác động của các tính năng không cần thiết trong phần mềm đối với hiệu năng
    • Gửi lời chúc mừng tới nhà phát triển