ข้ามไปยังเนื้อหา

รีวิวจริงที่ 1: HTTP JSON API

นี่คือรีวิวเต็มบทแรกจากทั้งหมดสี่บท แต่ละบทให้โปรแกรมสไตล์ AI ที่สมบูรณ์ ให้คุณลองหา bug ที่ปลูกไว้ด้วยนิสัยจากสี่บทแรกของโมดูลนี้ แล้วจึงแสดงผลลัพธ์จริงของเครื่องมือจริง พร้อมเวอร์ชันที่แก้แล้วซึ่งรันได้ถูกต้องจริง บทนี้เป็น JSON API เล็ก ๆ สำหรับสร้างและแสดงรายการ “items” — health check, route แสดงรายการ, และ route สร้างรายการ พร้อม bug สี่ตัวจาก AI bug catalog ที่ปลูกไว้ในโค้ดประมาณ 60 บรรทัด

Node.js
Output (verified on Node 24)
port looks fine relative to the minimum
health: {"ok":true}
created: {"id":1,"name":"widget"}

demo นี้ print สามบรรทัด แล้ว crash ก่อนบรรทัดที่สี่ — ไม่มีบรรทัด malformed response: โผล่มาเลย

ผลลัพธ์จริง (stderr ตามหลังสามบรรทัดข้างบน):

Error [ERR_HTTP_HEADERS_SENT]: Cannot write headers after they are sent to the client
at ServerResponse.writeHead (node:_http_server:356:11)
at sendJson (...)
at IncomingMessage.<anonymous> (...)

bug ที่ปลูกไว้สี่ตัว — หนึ่งขอบเขตข้อมูล, หนึ่งอายุของทรัพยากร, หนึ่ง listener ที่หายไป, หนึ่ง type ลองเดาทั้งสี่ตัวก่อนเปิดคำตอบด้านล่าง

Bug 1 — body += chunk; ที่ไม่มีการเช็กขนาด

อาการ: handler ของ POST /items สะสม request body ทั้งหมดลงใน string เดียวโดยไม่มีขีดจำกัดบนเลย

หลักฐาน: ไม่มีอะไรใน demo run นี้ที่ทดสอบ body ขนาดใหญ่จริง ๆ ซึ่งนั่นแหละคืออันตราย — bug นี้มองไม่เห็นในทุก request ขนาดปกติ และจะเสียค่า memory ก็ต่อเมื่อ client (หรือผู้โจมตี) ส่งข้อมูลระดับกิกะไบต์

วิธีแก้: ติดตามจำนวน byte ที่สะสมไว้แล้วปฏิเสธ (413 แล้ว req.destroy()) เมื่อเกินขีดจำกัด MAX_BODY แบบเดียวกับ route ในบท JSON API

Bug 2 — sendJson(res, 500, ...) ต่อจาก sendJson(res, 400, ...) ทันที

อาการ: ใน request ที่มี JSON ผิดรูปแบบ catch block เรียก sendJson สองครั้ง — และ sendJson เรียก res.writeHead() แล้ว res.end() เสมอ

หลักฐาน: crash จริงข้างบน — ERR_HTTP_HEADERS_SENT ถูก throw จากการเรียก sendJson ครั้งที่สองที่ res.writeHead() เพราะ header ถูกส่งไปแล้วโดยการเรียกครั้งแรกก่อนหน้านั้นไม่กี่ขณะ

วิธีแก้: helper สำหรับส่ง response ทุกตัวต้องมี guard ป้องกันการทำงานซ้ำสองครั้ง: if (res.writableEnded) return; ที่ต้นฟังก์ชัน sendJson และ return ทันทีหลังการเรียก sendJson ครั้งแรกใน catch block (ซึ่งไฟล์นี้มีอยู่แล้ว — การเรียกครั้งที่สองเป็นแค่โค้ดที่ตายแล้วซึ่งไม่ควรถูกเขียนขึ้นมาตั้งแต่แรก)

Bug 3 — ไม่มี listener error เลย

อาการ: ทั้ง server และ res แต่ละตัวไม่มี handler .on('error', ...) เลย

