2 คะแนน โดย GN⁺ 2024-09-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

เทคโนโลยีบล็อกของฉัน

เว็บเซิร์ฟเวอร์นี้เป็นเว็บเซิร์ฟเวอร์ขนาดเล็กที่ออกแบบมาเพื่อโฮสต์บล็อกของฉันเอง สร้างขึ้นให้แข็งแกร่งตั้งแต่ต้นเพื่อให้ทนทานต่อการใช้งานบนอินเทอร์เน็ตสาธารณะได้ ไม่ต้องมี 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 ความคิดเห็น

 
GN⁺ 2024-09-26
ความคิดเห็นบน Hacker News
  • ไม่จำเป็นต้องมี reverse proxy: เคยใช้ Jetty เพื่อนำแอปขึ้นอินเทอร์เน็ตโดยไม่มี reverse proxy และไม่พบปัญหา

    • มีหลายความเห็นที่บอกให้ใช้ reverse proxy โดยไม่ได้ให้เหตุผลที่ชัดเจนด้านความปลอดภัยหรือประสิทธิภาพ
    • จึงตั้งคำถามว่า reverse proxy จำเป็นจริงหรือไม่
  • เว็บเซิร์ฟเวอร์ C ที่สร้างเอง: เคยสร้างเว็บเซิร์ฟเวอร์ C ที่ใช้รันเว็บไซต์เชิงพาณิชย์

    • รองรับทราฟฟิกจำนวนมากด้วย RAM 128MB และ CPU 1 ตัว
    • กล่าวถึงว่าสภาพแวดล้อมอินเทอร์เน็ตเมื่อ 20 ปีก่อนมีความเป็นปฏิปักษ์น้อยกว่า
    • บอตทำหน้าที่เป็น fuzzer ได้ดี แต่ก็ยังจำเป็นต้องทำ fuzzing จริงด้วย
  • ความพึงพอใจในการสร้างบริการเอง: การสร้างบริการพื้นฐานด้วย system API ให้ความรู้สึกพึงพอใจมาก

    • รู้สึกทึ่งที่ฟังก์ชัน poll() ให้ประสิทธิภาพสูง
    • ฟังก์ชันต่อการเชื่อมต่อ พร้อม struct และ array ที่เกี่ยวข้อง คล้ายกับ nginx, redis และ memcached
    • เป็นงานที่ยอดเยี่ยม
  • แนะนำโปรเจ็กต์เล็ก ๆ: แนะนำโปรเจ็กต์สนุก ๆ ที่เริ่มทำในเวลาว่าง

  • แนะนำเฟรมเวิร์ก Kore: หากการเขียนส่วนที่เปิดสู่ภายนอกของแอป C เป็นเรื่องไม่สะดวก ขอแนะนำเฟรมเวิร์ก Kore

    • มีฟีเจอร์ในตัว เช่น การจัดการใบรับรอง ACME, Pgsql, curl และ WebSocket
    • สามารถผสม Lua/Python กับ C เพื่อ build และรันโมดูลได้
  • แชร์ลิงก์ที่น่าสนใจ: อินสแตนซ์ althttpd ของ sqlite.org รองรับคำขอ HTTP มากกว่า 500,000 ครั้งต่อวัน

    • ให้บริการคอนเทนต์ขนาด 200GB บน Linode ราคา $40/เดือน
    • 19% ของคำขอ HTTP เข้าถึงที่เก็บซอร์สโค้ด Fossil ผ่าน CGI
  • ความสนุกของการสร้างเครื่องมือเอง: เริ่มเบื่อกับความเห็นที่ว่าทุกอย่างต้อง "ผ่านการทดสอบในสนามจริง"

    • บั๊กแก้ไขได้
  • บรรยายใน Chaos Communication Congress: ทำให้นึกถึงบรรยายเกี่ยวกับบล็อก/เว็บเซิร์ฟเวอร์ที่เขียนด้วย C และมีฟีเจอร์ความปลอดภัยรวมอยู่ด้วย

    • มีฟีเจอร์อย่างสตอเรจแบบ immutable, การลดสิทธิ์, และการเข้าถึงใบรับรอง TLS ไม่ได้
  • เว็บไซต์ที่เสถียร: เป็นเว็บไซต์ที่ไม่แครชแม้จะขึ้นไปอยู่หน้าแรก

  • กลับสู่พื้นฐาน: ชอบแนวทางกลับไปใช้พื้นฐานโดยใช้เท่าที่จำเป็น

    • ตั้งคำถามว่าฟีเจอร์ที่ไม่จำเป็นในซอฟต์แวร์ส่งผลต่อประสิทธิภาพอย่างไร
    • ขอแสดงความยินดีกับนักพัฒนา