เปิดตัวบน HN: โฮสต์เว็บไซต์ด้วยเว็บเซิร์ฟเวอร์ C
(github.com/cozis)เทคโนโลยีบล็อกของฉัน
เว็บเซิร์ฟเวอร์นี้เป็นเว็บเซิร์ฟเวอร์ขนาดเล็กที่ออกแบบมาเพื่อโฮสต์บล็อกของฉันเอง สร้างขึ้นให้แข็งแกร่งตั้งแต่ต้นเพื่อให้ทนทานต่อการใช้งานบนอินเทอร์เน็ตสาธารณะได้ ไม่ต้องมี reverse proxy สามารถดูการทำงานจริงได้ที่ http://playin.coz.is/index.html ฉันเคยขอให้คนบน Reddit ลองแฮ็กมันเพื่อความสนุก และเก็บล็อกของคำขอทั้งแบบสนุก ๆ และมุ่งร้ายได้เป็นระดับกิกะไบต์ บางส่วนถูกเก็บไว้ใน attempts.txt และฉันอาจจะกลับไปไล่ดูเพิ่มเติมเล่น ๆ ทีหลัง
แต่...ทำไม?
ฉันชอบสร้างเครื่องมือของตัวเอง และเริ่มเบื่อกับการได้ยินว่าทุกอย่างต้อง "ผ่านการทดสอบภาคสนาม" แล้วถ้ามันแครชล่ะ? ก็แก้บั๊กได้
สเปก
- ใช้ได้บน Linux เท่านั้น
- รองรับ HTTP/1.1, pipelining และการเชื่อมต่อแบบ keep-alive
- รองรับ HTTPS (ใช้ BearSSL รองรับถึง TLS 1.2)
- มี dependency น้อยที่สุด (ถ้าใช้ HTTPS จะมีแค่ libc กับ BearSSL)
- ตั้งค่า timeout ได้
- มี access log, crash log, log rotation และการจำกัดการใช้ดิสก์
- ไม่มี
Transfer-Encoding: Chunked(จะตอบกลับด้วย411 Length Requiredเพื่อให้ไคลเอนต์ส่งใหม่พร้อมContent-Length) - ใช้คอร์เดียว (มีแผนจะเปลี่ยนเมื่อได้ VPS ที่ดีกว่า)
- ยังไม่มีการแคชไฟล์แบบ static
เบนช์มาร์ก
แม้โปรเจ็กต์นี้จะเน้นเรื่องความทนทาน แต่ก็ไม่ได้ช้าเลย นี่คือการเทียบแบบง่าย ๆ กับ nginx (endpoint แบบ static, ทั้งคู่รัน single-thread, จำกัด 1K connections):
-
(blogtech)
$ wrk -c 500 -d 5s http://127.0.0.1:80/hello- latency เฉลี่ย: 6.66ms
- requests/sec: 76974.24
- transfer/sec: 6.09MB
-
(nginx)
$ wrk -c 500 -d 5s http://127.0.0.1:8080/hello- latency เฉลี่ย: 149.11ms
- requests/sec: 44227.78
- transfer/sec: 8.27MB
การตั้งค่า 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 และรัน
โดยค่าเริ่มต้น เซิร์ฟเวอร์จะถูก build แบบรองรับ HTTP อย่างเดียว:
$ make
คำสั่งนี้จะสร้างไฟล์ปฏิบัติการ serve (release build), serve_cov (coverage build) และ serve_debug (debug build) โดย release build จะฟังที่พอร์ต 80 และ debug build จะฟังที่พอร์ต 8080
หากต้องการเปิดใช้ HTTPS ต้อง clone และ build BearSSL ก่อน:
$ mkdir 3p
$ cd 3p
$ git clone https://www.bearssl.org/git/BearSSL
$ cd BearSSL
$ make -j
$ cd ../../
$ make -B HTTPS=1
จะได้ไฟล์ปฏิบัติการชุดเดิม แต่สามารถให้บริการการเชื่อมต่อแบบปลอดภัยได้ที่พอร์ต 443 (release) หรือ 8081 (debug) ต้องวางไฟล์ cert.pem และ key.pem ไว้ในไดเรกทอรีเดียวกับไฟล์ปฏิบัติการ หากต้องการเปลี่ยนชื่อหรือตำแหน่ง ให้แก้ไข:
#define HTTPS_KEY_FILE "key.pem"
#define HTTPS_CERT_FILE "cert.pem"
หากต้องการทดสอบ HTTPS ในเครื่อง ให้สร้างใบรับรองแบบ self-signed (และ private key):
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365
วิธีใช้งาน
เซิร์ฟเวอร์จะให้บริการเนื้อหาแบบ static จากโฟลเดอร์ docroot/ หากต้องการเปลี่ยน ให้แก้ฟังก์ชัน 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 :|"));
}
คุณสามารถเพิ่ม endpoint ได้จากตรงนี้โดยแตกแขนงตามฟิลด์ request.path โดย path เป็นเพียง slice ของบัฟเฟอร์คำขอเท่านั้น URI ไม่ได้ถูก parse
การทดสอบ
ฉันรันเซิร์ฟเวอร์กับ valgrind และ sanitizers (address, undefined) เป็นประจำ และใช้ wrk ยิงทดสอบ นอกจากนี้ยังเพิ่มการทดสอบอัตโนมัติใน tests/test.py เพื่อเช็กการทำตามสเปก HTTP/1.1 อีกด้วย ฉันยังใช้มันโฮสต์เว็บไซต์ของตัวเองและโพสต์ลิงก์ไปตามที่ต่าง ๆ เพื่อให้มีโหลดต่อเนื่อง บรรดาบอตที่สแกนหาเว็บไซต์เปราะบางบนอินเทอร์เน็ตก็เป็น fuzzer ชั้นดี
ปัญหาที่ทราบ
- เซิร์ฟเวอร์ตอบกลับไคลเอนต์ HTTP/1.0 ด้วย HTTP/1.1
การมีส่วนร่วม
ฉันทำงานบนสาขา DEV เป็นหลัก และจะ merge ไป MAIN เป็นครั้งคราว ถ้าจะเปิด pull request การตั้งเป้าไปที่ DEV จะสะดวกกว่า
สรุปโดย GN⁺
- โปรเจ็กต์นี้เป็นเว็บเซิร์ฟเวอร์ที่มุ่งเน้น dependency ต่ำและความทนทาน
- รองรับ HTTP/1.1 และ HTTPS พร้อมฟังก์ชันด้านล็อกหลากหลายและ timeout ที่ปรับแต่งได้
- ผลเบนช์มาร์กแสดงเวลาในการตอบสนองที่เร็วกว่าของ nginx
- ออกแบบมาเพื่อให้นักพัฒนาสนุกกับการสร้างเครื่องมือของตัวเองและแก้บั๊กได้
- โปรเจ็กต์ที่มีความสามารถใกล้เคียงกัน ได้แก่ Nginx และ Apache HTTP Server
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ไม่จำเป็นต้องมี reverse proxy: เคยใช้ Jetty เพื่อนำแอปขึ้นอินเทอร์เน็ตโดยไม่มี reverse proxy และไม่พบปัญหา
เว็บเซิร์ฟเวอร์ C ที่สร้างเอง: เคยสร้างเว็บเซิร์ฟเวอร์ C ที่ใช้รันเว็บไซต์เชิงพาณิชย์
ความพึงพอใจในการสร้างบริการเอง: การสร้างบริการพื้นฐานด้วย system API ให้ความรู้สึกพึงพอใจมาก
poll()ให้ประสิทธิภาพสูงแนะนำโปรเจ็กต์เล็ก ๆ: แนะนำโปรเจ็กต์สนุก ๆ ที่เริ่มทำในเวลาว่าง
แนะนำเฟรมเวิร์ก Kore: หากการเขียนส่วนที่เปิดสู่ภายนอกของแอป C เป็นเรื่องไม่สะดวก ขอแนะนำเฟรมเวิร์ก Kore
แชร์ลิงก์ที่น่าสนใจ: อินสแตนซ์ althttpd ของ sqlite.org รองรับคำขอ HTTP มากกว่า 500,000 ครั้งต่อวัน
ความสนุกของการสร้างเครื่องมือเอง: เริ่มเบื่อกับความเห็นที่ว่าทุกอย่างต้อง "ผ่านการทดสอบในสนามจริง"
บรรยายใน Chaos Communication Congress: ทำให้นึกถึงบรรยายเกี่ยวกับบล็อก/เว็บเซิร์ฟเวอร์ที่เขียนด้วย C และมีฟีเจอร์ความปลอดภัยรวมอยู่ด้วย
เว็บไซต์ที่เสถียร: เป็นเว็บไซต์ที่ไม่แครชแม้จะขึ้นไปอยู่หน้าแรก
กลับสู่พื้นฐาน: ชอบแนวทางกลับไปใช้พื้นฐานโดยใช้เท่าที่จำเป็น