2 คะแนน โดย GN⁺ 2024-09-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • BlogTech เป็นชุดเครื่องมือบนภาษา C สำหรับจัดการเว็บไซต์ขนาดเล็กถึงขนาดกลาง โดยมี HTTPS, virtual host, การจัดการใบรับรองอัตโนมัติผ่าน ACME และไคลเอนต์สำหรับจัดการเซิร์ฟเวอร์ระยะไกล
  • ปัจจุบันยังอยู่ใน ขั้นทดสอบ เวอร์ชัน 0.4.x เคยถูกใช้รันบริการเป็นเวลาหลายสัปดาห์แล้ว แต่ไม่ได้ถูกนำเสนอว่าเป็น stable release
  • เซิร์ฟเวอร์รันได้บน Linux และ Windows แต่ HTTPS รองรับเฉพาะ Linux และบน Windows ทั้งไคลเอนต์และเซิร์ฟเวอร์ใช้ได้เฉพาะ HTTP
  • การจัดการไฟล์ระยะไกลทำผ่าน --put, --get, --delete และคำขอที่เปลี่ยนแปลงทรัพยากรจะยืนยันตัวตนด้วย HMAC/SHA256 signature ตาม shared secret key
  • ในสภาพแวดล้อม production ต้องมี HTTP server ที่พอร์ต 80 และระเบียน DNS ของโดเมน เมื่อเปิด ACME จะสร้างไฟล์ใบรับรองและ private key โดยอัตโนมัติ และบันทึกข้อผิดพลาดไว้ใน acme.log

เป้าหมายและสถานะปัจจุบันของ BlogTech

  • BlogTech เป็นชุดเครื่องมือสำหรับจัดการเว็บไซต์ขนาดเล็กถึงขนาดกลาง
  • ฟีเจอร์ที่รองรับมีดังนี้
    • HTTPS

    • Virtual host

      • การจัดการใบรับรองอัตโนมัติ ผ่าน ACME
      • ไคลเอนต์ สำหรับจัดการเซิร์ฟเวอร์ระยะไกล
      • ยังอยู่ใน ขั้นทดสอบ
      • มีกรณีที่เวอร์ชัน 0.4.x ให้บริการ https://coz.is/ ได้สำเร็จเป็นเวลาหลายสัปดาห์
      • เวอร์ชันไฟล์เดี่ยวแบบเดิมอยู่ใน branch single_file

การ build และการรันพื้นฐาน

  • BlogTech รันได้บน Linux และ Windows
    • การ build บน Linux ต้องมี OpenSSL development libraries และ gcc
    • การ build บน Windows ต้องมี clang
  • การ build ทำผ่านสคริปต์ตามแพลตฟอร์ม
    • Linux: ./build.sh
    • Windows: .\build.bat
  • ผลลัพธ์จากการ build มีดังนี้
    • Linux: blogtech
    • Windows: blogtech.exe
  • บน Linux สามารถติดตั้งได้ด้วย ./install.sh
  • ตัวอย่างการรันเซิร์ฟเวอร์พื้นฐานคือสร้างไดเรกทอรี docroot แล้วระบุ --serve, --document-root=docroot, --skip-auth-check
  • HTTP server พื้นฐานจะ listen ที่ 127.0.0.1:8080 และให้บริการคอนเทนต์จาก docroot
  • เมื่อ docroot ว่าง การเข้าถึง http://127.0.0.1:8080/ จะคืนค่า 404

