เรียนรู้การเขียนโปรแกรม Python CGI อย่างละเอียด คู่มือนี้ครอบคลุมการตั้งค่า, การจัดการฟอร์ม, การจัดการสถานะ, ความปลอดภัย และตำแหน่งของ CGI ในเว็บยุคใหม่
การเขียนโปรแกรม Python CGI: คู่มือฉบับสมบูรณ์สำหรับการสร้างส่วนต่อประสานเว็บ
ในโลกของการพัฒนาเว็บสมัยใหม่ที่ถูกครอบงำด้วยเฟรมเวิร์กที่ซับซ้อนอย่าง Django, Flask และ FastAPI คำว่า CGI (Common Gateway Interface) อาจฟังดูเหมือนเสียงสะท้อนจากอดีตที่ล่วงเลยไป อย่างไรก็ตาม การมองข้าม CGI ก็คือการมองข้ามเทคโนโลยีพื้นฐานที่ไม่เพียงแต่ขับเคลื่อนเว็บแบบไดนามิกในยุคแรกเริ่ม แต่ยังคงให้บทเรียนอันทรงคุณค่าและการประยุกต์ใช้ได้จริงในปัจจุบัน การทำความเข้าใจ CGI ก็เหมือนกับการทำความเข้าใจว่าเครื่องยนต์ทำงานอย่างไรก่อนที่คุณจะหัดขับรถ มันให้ความรู้ที่ลึกซึ้งและเป็นรากฐานของการโต้ตอบระหว่างไคลเอ็นต์-เซิร์ฟเวอร์ ซึ่งเป็นพื้นฐานของแอปพลิเคชันเว็บทั้งหมด
คู่มือฉบับสมบูรณ์นี้จะไขความกระจ่างเกี่ยวกับการเขียนโปรแกรม Python CGI เราจะสำรวจตั้งแต่หลักการพื้นฐาน แสดงให้คุณเห็นถึงวิธีการสร้างส่วนต่อประสานเว็บแบบไดนามิกและโต้ตอบได้ โดยใช้เพียงไลบรารีมาตรฐานของ Python ไม่ว่าคุณจะเป็นนักเรียนที่กำลังเรียนรู้พื้นฐานของเว็บ นักพัฒนาที่ทำงานกับระบบเก่า หรือใครบางคนที่ทำงานในสภาพแวดล้อมที่มีข้อจำกัด คู่มือนี้จะช่วยให้คุณมีทักษะในการใช้ประโยชน์จากเทคโนโลยีที่ทรงพลังและตรงไปตรงมานี้
CGI คืออะไรและทำไมจึงยังคงมีความสำคัญ?
Common Gateway Interface (CGI) คือโปรโตคอลมาตรฐานที่กำหนดว่าเว็บเซิร์ฟเวอร์จะโต้ตอบกับโปรแกรมภายนอกได้อย่างไร ซึ่งมักเรียกว่าสคริปต์ CGI เมื่อไคลเอ็นต์ (เช่น เว็บเบราว์เซอร์) ร้องขอ URL เฉพาะที่เกี่ยวข้องกับสคริปต์ CGI เว็บเซิร์ฟเวอร์จะไม่เพียงแค่ให้บริการไฟล์คงที่ แต่จะดำเนินการสคริปต์และส่งผลลัพธ์ของสคริปต์กลับไปยังไคลเอ็นต์ ซึ่งช่วยให้สามารถสร้างเนื้อหาแบบไดนามิกตามข้อมูลที่ผู้ใช้ป้อน การสืบค้นฐานข้อมูล หรือตรรกะอื่นใดที่สคริปต์มีอยู่
ลองนึกภาพว่าเป็นการสนทนา:
- ไคลเอ็นต์ถึงเซิร์ฟเวอร์: "ฉันต้องการดูทรัพยากรที่ `/cgi-bin/process-form.py` และนี่คือข้อมูลบางส่วนจากฟอร์มที่ฉันกรอก"
- เซิร์ฟเวอร์ถึงสคริปต์ CGI: "มีการร้องขอมาถึงคุณแล้ว นี่คือข้อมูลของไคลเอ็นต์และข้อมูลเกี่ยวกับการร้องขอ (เช่น ที่อยู่ IP เบราว์เซอร์ ฯลฯ) โปรดรันและให้การตอบกลับเพื่อส่งกลับไป"
- สคริปต์ CGI ถึงเซิร์ฟเวอร์: "ฉันได้ประมวลผลข้อมูลแล้ว นี่คือส่วนหัว HTTP และเนื้อหา HTML ที่จะส่งกลับ"
- เซิร์ฟเวอร์ถึงไคลเอ็นต์: "นี่คือหน้าเว็บแบบไดนามิกที่คุณร้องขอ"
ในขณะที่เฟรมเวิร์กสมัยใหม่ได้สรุปการโต้ตอบดิบนี้ไปแล้ว หลักการพื้นฐานยังคงเหมือนเดิม ดังนั้น ทำไมต้องเรียนรู้ CGI ในยุคของเฟรมเวิร์กระดับสูง?
- ความเข้าใจพื้นฐาน: มันบังคับให้คุณเรียนรู้กลไกหลักของคำขอและตอบกลับ HTTP รวมถึงส่วนหัว ตัวแปรสภาพแวดล้อม และสตรีมข้อมูล โดยไม่มีความซับซ้อนใดๆ ความรู้นี้มีค่าอย่างยิ่งสำหรับการแก้ไขข้อบกพร่องและการปรับแต่งประสิทธิภาพของแอปพลิเคชันเว็บใดๆ
- ความเรียบง่าย: สำหรับงานเดียวที่แยกออกมา การเขียนสคริปต์ CGI ขนาดเล็กสามารถทำได้เร็วกว่าและง่ายกว่าการตั้งค่าโปรเจกต์เฟรมเวิร์กทั้งหมดที่มีการกำหนดเส้นทาง, โมเดล และคอนโทรลเลอร์
- ไม่ขึ้นกับภาษา: CGI เป็นโปรโตคอล ไม่ใช่ไลบรารี คุณสามารถเขียนสคริปต์ CGI ได้ใน Python, Perl, C++, Rust หรือภาษาใดๆ ที่สามารถอ่านจากอินพุตมาตรฐานและเขียนไปยังเอาต์พุตมาตรฐานได้
- ระบบเก่าและสภาพแวดล้อมที่มีข้อจำกัด: แอปพลิเคชันเว็บเก่าจำนวนมากและสภาพแวดล้อมโฮสติ้งแบบใช้ร่วมบางแห่งยังคงพึ่งพาหรือรองรับเพียง CGI การรู้ว่าต้องทำงานกับมันอย่างไรจึงเป็นทักษะที่สำคัญ นอกจากนี้ยังพบบ่อยในระบบฝังตัวที่มีเว็บเซิร์ฟเวอร์แบบง่าย
การตั้งค่าสภาพแวดล้อม CGI ของคุณ
ก่อนที่คุณจะรันสคริปต์ Python CGI ได้ คุณต้องมีเว็บเซิร์ฟเวอร์ที่กำหนดค่าให้ดำเนินการสคริปต์นั้น นี่เป็นอุปสรรคที่พบบ่อยที่สุดสำหรับผู้เริ่มต้น สำหรับการพัฒนาและการเรียนรู้ คุณสามารถใช้เซิร์ฟเวอร์ยอดนิยมอย่าง Apache หรือแม้แต่เซิร์ฟเวอร์ในตัวของ Python
ข้อกำหนดเบื้องต้น: เว็บเซิร์ฟเวอร์
หัวใจสำคัญคือการบอกเว็บเซิร์ฟเวอร์ของคุณว่าไฟล์ในไดเรกทอรีเฉพาะ (ตามธรรมเนียมชื่อ `cgi-bin`) ไม่ได้มีไว้สำหรับการให้บริการเป็นข้อความ แต่ควรถูกดำเนินการ และส่งผลลัพธ์ไปยังเบราว์เซอร์ แม้ว่าขั้นตอนการกำหนดค่าเฉพาะจะแตกต่างกันไป แต่หลักการทั่วไปนั้นเป็นสากล
- Apache: โดยทั่วไป คุณต้องเปิดใช้งาน `mod_cgi` และใช้คำสั่ง `ScriptAlias` ในไฟล์กำหนดค่าของคุณเพื่อจับคู่พาธ URL กับไดเรกทอรีในระบบไฟล์ นอกจากนี้ คุณยังต้องการคำสั่ง `Options +ExecCGI` สำหรับไดเรกทอรีนั้นเพื่ออนุญาตการดำเนินการ
- Nginx: Nginx ไม่มีโมดูล CGI โดยตรงเหมือน Apache โดยทั่วไปจะใช้บริดจ์อย่าง FCGIWrap เพื่อดำเนินการสคริปต์ CGI
- `http.server` ของ Python: สำหรับการทดสอบในเครื่องอย่างง่าย คุณสามารถใช้เว็บเซิร์ฟเวอร์ในตัวของ Python ซึ่งรองรับ CGI ได้ทันที คุณสามารถเริ่มได้จากบรรทัดคำสั่งของคุณด้วย: `python3 -m http.server --cgi 8000` สิ่งนี้จะเริ่มเซิร์ฟเวอร์บนพอร์ต 8000 และถือว่าสคริปต์ใดๆ ในซับไดเรกทอรี `cgi-bin/` เป็นไฟล์ที่รันได้
"Hello, World!" แรกของคุณใน Python CGI
สคริปต์ CGI มีรูปแบบเอาต์พุตที่เฉพาะเจาะจง จะต้องพิมพ์ส่วนหัว HTTP ที่จำเป็นทั้งหมดก่อน ตามด้วยบรรทัดว่างเดียว จากนั้นจึงเป็นเนื้อหา (เช่น HTML)
มาสร้างสคริปต์แรกของเรากัน บันทึกโค้ดต่อไปนี้เป็น `hello.py` ภายในไดเรกทอรี `cgi-bin` ของคุณ
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 1. The HTTP Header
# The most important header is Content-Type, which tells the browser what kind of data to expect.
print("Content-Type: text/html;charset=utf-8")
# 2. The Blank Line
# A single blank line is crucial. It separates the headers from the content body.
print()
# 3. The Content Body
# This is the actual HTML content that will be displayed in the browser.
print("<h1>Hello, World!</h1>")
print("<p>This is my first Python CGI script.</p>")
print("<p>It's running on a global web server, accessible to anyone!</p>")
มาทำความเข้าใจกัน:
#!/usr/bin/env python3
: นี่คือบรรทัด "shebang" บนระบบที่คล้าย Unix (Linux, macOS) จะบอกระบบปฏิบัติการให้ดำเนินการไฟล์นี้โดยใช้ Python 3 interpreterprint("Content-Type: text/html;charset=utf-8")
: นี่คือส่วนหัว HTTP มันแจ้งเบราว์เซอร์ว่าเนื้อหาต่อไปนี้เป็น HTML และเข้ารหัสแบบ UTF-8 ซึ่งจำเป็นสำหรับการรองรับอักขระสากลprint()
: สิ่งนี้จะพิมพ์บรรทัดว่างที่บังคับซึ่งแยกส่วนหัวออกจากเนื้อหา การลืมสิ่งนี้เป็นข้อผิดพลาดที่พบบ่อยมาก- คำสั่ง `print` สุดท้ายจะสร้าง HTML ที่ผู้ใช้จะเห็น
สุดท้าย คุณต้องทำให้สคริปต์สามารถดำเนินการได้ บน Linux หรือ macOS คุณจะรันคำสั่งนี้ในเทอร์มินัลของคุณ: `chmod +x cgi-bin/hello.py` ตอนนี้ เมื่อคุณไปที่ `http://your-server-address/cgi-bin/hello.py` ในเบราว์เซอร์ของคุณ คุณจะเห็นข้อความ "Hello, World!" ของคุณ
หัวใจของ CGI: ตัวแปรสภาพแวดล้อม
เว็บเซิร์ฟเวอร์สื่อสารข้อมูลเกี่ยวกับการร้องขอไปยังสคริปต์ของเราได้อย่างไร? มันใช้ ตัวแปรสภาพแวดล้อม ตัวแปรเหล่านี้ถูกกำหนดโดยเซิร์ฟเวอร์ในสภาพแวดล้อมการดำเนินการของสคริปต์ ให้ข้อมูลมากมายเกี่ยวกับการร้องขอที่เข้ามาและตัวเซิร์ฟเวอร์เอง นี่คือ "Gateway" ใน Common Gateway Interface
ตัวแปรสภาพแวดล้อม CGI ที่สำคัญ
โมดูล `os` ของ Python ช่วยให้เราเข้าถึงตัวแปรเหล่านี้ได้ นี่คือบางส่วนที่สำคัญที่สุด:
REQUEST_METHOD
: เมธอด HTTP ที่ใช้สำหรับคำขอ (เช่น 'GET', 'POST')QUERY_STRING
: มีข้อมูลที่ส่งหลังจาก '?' ใน URL นี่คือวิธีที่ข้อมูลถูกส่งผ่านในคำขอ GETCONTENT_LENGTH
: ความยาวของข้อมูลที่ส่งในเนื้อหาคำขอ ใช้สำหรับคำขอ POSTCONTENT_TYPE
: ประเภท MIME ของข้อมูลในเนื้อหาคำขอ (เช่น 'application/x-www-form-urlencoded')REMOTE_ADDR
: ที่อยู่ IP ของไคลเอ็นต์ที่ทำการร้องขอHTTP_USER_AGENT
: สตริง user-agent ของเบราว์เซอร์ของไคลเอ็นต์ (เช่น 'Mozilla/5.0...')SERVER_NAME
: ชื่อโฮสต์หรือที่อยู่ IP ของเซิร์ฟเวอร์SERVER_PROTOCOL
: โปรโตคอลที่ใช้ เช่น 'HTTP/1.1'SCRIPT_NAME
: พาธไปยังสคริปต์ที่กำลังดำเนินการอยู่
ตัวอย่างเชิงปฏิบัติ: สคริปต์วินิจฉัย
มาสร้างสคริปต์ที่แสดงตัวแปรสภาพแวดล้อมที่มีอยู่ทั้งหมด นี่เป็นเครื่องมือที่มีประโยชน์อย่างยิ่งสำหรับการแก้ไขข้อบกพร่อง บันทึกสิ่งนี้เป็น `diagnostics.py` ในไดเรกทอรี `cgi-bin` ของคุณและทำให้มันสามารถดำเนินการได้
#!/usr/bin/env python3
import os
print("Content-Type: text/html\n")
print("<h1>CGI Environment Variables</h1>")
print("<p>This script displays all environment variables passed by the web server.</p>")
print("<table border='1' style='border-collapse: collapse; width: 80%;'>")
print("<tr><th>Variable</th><th>Value</th></tr>")
# Iterate through all environment variables and print them in a table
for key, value in sorted(os.environ.items()):
print(f"<tr><td>{key}</td><td>{value}</td></tr>")
print("</table>")
เมื่อคุณรันสคริปต์นี้ คุณจะเห็นตารางโดยละเอียดที่แสดงข้อมูลทุกส่วนที่เซิร์ฟเวอร์ส่งไปยังสคริปต์ของคุณ ลองเพิ่มสตริงคำค้นหาไปยัง URL (เช่น `.../diagnostics.py?name=test&value=123`) และสังเกตว่าตัวแปร `QUERY_STRING` เปลี่ยนแปลงไปอย่างไร
การจัดการอินพุตผู้ใช้: ฟอร์มและข้อมูล
วัตถุประสงค์หลักของ CGI คือการประมวลผลอินพุตของผู้ใช้ ซึ่งโดยทั่วไปมาจากฟอร์ม HTML ไลบรารีมาตรฐานของ Python มีเครื่องมือที่แข็งแกร่งสำหรับสิ่งนี้ มาสำรวจวิธีการจัดการเมธอด HTTP หลักสองวิธี: GET และ POST
ก่อนอื่น มาสร้างฟอร์ม HTML อย่างง่าย บันทึกไฟล์นี้เป็น `feedback_form.html` ในไดเรกทอรีเว็บหลักของคุณ (ไม่ใช่ในไดเรกทอรี cgi-bin)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Global Feedback Form</title>
</head>
<body>
<h1>Submit Your Feedback</h1>
<p>This form demonstrates both GET and POST methods.</p>
<h2>GET Method Example</h2>
<form action="/cgi-bin/form_handler.py" method="GET">
<label for="get_name">Your Name:</label>
<input type="text" id="get_name" name="username">
<br/><br/>
<label for="get_topic">Topic:</label>
<input type="text" id="get_topic" name="topic">
<br/><br/>
<input type="submit" value="Submit with GET">
</form>
<hr>
<h2>POST Method Example (More Features)</h2>
<form action="/cgi-bin/form_handler.py" method="POST">
<label for="post_name">Your Name:</label>
<input type="text" id="post_name" name="username">
<br/><br/>
<label for="email">Your Email:</label>
<input type="email" id="email" name="email">
<br/><br/>
<p>Are you happy with our service?</p>
<input type="radio" id="happy_yes" name="satisfaction" value="yes">
<label for="happy_yes">Yes</label><br>
<input type="radio" id="happy_no" name="satisfaction" value="no">
<label for="happy_no">No</label><br>
<input type="radio" id="happy_neutral" name="satisfaction" value="neutral">
<label for="happy_neutral">Neutral</label>
<br/><br/>
<p>Which products are you interested in?</p>
<input type="checkbox" id="prod_a" name="products" value="Product A">
<label for="prod_a">Product A</label><br>
<input type="checkbox" id="prod_b" name="products" value="Product B">
<label for="prod_b">Product B</label><br>
<input type="checkbox" id="prod_c" name="products" value="Product C">
<label for="prod_c">Product C</label>
<br/><br/>
<label for="comments">Comments:</label><br>
<textarea id="comments" name="comments" rows="4" cols="50"></textarea>
<br/><br/>
<input type="submit" value="Submit with POST">
</form>
</body>
</html>
ฟอร์มนี้ส่งข้อมูลไปยังสคริปต์ชื่อ `form_handler.py` ตอนนี้ เราต้องเขียนสคริปต์นั้น แม้ว่าคุณจะสามารถแยกวิเคราะห์ `QUERY_STRING` สำหรับคำขอ GET และอ่านจากอินพุตมาตรฐานสำหรับคำขอ POST ด้วยตนเองได้ แต่วิธีนี้มีแนวโน้มที่จะเกิดข้อผิดพลาดและซับซ้อน แทนที่จะเป็นเช่นนั้น เราควรใช้โมดูล `cgi` ที่มาพร้อมกับ Python ซึ่งออกแบบมาเพื่อวัตถุประสงค์นี้โดยเฉพาะ
คลาส `cgi.FieldStorage` คือฮีโร่ในที่นี้ มันแยกวิเคราะห์คำขอที่เข้ามาและให้ส่วนต่อประสานที่เหมือนพจนานุกรมแก่ข้อมูลฟอร์ม โดยไม่คำนึงว่าจะถูกส่งผ่าน GET หรือ POST
นี่คือโค้ดสำหรับ `form_handler.py` บันทึกในไดเรกทอรี `cgi-bin` ของคุณและทำให้มันสามารถดำเนินการได้
#!/usr/bin/env python3
import cgi
import html
# Create an instance of FieldStorage
# This one object handles both GET and POST requests transparently
form = cgi.FieldStorage()
# Start printing the response
print("Content-Type: text/html\n")
print("<h1>Form Submission Received</h1>")
print("<p>Thank you for your feedback. Here is the data we received:</p>")
# Check if any form data was submitted
if not form:
print("<p><em>No form data was submitted.</em></p>")
else:
print("<table border='1' style='border-collapse: collapse;'>")
print("<tr><th>Field Name</th><th>Value(s)</th></tr>")
# Iterate through all the keys in the form data
for key in form.keys():
# IMPORTANT: Sanitize user input before displaying it to prevent XSS attacks.
# html.escape() converts characters like <, >, & to their HTML entities.
sanitized_key = html.escape(key)
# The .getlist() method is used to handle fields that can have multiple values,
# such as checkboxes. It always returns a list.
values = form.getlist(key)
# Sanitize each value in the list
sanitized_values = [html.escape(v) for v in values]
# Join the list of values into a comma-separated string for display
display_value = ", ".join(sanitized_values)
print(f"<tr><td><strong>{sanitized_key}</strong></td><td>{display_value}</td></tr>")
print("</table>")
# Example of accessing a single value directly
# Use form.getvalue('key') for fields you expect to have only one value.
# It returns None if the key doesn't exist.
username = form.getvalue("username")
if username:
print(f"<h2>Welcome, {html.escape(username)}!</h2>")
ประเด็นสำคัญจากสคริปต์นี้:
- `import cgi` และ `import html`: เรานำเข้าโมดูลที่จำเป็น `cgi` สำหรับการแยกวิเคราะห์ฟอร์ม และ `html` สำหรับความปลอดภัย
- `form = cgi.FieldStorage()`: บรรทัดเดียวนี้ทำงานหนักทั้งหมด มันตรวจสอบตัวแปรสภาพแวดล้อม (`REQUEST_METHOD`, `CONTENT_LENGTH` ฯลฯ) อ่านสตรีมอินพุตที่เหมาะสม และแยกวิเคราะห์ข้อมูลเป็นอ็อบเจกต์ที่ใช้งานง่าย
- ความปลอดภัยเป็นอันดับแรก (`html.escape`): เรา ไม่เคย พิมพ์ข้อมูลที่ผู้ใช้ส่งโดยตรงลงใน HTML ของเรา การทำเช่นนั้นจะสร้างช่องโหว่ Cross-Site Scripting (XSS) ฟังก์ชัน `html.escape()` ใช้เพื่อทำให้ HTML หรือ JavaScript ที่เป็นอันตรายที่ผู้โจมตีอาจส่งมาเป็นกลาง
- `form.keys()`: เราสามารถวนซ้ำผ่านชื่อฟิลด์ทั้งหมดที่ส่งมาได้
- `form.getlist(key)`: นี่คือวิธีที่ปลอดภัยที่สุดในการดึงค่า เนื่องจากฟอร์มสามารถส่งหลายค่าสำหรับชื่อเดียวกันได้ (เช่น ช่องทำเครื่องหมาย) `getlist()` จึงส่งคืนรายการเสมอ หากฟิลด์มีเพียงค่าเดียว มันจะเป็นรายการที่มีหนึ่งรายการ
- `form.getvalue(key)`: นี่เป็นทางลัดที่สะดวกเมื่อคุณคาดหวังเพียงค่าเดียว มันจะส่งคืนค่าเดียวโดยตรง หรือหากมีหลายค่า ก็จะส่งคืนรายการของค่าเหล่านั้น มันจะส่งคืน `None` หากไม่พบคีย์
ตอนนี้ เปิด `feedback_form.html` ในเบราว์เซอร์ของคุณ กรอกทั้งสองฟอร์ม และดูว่าสคริปต์จัดการข้อมูลแตกต่างกันแต่มีประสิทธิภาพอย่างไรในแต่ละครั้ง
เทคนิค CGI ขั้นสูงและแนวปฏิบัติที่ดีที่สุด
การจัดการสถานะ: คุกกี้
HTTP เป็นโปรโตคอลไร้สถานะ การร้องขอแต่ละครั้งเป็นอิสระ และเซิร์ฟเวอร์ไม่มีหน่วยความจำของการร้องขอก่อนหน้าจากไคลเอ็นต์รายเดียวกัน หากต้องการสร้างประสบการณ์ที่คงอยู่ (เช่น ตะกร้าสินค้าหรือเซสชันที่เข้าสู่ระบบ) เราจำเป็นต้องจัดการสถานะ วิธีที่พบบ่อยที่สุดในการทำเช่นนี้คือการใช้คุกกี้
คุกกี้คือข้อมูลขนาดเล็กที่เซิร์ฟเวอร์ส่งไปยังเบราว์เซอร์ของไคลเอ็นต์ จากนั้นเบราว์เซอร์จะส่งคุกกี้นั้นกลับมาพร้อมกับการร้องขอที่ตามมาทุกครั้งไปยังเซิร์ฟเวอร์เดียวกัน สคริปต์ CGI สามารถตั้งค่าคุกกี้ได้โดยการพิมพ์ส่วนหัว `Set-Cookie` และสามารถอ่านคุกกี้ที่เข้ามาจากตัวแปรสภาพแวดล้อม `HTTP_COOKIE`
มาสร้างสคริปต์นับผู้เยี่ยมชมอย่างง่าย บันทึกสิ่งนี้เป็น `cookie_counter.py`
#!/usr/bin/env python3
import os
import http.cookies
# Load existing cookies from the environment variable
cookie = http.cookies.SimpleCookie(os.environ.get("HTTP_COOKIE"))
visit_count = 0
# Try to get the value of our 'visit_count' cookie
if 'visit_count' in cookie:
try:
# The cookie value is a string, so we must convert it to an integer
visit_count = int(cookie['visit_count'].value)
except ValueError:
# Handle cases where the cookie value is not a valid number
visit_count = 0
# Increment the visit count
visit_count += 1
# Set the cookie for the response. This will be sent as a 'Set-Cookie' header.
# We are setting the new value for 'visit_count'.
cookie['visit_count'] = visit_count
# You can also set cookie attributes like expiration date, path, etc.
# cookie['visit_count']['expires'] = '...'
# cookie['visit_count']['path'] = '/'
# Print the Set-Cookie header first
print(cookie.output())
# Then print the regular Content-Type header
print("Content-Type: text/html\n")
# And finally the HTML body
print("<h1>Cookie-based Visitor Counter</h1>")
print(f"<p>Welcome! This is your visit number: <strong>{visit_count}</strong>.</p>")
print("<p>Refresh this page to see the count increase.</p>")
print("<p><em>(Your browser must have cookies enabled for this to work.)</em></p>")
ในที่นี้ โมดูล `http.cookies` ของ Python ช่วยให้การแยกวิเคราะห์สตริง `HTTP_COOKIE` และการสร้างส่วนหัว `Set-Cookie` ง่ายขึ้น ทุกครั้งที่คุณเยี่ยมชมหน้านี้ สคริปต์จะอ่านจำนวนครั้งเก่า เพิ่มขึ้น และส่งค่าใหม่กลับไปเพื่อจัดเก็บในเบราว์เซอร์ของคุณ
การแก้ไขข้อบกพร่องสคริปต์ CGI: โมดูล `cgitb`
เมื่อสคริปต์ CGI ล้มเหลว เซิร์ฟเวอร์มักจะส่งข้อความ "500 Internal Server Error" ทั่วไปกลับมา ซึ่งไม่เป็นประโยชน์ต่อการแก้ไขข้อบกพร่อง โมดูล `cgitb` (CGI Traceback) ของ Python เป็นตัวช่วยชีวิต การเปิดใช้งานโมดูลนี้ที่ด้านบนของสคริปต์ของคุณ การยกเว้นที่ไม่ได้รับการจัดการใดๆ จะสร้างรายงานที่มีรายละเอียดและจัดรูปแบบโดยตรงในเบราว์เซอร์
หากต้องการใช้ ให้เพิ่มสองบรรทัดนี้ที่จุดเริ่มต้นของสคริปต์ของคุณ:
import cgitb
cgitb.enable()
คำเตือน: แม้ว่า `cgitb` จะมีค่าสำหรับการพัฒนา แต่คุณควรปิดใช้งานหรือกำหนดค่าให้บันทึกลงในไฟล์ในสภาพแวดล้อมการผลิต การเปิดเผยการติดตามข้อผิดพลาดโดยละเอียดต่อสาธารณะอาจเปิดเผยข้อมูลที่ละเอียดอ่อนเกี่ยวกับการกำหนดค่าและโค้ดของเซิร์ฟเวอร์ของคุณ
การอัปโหลดไฟล์ด้วย CGI
อ็อบเจกต์ `cgi.FieldStorage` ยังจัดการการอัปโหลดไฟล์ได้อย่างราบรื่น ฟอร์ม HTML จะต้องกำหนดค่าด้วย `method="POST"` และที่สำคัญคือ `enctype="multipart/form-data"`
มาสร้างฟอร์มอัปโหลดไฟล์ `upload.html`:
<!DOCTYPE html>
<html lang="en">
<head>
<title>File Upload</title>
</head>
<body>
<h1>Upload a File</h1>
<form action="/cgi-bin/upload_handler.py" method="POST" enctype="multipart/form-data">
<label for="userfile">Select a file to upload:</label>
<input type="file" id="userfile" name="userfile">
<br/><br/>
<input type="submit" value="Upload File">
</form>
</body>
</html>
และตอนนี้ตัวจัดการ `upload_handler.py` หมายเหตุ: สคริปต์นี้ต้องมีไดเรกทอรีชื่อ `uploads` ในตำแหน่งเดียวกับสคริปต์ และเว็บเซิร์ฟเวอร์ต้องมีสิทธิ์ในการเขียนลงในนั้น
#!/usr/bin/env python3
import cgi
import os
import html
# Enable detailed error reporting for debugging
import cgitb
cgitb.enable()
print("Content-Type: text/html\n")
print("<h1>File Upload Handler</h1>")
# Directory where files will be saved. SECURITY: This should be a secure, non-web-accessible directory.
upload_dir = './uploads/'
# Create the directory if it doesn't exist
if not os.path.exists(upload_dir):
os.makedirs(upload_dir, exist_ok=True)
# IMPORTANT: Set correct permissions. In a real scenario, this would be more restrictive.
# os.chmod(upload_dir, 0o755)
form = cgi.FieldStorage()
# Get the file item from the form. 'userfile' is the 'name' of the input field.
file_item = form['userfile']
# Check if a file was actually uploaded
if file_item.filename:
# SECURITY: Never trust the filename provided by the user.
# It could contain path characters like '../' (directory traversal attack).
# We use os.path.basename to strip any directory information.
fn = os.path.basename(file_item.filename)
# Create the full path to save the file
file_path = os.path.join(upload_dir, fn)
try:
# Open the file in write-binary mode and write the uploaded data
with open(file_path, 'wb') as f:
f.write(file_item.file.read())
message = f"The file '{html.escape(fn)}' was uploaded successfully!"
print(f"<p style='color: green;'>{message}</p>")
except IOError as e:
message = f"Error saving file: {e}. Check server permissions for the '{upload_dir}' directory."
print(f"<p style='color: red;'>{message}</p>")
else:
message = 'No file was uploaded.'
print(f"<p style='color: orange;'>{message}</p>")
print("<a href='/upload.html'>Upload another file</a>")
ความปลอดภัย: ข้อกังวลสูงสุด
เนื่องจากสคริปต์ CGI เป็นโปรแกรมที่สามารถดำเนินการได้ซึ่งถูกเปิดเผยต่ออินเทอร์เน็ตโดยตรง ความปลอดภัยจึงไม่ใช่ทางเลือก แต่เป็นข้อกำหนด ความผิดพลาดเพียงครั้งเดียวอาจนำไปสู่การถูกบุกรุกเซิร์ฟเวอร์ได้
การตรวจสอบความถูกต้องและการทำให้บริสุทธิ์ของอินพุต (การป้องกัน XSS)
ดังที่เราได้เห็นไปแล้ว คุณต้อง ไม่เคย ไว้วางใจอินพุตของผู้ใช้ ควรสมมติเสมอว่ามันเป็นอันตราย เมื่อแสดงข้อมูลที่ผู้ใช้ให้มากลับในหน้า HTML ให้หลีกเลี่ยงเสมอด้วย `html.escape()` เพื่อป้องกันการโจมตี Cross-Site Scripting (XSS) ไม่เช่นนั้นผู้โจมตีอาจแทรกแท็ก `