Ra mắt trên HN: Lưu trữ website bằng máy chủ web C
(github.com/cozis)- BlogTech là bộ công cụ dựa trên C để quản lý các website quy mô nhỏ đến trung bình, cung cấp HTTPS, virtual host, quản lý chứng chỉ tự động dựa trên ACME và client quản lý máy chủ từ xa
- Hiện đang ở giai đoạn thử nghiệm, với phiên bản
0.4.xđã được dùng để vận hành trong ` vài tuần nhưng chưa được xem là bản phát hành ổn định - Máy chủ chạy trên Linux và Windows nhưng HTTPS chỉ được hỗ trợ trên Linux; trên Windows cả client và server chỉ dùng được HTTP
- Quản lý tệp từ xa được thực hiện bằng
--put,--get,--delete, và các yêu cầu thay đổi tài nguyên được xác thực bằng chữ ký HMAC/SHA256 dựa trên khóa bí mật dùng chung - Trong môi trường vận hành cần máy chủ HTTP ở cổng 80 và bản ghi DNS cho tên miền; khi bật ACME, tệp chứng chỉ và khóa riêng sẽ được tạo tự động và lỗi sẽ được ghi vào
acme.log
Mục đích và trạng thái hiện tại của BlogTech
- BlogTech là bộ công cụ để quản lý các website quy mô nhỏ đến trung bình
- Các tính năng được hỗ trợ gồm:
-
HTTPS
-
Virtual host
- Quản lý chứng chỉ tự động qua ACME
- Client để quản trị máy chủ từ xa
- Vẫn đang ở giai đoạn thử nghiệm
- Có trường hợp dùng phiên bản
0.4.xđể phục vụhttps://coz.is/thành công trong vài tuần - Phiên bản một tệp trước đây nằm trong nhánh
single_file
-
Build và chạy cơ bản
- BlogTech chạy trên Linux và Windows
- Build trên Linux cần thư viện phát triển OpenSSL và
gcc - Build trên Windows cần
clang
- Build trên Linux cần thư viện phát triển OpenSSL và
- Việc build được thực hiện bằng script theo từng nền tảng
- Linux:
./build.sh - Windows:
.\build.bat
- Linux:
- Kết quả build như sau
- Linux:
blogtech - Windows:
blogtech.exe
- Linux:
- Trên Linux có thể cài đặt bằng
./install.sh - Ví dụ chạy máy chủ cơ bản là tạo thư mục
docrootrồi chỉ định--serve,--document-root=docroot,--skip-auth-check - Máy chủ HTTP mặc định lắng nghe tại
127.0.0.1:8080và phục vụ nội dung trongdocroot - Khi
docroottrống, truy cậphttp://127.0.0.1:8080/sẽ trả về 404
Quản lý tệp từ xa và xác thực
- BlogTech có thể tải tệp lên máy chủ ở chế độ client
- Tải tệp lên bằng
--put - Tải tệp từ xa xuống bằng
--get - Xóa tệp từ xa bằng
--delete
- Tải tệp lên bằng
- Dù chạy client và server trên cùng một máy, cách hoạt động với server trên máy từ xa cũng giống như vậy
- Các yêu cầu thay đổi tài nguyên được ký số bằng HMAC/SHA256
- Client và server phải chia sẻ cùng một khóa bí mật, theo quy ước được lưu trong tệp
admin.pwd - Tệp mật khẩu được chỉ định bằng tùy chọn
--auth-password-file - Nếu không có tệp mật khẩu thì mọi yêu cầu cần xác thực sẽ bị từ chối
- Mật khẩu rỗng cũng bị từ chối
- Trong quá trình phát triển, có thể dùng
--skip-auth-checkđể coi mọi yêu cầu là đã được xác thực- Khi dùng tùy chọn này,
--auth-password-filesẽ bị bỏ qua dù có được chỉ định - Cần dùng cẩn thận
- Khi dùng tùy chọn này,
-
Header xác thực và điều kiện phát lại
- Yêu cầu HTTP đã xác thực sẽ bao gồm các header
X-BlogTech- X-BlogTech-Nonce: token do client chọn ngẫu nhiênX-BlogTech-Timestamp: UNIX timestamp tại thời điểm yêu cầu được ký lần đầuX-BlogTech-Expire: thời gian tính bằng giây mà yêu cầu còn hiệu lực kể từ lúc kýX-BlogTech-Signature: giá trị HMAC của thông tin yêu cầu được mã hóa Base64- Chữ ký được tạo bằng cách dựng chuỗi yêu cầu đã chuẩn hóa, sau đó tính HMAC/SHA256 với mật khẩu xác thực làm khóa, rồi mã hóa kết quả sang Base64 có kèm padding
- Nonce mà server đã thấy sẽ được lưu trong bộ nhớ
- Khi server được tải lại, các nonce trước đó sẽ bị quên nên các yêu cầu chưa hết hạn có thể bị phát lại
- Yêu cầu HTTP đã xác thực sẽ bao gồm các header
Virtual host và ánh xạ thư mục
- BlogTech có thể lưu trữ nhiều website trên cùng một máy chủ
- Tên miền được cấu hình bằng cách chỉ định tùy chọn
--domainnhiều lần - BlogTech tạo các thư mục theo từng tên miền bên trong document root
defaultwebsiteA.comwebsiteB.com
- Yêu cầu đi vào một host cụ thể sẽ tham chiếu tới thư mục gắn với host đó
- Những yêu cầu không gắn với thư mục cụ thể sẽ tham chiếu tới thư mục default
- Trong ví dụ, các tệp được tải lên
websiteA.com,websiteB.com,other.comsẽ lần lượt được lưu vào thư mục tên miền tương ứng hoặc thư mụcdefault
HTTPS và cấu hình chứng chỉ
- HTTPS chỉ được hỗ trợ trên Linux
- Lý do là thư viện HTTP nền tảng cHTTP triển khai HTTPS bằng OpenSSL
- Trên Windows, cả client và server chỉ có thể dùng HTTP
- Để bật HTTPS cần các tùy chọn sau
--https-enabled--cert-file--cert-key-file
- Địa chỉ lắng nghe HTTPS mặc định là
127.0.0.1:8443 - Có thể đổi địa chỉ và cổng lắng nghe bằng các tùy chọn sau
--https-addr=<addr>--https-port=<port>
- Trong quá trình phát triển, có thể tạo chứng chỉ tự ký bằng lệnh OpenSSL
- Trình duyệt không cho phép duyệt máy chủ HTTPS dùng chứng chỉ tự ký
- cURL cần dùng cờ
--insecuremới có thể truy cập máy chủ dùng chứng chỉ tự ký - Nếu cần nhiều chứng chỉ, có thể truyền thêm
--extra-cert- Chứng chỉ mặc định được cung cấp qua
--cert-file,--cert-key-file - Chứng chỉ bổ sung được dùng khi client yêu cầu một tên miền cụ thể
- Chứng chỉ mặc định được cung cấp qua
- Các tùy chọn dòng lệnh dài có thể được chuyển sang tệp cấu hình
Cấp chứng chỉ tự động dựa trên ACME
- Giao thức ACME cho phép máy chủ web tự động yêu cầu cấp chứng chỉ từ tổ chức chứng thực
- BlogTech hỗ trợ luồng khởi động ở chế độ HTTPS không có chứng chỉ trước, rồi tạo chứng chỉ sau đó
- Trong môi trường vận hành cần các điều kiện sau
- Chạy máy chủ HTTP trên cổng 80
- Tạo bản ghi DNS liên kết tên miền với máy đang chạy server
- ACME được bật bằng cách chỉ định
--acme-enabledcùng với các tùy chọn HTTPS - Khác với chế độ HTTPS thông thường, ở chế độ ACME hệ thống giả định chứng chỉ chưa tồn tại
- Để tạo chứng chỉ cần các giá trị sau
--acme-domain--acme-email--acme-country--acme-organization--acme-agree-tos
- Nếu xử lý thành công sẽ tạo các tệp sau
acme_key.pem: khóa bí mật gắn với tài khoản ACMEcert.pem: chứng chỉ đã cấp hoặc tệp được chỉ định bằng--cert-filekey.pem: khóa riêng gắn với chứng chỉ hoặc tệp được chỉ định bằng--cert-key-file
- Nếu có lỗi, thông báo sẽ được ghi vào
acme.log - Có thể chỉ định
--acme-domainnhiều lần để xử lý nhiều tên miền bằng ACME - Chứng chỉ kết quả sẽ bao gồm tất cả các tên miền đã chỉ định
Kiểm thử ACME client
- Để kiểm thử ACME client trên Linux, cần cài Docker và clone Pebble ACME server
- Trong
docker-compose.yml, thêm mụcextra_hostsđể liên kết tên miền thử nghiệm vớihost-gateway - Trong
/etc/hosts, ánh xạ tên miền thử nghiệm tới127.0.0.1 - Khởi động Pebble bằng
docker compose up - Chạy instance thử nghiệm của BlogTech bằng
./blogtech -s --config=misc/pebble_blogtech.conf - Tương tác với ACME server sẽ được in ra stdout
- Nếu thành công,
acme_key.pem,cert.pem,key.pemsẽ được tạo - Có thể kiểm thử gia hạn chứng chỉ bằng cách thay đổi giá trị
validityPeriodtrongpebble/test/config/pebble-config.json - Dùng tùy chọn
--acme-force-renewal-period=<maximum duration in ms>để buộc gia hạn ngay cả khi chứng chỉ chưa hết hạn
Tệp cấu hình và nạp tự động
- BlogTech có thể chuyển số lượng bất kỳ đối số dòng lệnh nào sang tệp cấu hình
- Ví dụ có thể ghi các tùy chọn liên quan đến HTTPS và ACME vào
blogtech_server.conf, rồi chạy bằng--config=blogtech_server.conf - Nếu tên tệp cấu hình chính xác là
blogtech.conf, BlogTech sẽ tự động nạp - Khi đó có thể chạy như
./blogtech --serve - Để bỏ qua việc nạp tệp cấu hình ngầm, dùng
--no-config
Nhật ký crash và daemon systemd
- Nếu BlogTech bị crash ở chế độ server, tệp
crash.binsẽ được tạo - Ở lần khởi động server tiếp theo,
crash.binsẽ được chuyển thànhcrash.loglà stack trace có thể đọc được - Quá trình chuyển địa chỉ sang tên symbol hoặc số dòng có thể hơi thiếu ổn định
- BlogTech có thể được cài dưới dạng daemon systemd
- Ví dụ
blogtech.servicechạy/root/blogtech/blogtech -s, tự khởi động lại khi lỗi, và đặt thư mục làm việc là/root/blogtech/ - Các tùy chọn server sẽ được tự động nạp từ
/root/blogtech/blogtech.conf - Sau khi sao chép tệp service vào
/etc/systemd/system/, hãy kích hoạt và khởi động bằng các lệnh sausystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- Có thể quản lý service bằng
systemctl start,systemctl stop,systemctl restart,journalctl -u blogtech
1 bình luận
Ý kiến trên Hacker News
Tôi luôn thấy phần “không cần reverse proxy” khá khó hiểu
Nếu reverse proxy không có lý do đặc biệt nào để hữu ích, tôi thường đưa thẳng các ứng dụng Jetty nhúng ra Internet mà không có lớp phía trước, và cũng không gặp vấn đề gì
Những người phụ trách hạ tầng hoặc bảo mật hay hỏi tại sao không đặt nginx ở phía trước, nhưng khi hỏi lý do thì họ chỉ nói chung chung về bảo mật hoặc hiệu năng, thiếu tính cụ thể. Câu trả lời cụ thể nhất từng nghe là slow loris, nhưng từ lâu nó đã không còn là vấn đề lớn nữa
Tôi tự hỏi liệu reverse proxy có trở thành một dạng cargo cult tập thể hay không, hay là tôi đang bỏ lỡ một lý do chính đáng, có thể áp dụng rộng rãi
Nhìn chung, có thể bảo vệ máy chủ gốc và chỉ cho nó nhận lưu lượng liên quan. Khi cung cấp domain tùy chỉnh cho khách hàng, DNS của khách hàng cũng trỏ tới reverse proxy chứ không phải máy chủ gốc, nên nếu cần thay máy chủ gốc thì không phải lo khách hàng đổi DNS
Mỗi chương trình chạy trên một port khác nhau, nhưng tôi muốn tất cả đều có thể truy cập công khai qua các URL khác nhau, và trên Internet chỉ muốn mở port 443
Tôi cũng muốn tự động gia hạn chứng chỉ TLS cho từng domain. Các yêu cầu trên cần reverse proxy, và Caddy làm được cả hai
Nếu chỉ vận hành một máy chủ duy nhất và máy chủ đó tự chấm dứt TLS, thì reverse proxy không nhất thiết cần thiết
Vì có thể thêm chấm dứt TLS, viết lại URL và các tác vụ khác sau này mà không tốn nhiều công sức, tôi dùng nó như một thói quen, và đến nay thói quen đó đã đem lại lợi ích
Về bảo mật, trong toàn bộ hệ thống ta muốn dùng ít mã nhất có thể và một hệ điều hành vững chắc. Ứng dụng dạng proxy có thể đơn giản hơn nhiều so với máy chủ web/ứng dụng, và cũng có thể lọc lưu lượng đi vào, kiểm tra đầu vào, hoặc chuyển nó sang dạng có thể phân tích cú pháp an toàn và nhanh hơn. Nó cũng có thể chạy trên những hệ điều hành khó tấn công như OpenBSD, GenodeOS, INTEGRITY-178B
Về tính sẵn sàng, trong nhiều trường hợp cũng an toàn hơn khi đặt cân bằng tải, giám sát và phục hồi trên các hệ thống này. Vì máy chủ ứng dụng có thể chết thường xuyên hơn
Về hiệu năng, trước hết có lợi thế là có thể tối ưu hóa cao một ứng dụng đơn giản và tập trung. Sau đó có thể dùng CPU hoặc phần cứng tăng tốc PCI để tăng tốc nén hoặc mã hóa, thường gọi là offloading. Cấu hình có hiệu quả chi phí tốt nhất là nhiều máy chủ đa dụng được hưởng lợi từ một số ít máy chủ offloading đắt tiền. Một số hệ thống còn dùng cân bằng tải để gửi lưu lượng đến máy chủ xử lý tốt nhất, giảm việc dùng tài nguyên đắt đỏ
Ở cấu hình tối thiểu thì không cần, nhưng ngay cả trong môi trường vận hành một host, nó cũng cho phép rolling release, nén, TLS, phục vụ file tĩnh nhanh và khả năng A/B testing tiềm năng
Một lớp trung gian giữa request và máy chủ có thể khá hữu ích
Hay đấy. Trước đây tôi cũng từng tự viết một web server bằng C, và mã nguồn ở bên dưới. Nó cũng từng chạy một website thương mại trong một thời gian
Thật đáng ngạc nhiên khi có thể làm một web server HTTP/1.1 nhỏ và nhẹ đến mức nào. Website thương mại đó chạy trên một máy có RAM 128MB và 1 CPU, là một hệ thống chat tương tác trên web mã nguồn đóng, phục vụ thường xuyên cho khá nhiều trường học ở Anh. Tuy nhiên đó là chuyện của 20 năm trước, khi Internet chưa thù địch như bây giờ
Bài viết nói bot đóng vai trò như những fuzzer rất tốt, nhưng tôi vẫn nghĩ nên thử fuzzing thực sự một chút
http://git.annexia.org/?p=rws.git;a=tree
Các thành phần cần thiết:
http://git.annexia.org/?p=c2lib.git;a=tree
http://git.annexia.org/?p=pthrlib.git;a=tree
Website của tôi https://blessed.rs chạy trên VM nhỏ nhất mà tôi tìm được, với RAM 256MB, nhưng thường chỉ dùng khoảng 60MB
Điều thực sự khiến tôi ngạc nhiên là khi bản đồ bộ nhớ chỉ có 5 page 4KB, fork trên Linux trở nên cực kỳ nhanh
Đây là một dự án nhỏ tôi bắt đầu trong thời gian rảnh cho vui, và tôi nghĩ mọi người ở đây sẽ thích :)
Tuyệt vời. Trước đây tôi đã nghĩ, và giờ vẫn thấy, việc dựng một dịch vụ tối thiểu bằng cách dùng API hệ thống ở mức thấp nhất như thế này thật sự rất thỏa mãn
Gần như ma thuật vậy, nhất là khi thấy nó xử lý lưu lượng thật. Việc một
poll()bình thường có thể đạt được những con số như vậy hơi đáng ngạc nhiên, nhưng có lẽ vì đã lâu rồi tôi không xử lý sự kiện hay benchmark ở mức đóTôi cũng thích cách quản lý kết nối bằng hàm theo từng kết nối cùng các struct, mảng liên quan, và cả mảng file descriptor cho
poll. Nó làm tôi nhớ đến cách làm trong nhiều gói mã nguồn mở nổi tiếng về thông lượng cao như nginx, Redis, memcachedĐiều đó khiến tôi nghĩ rằng ai cũng nên biết và thử mọi ngôn ngữ có thể, dù là ngôn ngữ lập trình hay ngôn ngữ tự nhiên. Suy nghĩ bằng một ngôn ngữ nào đó là một trải nghiệm độc đáo. Những bối cảnh khác nhau khiến mọi thứ có cảm giác khác đi, và cuối cùng dù làm những việc tương tự, góc nhìn cũng thay đổi
Chẳng hạn, để thật sự hiểu bản chất của Linux hay Git, bạn phải nói được ngôn ngữ đó và hiểu những sắc thái thường biến mất trong bản dịch. Cũng giống như muốn hiểu ý nghĩa chủ quan thật sự của từ “rừng” trong tiếng Nga thì phải nói và hiểu tiếng Nga
Bối cảnh thay đổi góc nhìn, và đôi khi thay đổi mọi thứ
Tôi cũng ngạc nhiên khi một
poll()bình thường chịu được đến mức đó. Tôi từng nghĩ rồi sẽ phải chuyển sangepoll, nhưngpoll()đang hoạt động rất tốtChẳng có gì mới dưới ánh mặt trời
Cái này cũng có thể thú vị: https://news.ycombinator.com/item?id=27431910
Tính đến năm 2024, instance althttpd của sqlite.org xử lý hơn 500 nghìn request HTTP mỗi ngày, khoảng 5–6 request mỗi giây, phục vụ khoảng 200GB nội dung mỗi ngày, khoảng 18Mbit/s, trên một máy Linode 40 USD/tháng. Load average của máy này thường ở quanh mức 0.5. Khoảng 19% request HTTP là CGI trỏ đến nhiều kho mã nguồn Fossil
Nếu muốn viết ứng dụng C nhưng không thoải mái khi tự viết phần trực tiếp tiếp xúc với Internet công cộng, Kore là một framework khá ổn
Nó có các tính năng tích hợp tiện lợi như quản lý chứng chỉ ACME, Pgsql, curl, websocket
Về cơ bản bạn có thể build module để chạy và ghép lại với nhau, cũng có thể trộn Lua/Python với C
https://kore.io/
Cuối cùng cũng có một website lên trang nhất mà không chết
Không có ý nói dự án này không tuyệt, nhưng nếu trong môi trường vận hành bạn thật sự coi trọng điều này và chủ yếu phục vụ nội dung tĩnh, thì cứ dùng CDN là được. Nó gần như luôn có hiệu năng tốt hơn bất cứ thứ gì bạn tự viết. Chỉ là không vui bằng thôi
Dự án gọn gàng và hay, nhưng tôi nghĩ phần lớn người ta sẽ vào GitHub chứ không vào link hiển thị trang web. Tôi có bỏ sót gì không?
Tôi thích đoạn: “Tôi thích tự làm công cụ của mình, và hơi chán khi cứ bị bảo rằng mọi thứ phải được kiểm chứng trong thực chiến. Vậy nếu nó chết thì sao? Có bug thì sửa thôi :^)”
Chỉ 3.4k dòng C mà đã có server HTTP và HTTPS hoàn chỉnh? Thành thật mà nói, tôi tưởng để tuân thủ đầy đủ đặc tả thì sẽ cần nhiều hơn thế rất nhiều
Chỉ cần nhận request GET và đặt
Content-Lengthtrong response là đủ cho 99% user agent. Xử lýTransfer-Encodingvà header byte range cũng không làm code phình ra quá nhiềuHTTPS chỉ là HTTP trên socket TLS, và nếu bạn không tự triển khai mã hóa thì mức abstraction đúng ra cũng chỉ đến vậy. Khá thú vị và thực ra không tệ đến thế
httpd(8)[1] của OpenBSD, tính cả tài liệu, hiện cũng chỉ hơi dưới 15.000 dòngNếu bỏ bớt vài tính năng và đặt ra một số giả định, thì việc rơi vào khoảng 5.000 dòng như dự án này cũng không có gì đáng ngạc nhiên
Kết quả
$ wc -l *là tổng cộng 14815 dòng[1]: https://man.openbsd.org/httpd.8
Tất nhiên tôi sẽ không đưa nó lên Internet công cộng và nó chỉ triển khai một phần rất nhỏ của HTTP/1.1, nhưng nó chạy được và chỉ cần
malloclúc khởi tạoTôi nhớ đến một bài thuyết trình ở Chaos Communication Congress về blog/web server viết bằng C
Nội dung nói về việc đưa vào nhiều tính năng bảo mật như kho lưu trữ bất biến, hạ quyền, không cho blog truy cập chứng chỉ TLS, v.v.: https://www.youtube.com/watch?v=TaE28fJVPTk