การจัดการไฟล์ระยะไกลและการยืนยันตัวตน

  • BlogTech สามารถอัปโหลดไฟล์ไปยังเซิร์ฟเวอร์ในโหมดไคลเอนต์ได้
    • อัปโหลดไฟล์ด้วย --put
    • ดาวน์โหลดไฟล์ระยะไกลด้วย --get
    • ลบไฟล์ระยะไกลด้วย --delete
  • ไม่ว่าจะรันไคลเอนต์และเซิร์ฟเวอร์บนเครื่องเดียวกัน หรือกับเซิร์ฟเวอร์บนเครื่องระยะไกล ก็ทำงานในลักษณะเดียวกัน
  • คำขอที่เปลี่ยนแปลงทรัพยากรจะถูกเซ็นแบบดิจิทัลด้วย HMAC/SHA256 signature
  • ไคลเอนต์และเซิร์ฟเวอร์ต้องใช้ secret key เดียวกันร่วมกัน โดยตามธรรมเนียมจะเก็บไว้ในไฟล์ admin.pwd
  • ระบุไฟล์รหัสผ่านด้วย option --auth-password-file
  • หากไม่มีไฟล์รหัสผ่าน คำขอทั้งหมดที่ต้องยืนยันตัวตนจะถูกปฏิเสธ
  • รหัสผ่านว่างก็จะถูกปฏิเสธเช่นกัน
  • ระหว่างพัฒนา สามารถใช้ --skip-auth-check เพื่อถือว่าคำขอทั้งหมดผ่านการยืนยันตัวตนแล้วได้
    • เมื่อใช้ option นี้ แม้จะระบุ --auth-password-file ไว้ก็จะถูกละเว้น
    • ควรใช้อย่างระมัดระวัง
  • เงื่อนไขของ header ยืนยันตัวตนและการส่งซ้ำ

    • HTTP request ที่ยืนยันตัวตนแล้วจะมี header X-BlogTech-
    • X-BlogTech-Nonce: token ที่ไคลเอนต์สุ่มเลือก
    • X-BlogTech-Timestamp: UNIX timestamp ณ เวลาที่คำขอถูกเซ็นครั้งแรก
    • X-BlogTech-Expire: ระยะเวลาเป็นวินาทีที่คำขอยังคงมีผล นับจากเวลาที่เซ็น
    • X-BlogTech-Signature: ค่า HMAC ของข้อมูลคำขอที่เข้ารหัสเป็น Base64
    • signature ได้มาจากการสร้างสตริงคำขอแบบ normalized จากนั้นคำนวณ HMAC/SHA256 โดยใช้รหัสผ่านสำหรับยืนยันตัวตนเป็น key แล้วเข้ารหัสผลลัพธ์เป็น Base64 พร้อม padding
    • nonce ที่เซิร์ฟเวอร์เคยเห็นแล้วจะถูกเก็บไว้ใน หน่วยความจำ
    • เมื่อเซิร์ฟเวอร์ reload จะลืม nonce ก่อนหน้า ดังนั้นคำขอที่ยังไม่หมดอายุอาจถูกส่งซ้ำได้

Virtual host และการ mapping ไดเรกทอรี

  • BlogTech สามารถโฮสต์หลายเว็บไซต์บนเซิร์ฟเวอร์เดียวกันได้
  • ตั้งค่าโดเมนด้วยการระบุ option --domain หลายครั้ง
  • BlogTech จะสร้างไดเรกทอรีแยกตามโดเมนภายใน document root
    • default
    • websiteA.com
    • websiteB.com
  • คำขอที่เข้ามายัง host เฉพาะจะอ้างอิงไดเรกทอรีที่เชื่อมโยงกับ host นั้น
  • คำขอที่ไม่ได้เชื่อมโยงกับโฟลเดอร์เฉพาะจะอ้างอิงไดเรกทอรี default
  • ในตัวอย่าง ไฟล์ที่อัปโหลดไปยัง websiteA.com, websiteB.com, other.com จะถูกบันทึกไว้ในไดเรกทอรีของโดเมนแต่ละรายการหรือไดเรกทอรี default

HTTPS และการตั้งค่าใบรับรอง

  • HTTPS รองรับเฉพาะบน Linux
  • เนื่องจาก cHTTP ซึ่งเป็น HTTP library เบื้องหลัง implement HTTPS ด้วย OpenSSL
  • บน Windows ทั้งไคลเอนต์และเซิร์ฟเวอร์ใช้ได้เฉพาะ HTTP
  • หากต้องการเปิด HTTPS ต้องมี option ต่อไปนี้
    • --https-enabled
    • --cert-file
    • --cert-key-file
  • ที่อยู่ listen เริ่มต้นของ HTTPS คือ 127.0.0.1:8443
  • สามารถเปลี่ยนที่อยู่และพอร์ตที่ listen ได้ด้วย option ต่อไปนี้
    • --https-addr=<addr>
    • --https-port=<port>
  • ระหว่างพัฒนา สามารถสร้าง ใบรับรองแบบ self-signed ด้วยคำสั่ง OpenSSL ได้
  • เบราว์เซอร์ไม่อนุญาตให้ท่องเว็บเซิร์ฟเวอร์ HTTPS ที่ใช้ใบรับรองแบบ self-signed
  • cURL ต้องใช้ flag --insecure จึงจะเข้าถึงเซิร์ฟเวอร์ที่ใช้ใบรับรองแบบ self-signed ได้
  • หากต้องการใบรับรองหลายใบ สามารถส่ง --extra-cert เพิ่มได้
    • ใบรับรองหลักระบุด้วย --cert-file, --cert-key-file
    • ใบรับรองเพิ่มเติมจะถูกใช้เมื่อไคลเอนต์ร้องขอโดเมนเฉพาะ
  • option บรรทัดคำสั่งที่ยาวสามารถย้ายไปไว้ในไฟล์ config ได้

