本文 AI 產出,尚未審核

Python 課程 – 網路與 API(Networking & APIs)

主題:HTTP 伺服器(http.server


簡介

在現代的程式開發中,網路服務是不可或缺的基礎。即使是最簡單的原型、測試工具或是教學示範,都需要一個能夠接受 HTTP 請求、回傳回應的伺服器。Python 內建的 http.server 模組提供了零安裝、即時啟動的 HTTP 伺服器,讓開發者不必額外安裝第三方套件,就能在本機或局域網上快速建立靜態或簡易動態的服務。

本篇文章將從 核心概念實作範例常見陷阱與最佳實踐、以及 實際應用場景 四個面向,深入說明 http.server 的使用方式,協助讀者從「能跑起來」到「寫得安全、可維護」的階段。


核心概念

1. http.server 的基本結構

http.server 其實是由兩個主要類別組成:

類別 功能說明
http.server.HTTPServer 繼承自 socketserver.TCPServer,負責監聽 TCP 連線、接受請求並將其交給處理器(Handler)。
http.server.BaseHTTPRequestHandler 抽象基底類別,定義了 do_GET()do_POST() 等方法,負責解析請求、產生回應。

當我們直接使用 python -m http.server 時,Python 會自動建立一個 SimpleHTTPRequestHandler(繼承自 BaseHTTPRequestHandler),並將當前目錄的檔案以 靜態檔案伺服器 的方式提供。

2. 為什麼要自訂 Handler?

內建的 SimpleHTTPRequestHandler 已足以服務靜態檔案,但在以下情況下,我們往往需要自行撰寫 Handler:

  • 動態產生內容(例如 JSON API、表單回應)
  • 自訂路由(不同 URL 對應不同的處理邏輯)
  • 加入認證、日誌、CORS 等額外功能

自訂 Handler 的方式很簡單:繼承 BaseHTTPRequestHandler,覆寫 do_GET()do_POST() 等方法,然後把它傳給 HTTPServer

3. HTTPServer 的執行流程

  1. 建立 socket:綁定 IP(預設 0.0.0.0)與埠號(預設 8000)。
  2. 等待連線serve_forever() 會持續阻塞,直到收到中斷訊號(Ctrl+C)或程式自行結束。
  3. 分派請求:每當有新連線進來,HTTPServer 會產生一個 Handler 實例,呼叫對應的 do_* 方法。

⚠️ 小提醒HTTPServer 預設是 單執行緒(single‑threaded),同時只能處理一個請求。若需要同時處理多個連線,請改用 ThreadingHTTPServerForkingHTTPServer(Linux 專用)。

4. 常用屬性與方法

屬性/方法 說明
self.path 請求的 URL 路徑(包含 query string)。
self.headers http.client.HTTPMessage 物件,存放所有請求標頭。
self.send_response(code, message=None) 發送狀態碼(例如 200404)。
self.send_header(key, value) 加入單一回應標頭。必須在 end_headers() 前呼叫。
self.end_headers() 結束標頭區塊,之後才能寫入內容。
self.wfile.write(bytes) 實際寫入回應內容(必須是 bytes)。
self.rfile.read(n) 讀取請求主體(例如 POST 的表單資料)。

程式碼範例

以下示範 5 個實用範例,從最簡單的靜態伺服器到支援 JSON API、檔案上傳、跨來源資源共享(CORS)與多執行緒。

範例 1:最簡單的靜態檔案伺服器(單行指令)

# 在終端機執行以下指令,即可在當前目錄啟動 8000 埠的 HTTP 伺服器
python -m http.server 8000

說明:等同於 python -c "from http.server import SimpleHTTPRequestHandler, HTTPServer; HTTPServer(('0.0.0.0',8000), SimpleHTTPRequestHandler).serve_forever()",不需要寫任何程式碼。


範例 2:自訂 Handler,回傳簡易的 HTML 頁面

# file: simple_server.py
from http.server import HTTPServer, BaseHTTPRequestHandler

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # 只處理根目錄 /
        if self.path == '/':
            self.send_response(200)               # 200 OK
            self.send_header('Content-Type', 'text/html; charset=utf-8')
            self.end_headers()
            html = """<!DOCTYPE html>
            <html><head><title>Hello</title></head>
            <body><h1>歡迎使用自訂 HTTP 伺服器</h1></body></html>"""
            self.wfile.write(html.encode('utf-8')) # 必須是 bytes
        else:
            self.send_error(404, "找不到頁面")

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), MyHandler)
    print('伺服器啟動於 http://0.0.0.0:8080')
    server.serve_forever()