หลักฐาน: นี่คือเหตุผลที่แท้จริงว่าทำไม bug 2 ถึง crash ทั้งโปรเซสแทนที่จะแค่ล้มเหลวหนึ่ง request — ERR_HTTP_HEADERS_SENT ถูกส่งเป็น event 'error' บน response stream และเมื่อไม่มี listener เลย Node ก็ถือว่ามันร้ายแรง

วิธีแก้: res.on('error', function (err) { console.error(...); }) ข้างใน request handler และ server.on('error', function (err) { console.error(...); }) ที่ระดับบนสุด เพื่อให้ response ที่พังลดระดับลงเหลือแค่ error ที่ log ไว้ แทนที่จะดึงทุก request ที่กำลังทำงานอยู่ล้มไปด้วย

Bug 4 — PORT > process.env.MIN_ALLOWED_PORT

อาการ: ข้อความที่ print กลับด้าน: '9000' > '10000' ถูกเทียบเป็น string (ทั้งสองฝั่งเป็น string) จึงเทียบแบบ lexicographic — '9' > '1' เป็น true — โค้ดจึง print “port looks fine” สำหรับพอร์ตที่จริง ๆ แล้วต่ำกว่าค่าต่ำสุดที่กำหนดไว้

หลักฐาน: บรรทัดแรกของผลลัพธ์จริงข้างบน — port looks fine relative to the minimum — เป็น branch ที่ผิด; 9000 < 10000 ควร print คำเตือนแทน

วิธีแก้: Number(process.env.PORT) > Number(process.env.MIN_ALLOWED_PORT)

Node.js
Output (verified on Node 24)
warning: port is below the configured minimum
health: {"ok":true}
created: {"id":1,"name":"widget"}
malformed response: {"error":"malformed JSON"}

ทั้งสี่ request เสร็จสมบูรณ์แล้ว: คำเตือนเรื่องพอร์ตถูกต้อง health check และการสร้างรายการที่ถูกต้องทั้งคู่ทำงานได้ และ request ที่ผิดรูปแบบได้ 400 ที่สะอาดแทนที่จะทำให้โปรเซส crash

npx eslint (flat config พร้อม eslint-plugin-n + eslint-plugin-promise) บนเวอร์ชันที่มี bug: ไม่มีปัญหาเลย ไม่มี bug ทั้งสี่ตัวที่ตรงกับกฎไหนเลย — ไม่มีการจำกัด body, การส่ง response ซ้ำสองครั้ง, ไม่มี error listener, และการเทียบ string กับ string ล้วนอยู่นอกเหนือสิ่งที่กฎที่ติดตั้งในคอร์สนี้ตรวจสอบ

node --trace-warnings บนเวอร์ชันที่มี bug: ไม่มี warning เลย — ไฟล์นี้ crash ด้วย uncaught exception ไม่ใช่ warning ดังนั้น --trace-warnings จึงไม่มีอะไรเพิ่มเติมให้ที่นี่ (flag นั้นมีไว้สำหรับ bug 12 ในแคตตาล็อก คือ listener leak ไม่ใช่ตัวนี้)

การรันไฟล์คือสิ่งที่จับ bug ได้จริงสามใน สี่ตัว (2, 3, และ 4 ทั้งหมดเห็นได้ในผลลัพธ์จริงข้างบน); bug 1 (ไม่จำกัด body) ไม่มีอาการที่เห็นได้เลยใน run นี้ — ต้องใช้ request ที่ขนาดใหญ่เกิน ไม่ใช่แค่ผิดรูปแบบ ถึงจะสังเกตเห็นมันได้ เหมือนกับ pattern 9 ใน AI bug catalog เป๊ะ

เวอร์ชันที่มี bug crash ด้วย ERR_HTTP_HEADERS_SENT ไม่ใช่ ERR_STREAM_WRITE_AFTER_END แบบ pattern ที่คล้ายกันใน AI bug catalog ทำไม error ถึงต่างกัน?
ทำไม bug 3 (ไม่มี error listener) ถึงสำคัญ ทั้งที่ไฟล์นี้ไม่เคยจงใจ trigger event "error" เลย?
บรรทัดแรกของผลลัพธ์จริง "port looks fine relative to the minimum" พิสูจน์อะไรจริง ๆ เกี่ยวกับ bug 4?
ทำไม eslint ถึงรายงานว่าไม่มีปัญหาเลยบนไฟล์ที่มี bug จริงที่ปลูกไว้สี่ตัว?