การออกใบรับรองอัตโนมัติผ่าน ACME

  • โปรโตคอล ACME ช่วยให้เว็บเซิร์ฟเวอร์ขอออกใบรับรองจาก Certificate Authority ได้โดยอัตโนมัติ
  • BlogTech รองรับ flow ที่เริ่มต้นในโหมด HTTPS โดยยังไม่มีใบรับรอง แล้วจึงสร้างใบรับรองขึ้นมา
  • ในสภาพแวดล้อม production ต้องมีเงื่อนไขต่อไปนี้
    • รัน HTTP server ที่ พอร์ต 80
    • สร้าง DNS record ที่เชื่อมโดเมนเข้ากับเครื่องที่รันเซิร์ฟเวอร์
  • เปิด ACME โดยระบุ --acme-enabled ร่วมกับ option ของ HTTPS
  • ต่างจากโหมด HTTPS ทั่วไป ในโหมด ACME จะคาดว่าใบรับรองยังไม่มีอยู่
  • การสร้างใบรับรองต้องใช้ค่าต่อไปนี้
    • --acme-domain
    • --acme-email
    • --acme-country
    • --acme-organization
    • --acme-agree-tos
  • หากประมวลผลสำเร็จ จะสร้างไฟล์ต่อไปนี้
    • acme_key.pem: private key ที่เชื่อมโยงกับบัญชี ACME
    • cert.pem: ใบรับรองที่ออกให้ หรือไฟล์ที่ระบุด้วย --cert-file
    • key.pem: private key ที่เชื่อมโยงกับใบรับรอง หรือไฟล์ที่ระบุด้วย --cert-key-file
  • หากเกิดข้อผิดพลาด ข้อความจะถูกบันทึกไว้ใน acme.log
  • หากระบุ --acme-domain หลายครั้ง จะสามารถให้ ACME จัดการหลายโดเมนได้
  • ใบรับรองผลลัพธ์จะรวมโดเมนทั้งหมดที่ระบุไว้

การทดสอบไคลเอนต์ ACME

  • หากต้องการทดสอบไคลเอนต์ ACME บน Linux ต้องติดตั้ง Docker และ clone Pebble ACME server
  • ใน docker-compose.yml ให้เพิ่มรายการ extra_hosts ที่เชื่อมโดเมนทดสอบไปยัง host-gateway
  • ใน /etc/hosts ให้ map โดเมนทดสอบไปยัง 127.0.0.1
  • เริ่ม Pebble ด้วย docker compose up
  • BlogTech รัน instance สำหรับทดสอบด้วย ./blogtech -s --config=misc/pebble_blogtech.conf
  • การโต้ตอบกับ ACME server จะแสดงออกทาง stdout
  • หากสำเร็จ จะสร้าง acme_key.pem, cert.pem, key.pem
  • ทดสอบการต่ออายุใบรับรองได้โดยเปลี่ยนค่า validityPeriod ใน pebble/test/config/pebble-config.json
  • สามารถใช้ option --acme-force-renewal-period=<maximum duration in ms> เพื่อสั่งให้ต่ออายุ แม้ใบรับรองยังไม่หมดอายุก็ตาม

ไฟล์ config และการโหลดอัตโนมัติ

  • BlogTech สามารถย้าย command-line arguments จำนวนเท่าใดก็ได้ไปไว้ใน ไฟล์ config
  • ตัวอย่างเช่น เขียน option ที่เกี่ยวข้องกับ HTTPS และ ACME ไว้ใน blogtech_server.conf แล้วรันด้วย --config=blogtech_server.conf
  • หากชื่อไฟล์ config เป็น blogtech.conf พอดี BlogTech จะโหลดโดยอัตโนมัติ
  • ในกรณีนี้สามารถรันแบบ ./blogtech --serve ได้
  • หากต้องการละเว้นการโหลดไฟล์ config แบบ implicit ให้ใช้ --no-config