重點:使用 send_error() 可自動產生錯誤頁面,省去自行寫 404 HTML。


範例 3:提供 JSON API(GET & POST)

# file: json_api.py
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

class APIHandler(BaseHTTPRequestHandler):
    # 讓瀏覽器或其他客戶端可以跨域呼叫
    def _set_cors_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')

    def do_OPTIONS(self):
        self.send_response(204)  # No Content
        self._set_cors_headers()
        self.end_headers()

    def do_GET(self):
        if self.path == '/api/time':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self._set_cors_headers()
            self.end_headers()
            data = {'time': self.date_time_string()}
            self.wfile.write(json.dumps(data).encode('utf-8'))
        else:
            self.send_error(404, "API not found")

    def do_POST(self):
        if self.path == '/api/echo':
            content_len = int(self.headers.get('Content-Length', 0))
            raw_body = self.rfile.read(content_len)
            try:
                payload = json.loads(raw_body)
            except json.JSONDecodeError:
                self.send_error(400, "Invalid JSON")
                return

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self._set_cors_headers()
            self.end_headers()
            # 回傳相同內容,示範 echo 功能
            self.wfile.write(json.dumps(payload).encode('utf-8'))
        else:
            self.send_error(404, "API not found")

if __name__ == '__main__':
    httpd = HTTPServer(('127.0.0.1', 5000), APIHandler)
    print('JSON API 伺服器啟動於 http://127.0.0.1:5000')
    httpd.serve_forever()

說明

  • do_OPTIONS 處理 CORS 先行請求(pre‑flight)。
  • self.date_time_string() 取得符合 HTTP 標準的日期字串。
  • 使用 json.dumps()encode('utf-8') 產生回傳的 bytes

範例 4:支援檔案上傳(multipart/form-data)

# file: upload_server.py
import cgi
import os
from http.server import HTTPServer, SimpleHTTPRequestHandler

UPLOAD_DIR = 'uploads'
os.makedirs(UPLOAD_DIR, exist_ok=True)

class UploadHandler(SimpleHTTPRequestHandler):
    def do_POST(self):
        # 只接受 /upload 路徑
        if self.path != '/upload':
            self.send_error(404, "Not Found")
            return

        # 解析 multipart/form-data
        ctype, pdict = cgi.parse_header(self.headers.get('Content-Type'))
        if ctype != 'multipart/form-data':
            self.send_error(400, "Invalid Content-Type")
            return

        pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
        fields = cgi.parse_multipart(self.rfile, pdict)

        # 取得上傳的檔案(假設表單欄位名稱是 file)
        uploaded_files = fields.get('file')
        if not uploaded_files:
            self.send_error(400, "No file uploaded")
            return

        for i, file_data in enumerate(uploaded_files):
            filename = f'upload_{i}.bin'
            filepath = os.path.join(UPLOAD_DIR, filename)
            with open(filepath, 'wb') as f:
                f.write(file_data)

        # 回傳成功訊息
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(b'File uploaded successfully!')

if __name__ == '__main__':
    httpd = HTTPServer(('0.0.0.0', 8081), UploadHandler)
    print('檔案上傳伺服器啟動於 http://0.0.0.0:8081')
    httpd.serve_forever()

重點

  • 透過 cgi.parse_multipart 解析 multipart 表單。
  • 為避免檔名衝突,示範用編號方式儲存。實務上應使用 uuid 或原始檔名並做好 安全檢查(避免目錄穿越)。

範例 5:多執行緒 HTTP 伺服器(同時處理多個請求)

# file: threaded_server.py
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler

# 直接使用內建的 SimpleHTTPRequestHandler,支援靜態檔案
if __name__ == '__main__':
    server = ThreadingHTTPServer(('0.0.0.0', 9000), SimpleHTTPRequestHandler)
    print('Threaded HTTP 伺服器啟動於 http://0.0.0.0:9000')
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print('\n伺服器關閉')
        server.server_close()