Crash log และ systemd daemon

  • หาก BlogTech crash ในโหมดเซิร์ฟเวอร์ จะสร้างไฟล์ crash.bin
  • เมื่อเริ่มเซิร์ฟเวอร์ครั้งถัดไป crash.bin จะถูกแปลงเป็น crash.log ซึ่งเป็น stack trace ที่มนุษย์อ่านได้
  • ขั้นตอนการแปลง address เป็นชื่อ symbol หรือเลขบรรทัดอาจยังไม่เสถียรนัก
  • BlogTech สามารถติดตั้งเป็น systemd daemon ได้
  • ตัวอย่าง blogtech.service จะรัน /root/blogtech/blogtech -s รีสตาร์ทเมื่อ fail และตั้ง working directory เป็น /root/blogtech/
  • option ของเซิร์ฟเวอร์จะถูกโหลดอัตโนมัติจาก /root/blogtech/blogtech.conf
  • หลังคัดลอก service file ไปยัง /etc/systemd/system/ แล้ว ให้เปิดใช้งานและเริ่มด้วยคำสั่งต่อไปนี้
    • systemctl daemon-reload
    • systemctl enable blogtech
    • systemctl start blogtech
  • จัดการ service ได้ด้วย systemctl start, systemctl stop, systemctl restart, journalctl -u blogtech

1 ความคิดเห็น

 
GN⁺ 2024-09-26
ความคิดเห็นจาก Hacker News
  • ส่วนที่ว่า “ไม่จำเป็นต้องมี reverse proxy” ทำให้ผมสงสัยอยู่เสมอ
    ถ้าไม่มีเหตุผลพิเศษที่ reverse proxy จะช่วยได้ ผมก็มักจะเปิดแอป Jetty แบบ embedded ออกสู่อินเทอร์เน็ตโดยตรงโดยไม่มีอะไรอยู่ข้างหน้า และก็ไม่เคยมีปัญหา
    คนดูแลอินฟราหรือความปลอดภัยมักถามว่าทำไมไม่วาง nginx ไว้ข้างหน้า แต่พอถามเหตุผล ก็มักพูดกว้าง ๆ เรื่องความปลอดภัยหรือประสิทธิภาพ โดยขาดความชัดเจน คำตอบที่เป็นรูปธรรมที่สุดคือ slow loris แต่เรื่องนั้นก็ไม่ใช่ปัญหาใหญ่มานานแล้ว
    ผมสงสัยว่า reverse proxy กลายเป็น cargo cult ในหมู่คนทำงานไปแล้วหรือเปล่า หรือผมพลาดเหตุผลดี ๆ ที่ใช้ได้ทั่วไปอยู่

    • สำหรับผม reverse proxy ทำให้เซิร์ฟเวอร์ต้นทางทำแค่ ให้บริการแอปพลิเคชัน เท่านั้น การทำ TLS termination, load balancing, URL rewrite และฟังก์ชันความปลอดภัยอย่าง WAF เมื่อจำเป็น ล้วนจัดการได้ที่ reverse proxy จึงเป็นการ แยกบทบาท
      โดยรวมแล้วช่วยปกป้องเซิร์ฟเวอร์ต้นทางและให้รับเฉพาะทราฟฟิกที่เกี่ยวข้องได้ แม้ในกรณีให้โดเมนแบบกำหนดเองแก่ลูกค้า DNS ของลูกค้าก็จะชี้ไปที่ reverse proxy ไม่ใช่เซิร์ฟเวอร์ต้นทาง ดังนั้นถ้าจำเป็นต้องเปลี่ยนเซิร์ฟเวอร์ต้นทาง ก็ไม่ต้องกังวลเรื่องให้ลูกค้าแก้ DNS
    • ผมรันโปรแกรมเซิร์ฟเวอร์หลายตัวใน homelab
      แต่ละตัวรันบนพอร์ตต่างกัน แต่ผมอยากให้ทั้งหมดเข้าถึงจากสาธารณะได้ด้วย URL คนละอัน และอยากเปิดเผยต่ออินเทอร์เน็ตแค่ พอร์ต 443
      ยังอยากให้ต่ออายุใบรับรอง TLS ของแต่ละโดเมนอัตโนมัติด้วย ความต้องการข้อแรกต้องใช้ reverse proxy และ Caddy ทำได้ทั้งสองอย่าง
      ถ้ารันแค่เซิร์ฟเวอร์เดียว และเซิร์ฟเวอร์นั้นทำ TLS termination เองอยู่แล้ว reverse proxy ก็ไม่จำเป็นเสมอไป
    • ในการ deploy ส่วนใหญ่ ผลกระทบด้านประสิทธิภาพของ reverse proxy นั้นน้อยจนมองข้ามได้ และผมก็เตรียมคอนฟิกไว้ล่วงหน้าอยู่แล้ว
      ภายหลังสามารถเพิ่ม TLS termination, URL rewrite และงานอื่น ๆ ได้โดยไม่ต้องลงแรงมาก เลยใช้จนเป็นนิสัย และจนถึงตอนนี้นิสัยนั้นก็ตอบแทนผมดี
    • ถ้าจะตอบในมุมที่ใช้ได้กับเซิร์ฟเวอร์หลายประเภทที่วางอยู่หน้าเว็บแอปพลิเคชัน ก็มีหลายวิธีที่อุปกรณ์เพิ่มเติมช่วยได้ทั้งด้านความปลอดภัยและประสิทธิภาพ
      ด้านความปลอดภัย เราอยากให้ทั้งระบบมีโค้ดให้น้อยที่สุดเท่าที่ทำได้ และใช้ระบบปฏิบัติการที่แข็งแกร่ง แอปแบบพร็อกซีอาจเรียบง่ายกว่าเว็บ/แอปพลิเคชันเซิร์ฟเวอร์มาก และยังสามารถกรองทราฟฟิกขาเข้า ตรวจสอบอินพุต หรือแปลงให้อยู่ในรูปแบบที่ parse ได้ปลอดภัยและเร็วกว่าได้ด้วย อาจรันบนระบบปฏิบัติการที่โจมตียากอย่าง OpenBSD, GenodeOS, INTEGRITY-178B ก็ได้
      ด้านความพร้อมใช้งาน การวาง load balancing, monitoring และ recovery ไว้ในระบบเหล่านี้ก็มักปลอดภัยกว่า เพราะแอปพลิเคชันเซิร์ฟเวอร์อาจล่มบ่อยกว่า
      ด้านประสิทธิภาพ ข้อดีแรกคือสามารถปรับแต่งแอปที่เรียบง่ายและมีหน้าที่เฉพาะให้เหมาะสมได้อย่างมาก จากนั้นอาจใช้ตัวเร่งฮาร์ดแวร์ CPU หรือ PCI เพื่อเร่งการบีบอัดหรือการเข้ารหัส ซึ่งมักเรียกว่า offloading โครงสร้างที่คุ้มค่าที่สุดคือให้เซิร์ฟเวอร์ทั่วไปจำนวนมากได้รับประโยชน์จากเซิร์ฟเวอร์ offloading ราคาแพงจำนวนน้อย บางส่วนใช้ load balancing เพื่อส่งทราฟฟิกขาเข้าไปยังเซิร์ฟเวอร์ที่รับมือได้ดีที่สุด ลดการใช้ทรัพยากรราคาแพง
    • ผมไม่คิดว่ามีหลักทั่วไปที่ใช้ได้กับทุกเซิร์ฟเวอร์
      ในคอนฟิกขั้นต่ำสุดอาจไม่จำเป็น แต่แม้ในสภาพแวดล้อมที่รันบนโฮสต์เดียว ก็ช่วยให้ทำ rolling release, การบีบอัด, TLS, การเสิร์ฟไฟล์ static อย่างรวดเร็ว และ A/B testing ที่อาจต้องใช้ได้
      ชั้นคั่นกลางระหว่าง request กับเซิร์ฟเวอร์มีประโยชน์พอสมควร
  • เยี่ยมเลย ผมเองก็เคยเขียน เว็บเซิร์ฟเวอร์ภาษา C ขึ้นมา และมีซอร์สอยู่ด้านล่าง เคยใช้รันเว็บไซต์เชิงพาณิชย์อยู่พักหนึ่งด้วย
    น่าทึ่งว่าเว็บเซิร์ฟเวอร์ HTTP/1.1 สามารถทำให้เล็กและเบาได้แค่ไหน เว็บไซต์เชิงพาณิชย์นั้นรันบนเครื่องที่มี RAM 128MB และ CPU 1 ตัว และเป็นระบบแชตบนเว็บแบบโต้ตอบที่เป็นซอฟต์แวร์ปิด ใช้ให้บริการโรงเรียนในสหราชอาณาจักรจำนวนมากเป็นประจำ แต่แน่นอนว่านั่นเป็นเรื่องเมื่อ 20 ปีก่อน ตอนที่อินเทอร์เน็ตยังไม่เป็นศัตรูเท่าทุกวันนี้
    ในบทความบอกว่าบอตทำหน้าที่เป็น fuzzer ได้ยอดเยี่ยม แต่ผมก็ยังคิดว่าควรทำ fuzzing จริง ๆ บ้างสักหน่อย
    http://git.annexia.org/?p=rws.git;a=tree
    สิ่งที่ต้องใช้:
    http://git.annexia.org/?p=c2lib.git;a=tree
    http://git.annexia.org/?p=pthrlib.git;a=tree

    • ถ้าจะรันเว็บเซิร์ฟเวอร์ขนาดประมาณนี้โดยไม่ต้องกังวลกับอินเทอร์เน็ตที่เป็นศัตรูมากเกินไป Rust เป็นตัวเลือกที่ดี
      เว็บไซต์ของผม https://blessed.rs รันบน VM ที่เล็กที่สุดเท่าที่หาได้ คือ RAM 256MB แต่โดยปกติใช้เพียงประมาณ 60MB
    • นี่ดูใช้งานได้จริงมากกว่าเว็บเซิร์ฟเวอร์ HTTP/1.0 ขนาดเล็กและเบาที่ผมทำเองมาก แต่ rws ก็คงไม่ได้เล็กและเบาขนาดนั้น: http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/httpdito-readme
      สิ่งที่น่าทึ่งจริง ๆ คือเมื่อ memory map มีแค่หน้า 4KB จำนวน 5 หน้า fork ของ Linux จะเร็วมาก
  • เป็นโปรเจกต์เล็ก ๆ ที่เริ่มทำเล่นในเวลาว่าง คิดว่าที่นี่น่าจะชอบ :)

    • การไล่อ่านบั๊กและ CVE เก่า ๆ ของ HTTP server แล้วดูว่ามีผลกับโค้ดของตัวเองไหม และจะแก้ได้อย่างไร เป็นแบบฝึกหัดที่น่าสนใจ การลองทำอะไรแบบนี้เองสนุกดี
    • พอดีผมอยากลองจับ C11 concurrency API และในฐานะคนที่มาจาก C++ ก็สงสัยว่าโครงสร้างพวกนั้นทำงานอย่างไรใน C เลยอยากลองเขียนอะไรคล้ายเซิร์ฟเวอร์ดู
  • ยอดเยี่ยมมาก ผมคิดมานานแล้ว และตอนนี้ก็ยังคิดแบบนั้นอยู่ ว่าการใช้ system API ระดับต่ำสุดแบบนี้เพื่อรันบริการขั้นต่ำขึ้นมามันน่าพอใจจริง ๆ
    แทบเหมือนเวทมนตร์ และยิ่งเป็นอย่างนั้นเมื่อเห็นมันจัดการทราฟฟิกจริงได้ ผมแปลกใจนิดหน่อยที่ poll() ธรรมดา ๆ ทำตัวเลขได้ถึงขนาดนั้น แต่คงเป็นเพราะผมห่างจากการจัดการอีเวนต์หรือ benchmark ในระดับนั้นมานานแล้ว
    ชอบฟังก์ชันต่อการเชื่อมต่อหนึ่งรายการ รวมถึง struct ที่เกี่ยวข้อง วิธีจัดการการเชื่อมต่อด้วย array และ array ของ file descriptor สำหรับ poll ด้วย ทำให้นึกถึงแนวทางที่แพ็กเกจโอเพนซอร์สหลายตัวซึ่งขึ้นชื่อเรื่อง throughput สูงอย่าง nginx, Redis, memcached ใช้กัน

    • ตอนเรียนมหาวิทยาลัยแล้วต้องทำ C/C++ รู้สึกเหมือนหัวจะแตก มันเป็นประสบการณ์ที่เฉพาะทางมากและทำให้ถ่อมตัวลง ซึ่งมีสิ่งที่ผมชอบอย่างวิศวกรรม ประวัติศาสตร์ วัฒนธรรม ภาษาศาสตร์ ฯลฯ ปะปนอยู่เล็กน้อยทั้งหมด
      มันทำให้ผมคิดว่าทุกคนควรรู้และลองใช้ภาษาให้มากที่สุดเท่าที่ทำได้ ไม่ว่าจะเป็นภาษาโปรแกรมหรือภาษาธรรมชาติ การได้ คิดด้วย ภาษาใดภาษาหนึ่งเป็นประสบการณ์ที่ไม่เหมือนใคร บริบทที่ต่างกันทำให้ทุกอย่างรู้สึกต่างออกไป และแม้สุดท้ายจะทำสิ่งคล้ายกัน มุมมองก็เปลี่ยนไป
      ตัวอย่างเช่น ถ้าจะเข้าใจแก่นแท้ของ Linux หรือ Git จริง ๆ ก็ต้องพูดภาษานั้นได้ และเข้าใจนัยละเอียดอ่อนที่มักหายไปในการแปล คล้ายกับการจะเข้าใจความหมายเชิงอัตวิสัยที่แท้จริงของคำว่า “ป่า” ในภาษารัสเซีย ก็ต้องพูดและเข้าใจภาษารัสเซีย
      บริบทเปลี่ยนมุมมอง และบางครั้งก็เปลี่ยนทุกอย่าง
    • เห็นด้วยเต็มที่ และพอได้ใช้จริงก็ยิ่งน่าพอใจ ตอนนี้ผมเริ่มสนใจ โปรโตคอลอีเมล ขึ้นมาแล้วด้วย
      ผมก็แปลกใจเหมือนกันที่ poll() ธรรมดา ๆ รับไหวขนาดนั้น เคยคิดว่าสักวันคงต้องย้ายไป epoll แต่ poll() ก็ทำงานได้ดีมากอยู่
    • คนดูเหมือนจะลืมไปว่า abstraction ที่ยอดเยี่ยมและเท่ทั้งหมด สุดท้ายแก่นของมันก็ทำสิ่งเดียวกันอยู่ดี คือ เปิด socket อ่าน และเขียน
      ไม่มีอะไรใหม่ใต้ดวงอาทิตย์
  • อันนี้ก็น่าสนใจเช่นกัน: https://news.ycombinator.com/item?id=27431910
    ณ ปี 2024 อินสแตนซ์ althttpd ของ sqlite.org จัดการคำขอ HTTP มากกว่า 500,000 ครั้งต่อวัน หรือประมาณ 5–6 ครั้งต่อวินาที และให้บริการคอนเทนต์ราว 200GB ต่อวัน หรือประมาณ 18Mbit/s บน Linode เดือนละ 40 ดอลลาร์ ค่า load average ของเครื่องนี้มักอยู่แถว 0.5 ประมาณ 19% ของคำขอ HTTP เป็น CGI ที่ไปยัง repository ซอร์สโค้ด Fossil หลายตัว

    • ผมได้แรงบันดาลใจอย่างมากจากบทความนี้ มันทำให้ตระหนักว่าเรื่องแบบนี้ เป็นไปได้จริง
  • ถ้าอยากเขียนแอป C แต่ไม่สบายใจที่จะเขียนส่วนที่สัมผัสอินเทอร์เน็ตสาธารณะโดยตรงเอง Kore เป็นเฟรมเวิร์กที่ใช้ได้ดี
    มีฟีเจอร์ในตัวที่สะดวก เช่น การจัดการใบรับรอง ACME, Pgsql, curl, WebSocket
    โดยพื้นฐานคือ build โมดูลแล้วรันและประกอบใช้งานร่วมกันได้ และยังผสม Lua/Python กับ C ได้ด้วย
    https://kore.io/

  • ในที่สุดก็มีเว็บไซต์ที่ขึ้นหน้าแรกแล้วไม่ตาย

    • ถ้ามี CDN อยู่ข้างหน้า เว็บไซต์ไหนก็ทำแบบนั้นได้
      ไม่ได้หมายความว่าโปรเจกต์นี้ไม่ยอดเยี่ยม แต่ถ้าในสภาพแวดล้อม production คุณให้ความสำคัญกับเรื่องนี้จริง ๆ และส่วนใหญ่เสิร์ฟคอนเทนต์แบบ static ก็ใช้ CDN ไปเถอะ มันจะให้ประสิทธิภาพดีกว่าแทบทุกอย่างที่คุณเขียนเองเสมอ แค่ไม่สนุกเท่านั้น
    • ลิงก์ไม่ได้ไปที่ GitHub เหรอ? คอมเมนต์นี้ทำให้ผมงงนิดหน่อย
      โปรเจกต์ดูสะอาดและเจ๋งดี แต่ผมคิดว่าคนส่วนใหญ่คงไปที่ GitHub มากกว่าจะไปลิงก์ที่แสดงหน้าเว็บ ผมพลาดอะไรไปหรือเปล่า?
  • ชอบตรงที่ว่า “ผมสนุกกับการทำเครื่องมือของตัวเอง และก็เบื่อนิดหน่อยกับการได้ยินว่าทุกอย่างต้อง ผ่านการพิสูจน์ในสนามจริง แล้วถ้ามันตายแล้วไงล่ะ? บั๊กก็แก้ได้ :^)”

  • HTTP และ HTTPS server แบบครบถ้วนด้วยโค้ด C แค่ 3.4k บรรทัด เองเหรอ? พูดตรง ๆ ผมนึกว่าถ้าจะให้สอดคล้องกับสเปกอย่างครบถ้วนต้องใช้มากกว่านี้เยอะ

    • HTTP/1.1 นั้นเรียบง่ายมาก ถ้าคุณเมินสเปกส่วนใหญ่
      แค่รับคำขอ GET และตั้ง Content-Length ใน response ก็เพียงพอสำหรับ user agent 99% แล้ว การจัดการ Transfer-Encoding กับ header ช่วงไบต์ก็ไม่ได้ทำให้โค้ดเพิ่มขึ้นมากนัก
      HTTPS ก็เป็นแค่ HTTP บน TLS socket และถ้าไม่ได้ implement การเข้ารหัสเอง ระดับ abstraction ก็ควรจะมีเท่านั้น สนุกดี และจริง ๆ ก็ไม่ได้แย่ขนาดนั้น
    • ขนาดนี้ดูเหมาะสมดี httpd(8) [1] ของ OpenBSD ตอนนี้ก็ยังไม่ถึง 15,000 บรรทัดนิด ๆ รวมเอกสารแล้ว
      ถ้าตัดฟีเจอร์บางอย่างออกและตั้งสมมติฐานไว้บ้าง ก็ไม่น่าแปลกใจถ้าโปรเจกต์นี้จะอยู่ในช่วง 5,000 บรรทัดได้
      ผล $ wc -l * รวมทั้งหมดคือ 14815 บรรทัด
      [1]: https://man.openbsd.org/httpd.8
    • ในการทดลองหนึ่งของผม ผมใช้เว็บเซิร์ฟเวอร์ C แบบ embedded ง่าย ๆ เพื่อให้บริการ live view สำหรับการเก็บข้อมูล ซึ่งมีไม่ถึง 250 บรรทัด
      แน่นอนว่าผมคงไม่เอาไปเปิดบนอินเทอร์เน็ตสาธารณะ และ implement แค่ส่วนเล็กมากของ HTTP/1.1 แต่มันใช้งานได้ และต้องใช้ malloc เฉพาะตอน initialize เท่านั้น
    • ยังมี HTTP/1.1 server ขนาดประมาณนี้อีกหลายตัว: https://www.acme.com/software/thttpd/benchmarks.html
  • ทำให้นึกถึงการบรรยายใน Chaos Communication Congress เกี่ยวกับบล็อก/เว็บเซิร์ฟเวอร์ที่เขียนด้วย C
    เนื้อหาใส่ฟีเจอร์ด้านความปลอดภัยไว้มากมาย เช่น immutable storage, การลดสิทธิ์, การไม่ให้บล็อกเข้าถึงใบรับรอง TLS เป็นต้น: https://www.youtube.com/watch?v=TaE28fJVPTk