說明ThreadingHTTPServerHTTPServer 的子類別,會為每個連線建立一個新執行緒,讓多個使用者能同時取得檔案或 API 回應。


常見陷阱與最佳實踐

陷阱 可能的後果 建議的解決方式
使用單執行緒 HTTPServer 同時多個客戶端請求會排隊,導致延遲甚至超時。 在開發階段使用 ThreadingHTTPServer;正式上線則考慮使用 WSGI(如 gunicorn)或 反向代理(Nginx)
直接回傳使用者上傳的檔名 可能造成 目錄穿越../../etc/passwd)或檔名衝突。 只接受白名單字元、使用 os.path.basenameuuid.uuid4() 產生安全檔名
未設定 Content-Type 瀏覽器無法正確解析回傳資料,API 使用者會收到錯誤格式。 始終使用 self.send_header('Content-Type', ...),對 JSON 使用 application/json
缺乏 CORS 設定 前端跨域請求被瀏覽器阻擋。 在 API 回應中加入 Access-Control-Allow-Origin: *(或限定來源)
忘記 end_headers() 回傳的 HTTP 標頭不完整,客戶端會收到錯誤或無法解析。 每次在寫入 wfile 前呼叫 self.end_headers()
使用 print 輸出除錯訊息 會與 HTTP 回應混在一起,導致協定錯誤。 使用 logging 模組,或將除錯訊息寫入檔案/標準錯誤
未捕捉例外 程式崩潰導致伺服器停止服務。 do_* 方法內部使用 try/except,並回傳適當的 HTTP 錯誤碼(如 500)

其他最佳實踐

  1. 使用 with 管理檔案:確保檔案資源正確關閉。
  2. 限制請求大小:對於上傳或 POST 請求,檢查 Content-Length,防止 DoS 攻擊。
  3. 啟用 HTTPShttp.server 本身不支援 TLS,開發測試時可搭配 ssl.wrap_socket,正式環境建議使用反向代理(Nginx/Apache)提供 TLS。
  4. 日誌紀錄BaseHTTPRequestHandler.log_message() 可自訂輸出格式,或直接使用 logging
  5. 分離路由與處理邏輯:即使是簡易伺服器,也建議把路由表寫成字典,讓程式易於擴充。

實際應用場景

場景 為何選擇 http.server 可能的擴充方式
前端開發時的靜態資源伺服器 零設定、即時啟動、可直接瀏覽目錄結構。 加入 ThreadingHTTPServer、使用 --directory 指定根目錄。
快速原型 API 只需要幾行程式碼即可提供 JSON 端點,方便前端測試。 加入 CORS、簡易驗證(API key)或使用 uvicornFastAPI 進一步擴充。
教育訓練或實驗室 學生可直接在本機建立自己的伺服器,了解 HTTP 流程。 結合 socketasyncio 示範非同步 I/O。
IoT 裝置的本地管理介面 裝置資源受限,無法安裝完整的 Web 框架。 ThreadingHTTPServer 提供簡易設定頁面,配合 cgi 處理表單。
自動化測試的 Mock Server 測試程式需要一個可預測回應的 HTTP 端點。 do_GET / do_POST 中回傳固定 JSON,或根據 URL 讀取本機檔案做回應。

小結http.server 不是用來建置大型商業系統的解決方案,但在 開發階段、測試環境、教學示範 中,它的「即開即用」特性可以大幅提升工作效率。


總結

  • http.server 是 Python 標準函式庫中最輕量的 HTTP 伺服器,適合 快速測試、原型開發與教學
  • 透過 自訂 Handler,我們可以實作靜態檔案、JSON API、檔案上傳、CORS 等常見需求。
  • 為了避免 單執行緒瓶頸安全漏洞,建議使用 ThreadingHTTPServer、做好檔名過濾、限制請求大小、加入日誌與錯誤處理。
  • 在實務上,http.server 常被用作 本機開發伺服器、Mock API、IoT 裝置管理介面,配合反向代理或更完整的 WSGI 框架即可滿足生產環境需求。

掌握了這些概念與技巧後,你就能在任何需要臨時提供 HTTP 服務的情境下,快速部署一個 安全、可維護 的伺服器,為後續的更高階開發奠定堅實基礎。祝你玩得開心、寫得順利! 🎉