2026年7月18日 星期六

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (二)

在前一篇測試中, 我已將 serverless 第一版原始碼從 GitHub 上複製到本機 Antigravity CLI 的專案目錄下, 並做好前置處理, 包含將原專案打上標籤, 建立新分支, 建立隔離資料夾並移動應用 App, 最後提交 Git 紀錄. 本篇要用 agy 來進行重構, 為 serverless 平台加上 API Token 認證機制. 

本系列全部文章索引參考 :



2. 用 agy 重構專案 : 

切換到專案目錄下後輸入 agy 啟動 Antigravity CLI 介面, 這次仍然使用 Claude Sonnet 模型來完成此重構計劃. 輸入下列提示詞 :

我們正在 feature/api-token-auth 分支上,要為這個 serverless 專案加上 API Token 認證機制。請協助修改 serverless.py,完成以下任務:

1. 初始化資料庫:
   在系統啟動時(或初始化 DB 的地方),自動建立 `api_tokens` 資料表(欄位包含:id, token, owner, is_active, created_at)。

2. 實作驗證裝飾器(Decorator):
   撰寫一個 `@require_api_token` 裝飾器,從請求的 Header 中讀取 `X-API-Key`。
   - 若 Header 遺失或 Token 不存在於資料庫中(且 is_active 必須為 1),回傳 401 Unauthorized。

3. 保護 API 路由:
   將這個裝飾器套用到「儲存/更新 function」以及「執行 function」的 Flask Route 上。

請先分析原始專案的架構, 提出你的重構計劃, 讓我看過後再修改程式碼。




Claude 閱讀原專案後規劃結果如下 :

> 我們正在 feature/api-token-auth 分支上,要為這個 serverless 專案加上 API Token 認證機制。
  請協助修改 serverless.py,完成以下任務:

  1. 初始化資料庫:
     在系統啟動時(或初始化 DB 的地方),自動建立 `api_tokens` 資料表(欄位包含:id, token, owner,
  is_active, created_at)。

  2. 實作驗證裝飾器(Decorator):
     撰寫一個 `@require_api_token` 裝飾器,從請求的 Header 中讀取 `X-API-Key`。
     - 若 Header 遺失或 Token 不存在於資料庫中(且 is_active 必須為 1),回傳 401 Unauthorized。

  3. 保護 API 路由:
     將這個裝飾器套用到「儲存/更新 function」以及「執行 function」的 Flask Route 上。

  請先分析原始專案的架構, 提出你的重構計劃, 讓我看過後再修改程式碼。
▸ Thought for 2s
  Let me first explore the project structure to understand the codebase before mak...

● ListDir(D:/antigravity_cli/projects/serverless)
● Read(D:/antigravity_cli/projects/serverless/serverless.py)
● ListDir(D:/antigravity_cli/projects/serverless/functions)
● Read(D:/antigravity_cli/projects/serverless/requirements.txt)
● Read(D:/antigravity_cli/projects/serverless/functions/save_function.py)
● Read(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)

  已完整閱讀專案架構,以下是我的分析與重構計劃。
  ──────

● Create(C:/Users/tony1/.gemini/antigravity-...-a43e-cfa6fbc204e5/refactor_plan.md) (ctrl+o to expand)

  以下是重構計劃的摘要:

  ## 架構分析結論

  現有系統有兩層認證:

  1. Session Cookie — 管理者透過  /login  密碼登入,保護  PROTECTED_FUNCTIONS  清單
  2. 一般函式執行 — 完全沒有保護,任何人只要知道 URL 就能呼叫
  ──────
  ## 三個關鍵設計決策

  ### 1.  init_db()  修正

  移除  if not os.path.exists(DB_PATH)  判斷,改用  CREATE TABLE IF NOT EXISTS
  ,確保每次啟動都檢查表是否存在,不會因 DB 已建立但缺表而漏掉建立  api_tokens 。

  ### 2.  @require_api_token  裝飾器

  讀取  X-API-Key  Header → 查詢  api_tokens  表( is_active=1 )→ 不符合一律回 401 JSON。

  ### 3. 保護路由的方案

  因為  save_function 、 update_function  與一般函式執行共用同一個  handle_function()
  路由,無法直接套裝飾器。

  採用方案 A:在路由內部邏輯中呼叫驗證輔助函式,改動最小且與現有 Session 機制並存。
  ──────
  計劃文件已整理完畢,請確認後按「Proceed」或告訴我需要調整的地方,即可開始撰寫程式碼。

此計劃看來很周到, 輸入 Proceed 按 Enter 看它表演即可 :

> Proceed

▸ Thought for 1s
  The user wants to proceed with the refactoring plan. Let me implement the change...
  好的,開始實作!

● Read(D:/antigravity_cli/projects/serverless/serverless.py)
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  修改成功,來驗證最終結果:

● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  內容正確。最後修正一個步驟編號的小問題(step 2 重複出現):
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  所有修改完成,以下是完整摘要:
  ──────
  ## ✅ 修改完成摘要
  檔案:serverless.py

  ### 變更一覽

   #             | 位置          | 修改內容
  ---------------|---------------|------------------------------------------------------------------
   1             | L6            | 新增  from functools import wraps
   2             | L16–37        |  init_db()  改為冪等執行,新增  api_tokens  資料表
   3             | L56–73        | 新增  require_api_token(f)  裝飾器(備用,可掛在未來獨立路由上)
   4             | L105–106      | 新增  TOKEN_PROTECTED_FUNCTIONS  常數
   5             | L167–181      |  handle_function()  步驟 2 加入 API Token 驗證邏輯
   6             | L207–208      | 移除  __main__  區塊重複的  init_db()  呼叫

  ### 認證邏輯流程(修改後)

    /function/<func_name>
        │
        ├─ func_name 在 PROTECTED_FUNCTIONS?
        │   └─ 未登入 Session → 401 (Authentication required)
        │
        ├─ func_name 在 TOKEN_PROTECTED_FUNCTIONS(save/update)
        │  OR func_name 不在 PROTECTED_FUNCTIONS(一般函式)?
        │   ├─ 無 X-API-Key Header → 401 (Missing API token)
        │   └─ Token 無效或 is_active=0 → 401 (Invalid or inactive API token)
        │
        └─ 驗證通過 → 動態載入並執行函式

  ### 快速測試方式

    # 插入測試 Token
    sqlite3 serverless.db "INSERT INTO api_tokens (token, owner) VALUES ('test-token-123',
  'developer');"

    # ✅ 正確呼叫
    curl http://localhost:5000/function/hello -H "X-API-Key: test-token-123"

    # ❌ 無 Token → 401
    curl http://localhost:5000/function/hello

不到三分鐘就完成重構了, 這若是照以前人工來改程式碼, 恐怕得花上一周或更久. 我叫 Claude 說明它改了哪些地方 :

> 解釋你修改了哪些檔案? 哪個部分(函式)?  

▸ Thought Process
  The user wants a clear explanation of which files and which functions were modif...
  這次只修改了 1 個檔案,共改動 4 個區塊:
  ──────
  ## 修改的檔案
  ### serverless.py
  ──────
  ### 1. Import 區塊(L1–8)

    + from functools import wraps

  新增  wraps ,讓裝飾器能保留被包裝函式的原始名稱與文件字串。
  ──────
  ### 2.  init_db()  函式(L16–37)
  改動前:用  if not os.path.exists(DB_PATH)  判斷,只有 DB 不存在時才建表,且只建  call_stats
  一張表。

  改動後:
  • 移除  os.path.exists  條件判斷,改為每次啟動都執行( CREATE TABLE IF NOT EXISTS
  本身就是冪等的,不會重複建表)
  • 新增  api_tokens  資料表,欄位為  id, token, owner, is_active, created_at
  ──────
  ### 3. 新增  require_api_token()  裝飾器(L56–73)

  全新函式,位於  record_call()  之後、 app = Flask(...)  之前。

  功能:從 Request Header 讀取  X-API-Key ,查詢資料庫確認 Token 存在且  is_active=1 ,否則回傳  401
  。

  │ 此裝飾器目前定義為備用,供未來掛在獨立路由上使用。
  ──────
  ### 4.  handle_function()  路由函式(L163–181)

  在原有的 Session 驗證之後,插入第二層 API Token 驗證邏輯(步驟 2):

    觸發條件(滿足其一即驗證):
      A. func_name 在 TOKEN_PROTECTED_FUNCTIONS → save_function、update_function
      B. func_name 不在 PROTECTED_FUNCTIONS → 所有一般使用者自訂函式

  兩種情況都會檢查  X-API-Key  Header,並查詢  api_tokens  表,不符合就回傳  401 。
  ──────
  ### 未修改的部分

   未動到的檔案/函式                               | 原因
  -------------------------------------------------|------------------------------------------------
    functions/  目錄下所有模組                     | 驗證邏輯集中在  serverless.py ,子模組不需變動
    check_auth()                                   | Session 登入機制保持原樣
    record_call()                                  | 呼叫統計邏輯不變
    /login 、 /logout  路由                        | 管理者登入流程不變

這個說明後續可以開啟主程式 serverless.py 來檢視. 


3. 功能測試 : 

上面 agy 已完成重構, 接下來實地測試 Claud 實作的 API Token 是否能正常運作. 在進行測試之前, 須在專案目錄下建立一個純文字檔 .env, 紀錄管理者的 token 與 key, 前者是自己容易記的密碼, 後者是用工具函式產生的一長串金鑰, 可以用 Python 內建的 secrets 模組來產生, 參考 :


在專案目錄下用記事本編輯一個 .env 檔, 輸入如下內容 (範例) :

SECRET_TOKEN=admin123
SECRET_KEY=fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE

其中的 SECRET_KEY 可在 Thonny 的 Python 互動環境匯入 secrets 模組後呼叫 secrets.token_urlsafe(32) 函式產生, 例如 :

>>> import secrets  
>>> secrets.token_urlsafe(32)  
'fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE'

由於我主要的測試環境主要是使用 Thonny 自帶的 Python, 所有 requirements.txt 中的套件在此開發環境都有安裝 (此專案主要是用到 Flask), 所以按 Thonny 的 "工具/開啟系統終端機" 開啟命令提示字元視窗, 切換到專案目錄下, 用下列指令啟動 Flask 開發伺服器 :

D:\antigravity_cli\projects\serverless>python serverless.py  
 * Serving Flask app 'serverless'
 * Debug mode: on

伺服器運行起來後, serverless.py 就會建立一個 SQlite 資料庫 serverless.db, 執行下列指令在此系統資料庫裡的 api_tokens 資料表填入測試用的權杖 test-token-123 : 

PS D:\antigravity_cli\projects\serverless> python -c "import sqlite3; conn = sqlite3.connect('serverless.db'); conn.execute('INSERT INTO api_tokens (token, owner, is_active) VALUES (?, ?, ?)', ('test-token-123', 'developer', 1)); conn.commit(); conn.close(); print('🎉 測試 Token 寫入成功!')"
🎉 測試 Token 寫入成功!

然後在瀏覽器網址列輸入 Flask 開發伺服器預設網址 http://localhost:5000/ 會看到 serverless 平台網站已順利運行, 按 "登入系統" :



輸入 .env 檔案中設定的 SECRET_TOKEN 密碼, 按 "登入" 鈕 :



登入成功後按 "函式列表" 進入管理頁面首頁 :



按 "資料表" :




可見多了一個 api_tokens 的資料表, 這就是 Claude 重構此平台時所添加, 用來儲存 API Key (token) 的資料表, 按 "檢視" :



顯示前面用 Python 指令寫入 api_tokens 資料表的測試用權杖 :




接下來用 curl.exe 測試沒帶 token 的情況, 這樣的請求會被拒絕 : 

PS D:\antigravity_cli\projects\serverless> curl.exe -i http://localhost:5000/function/hello   
HTTP/1.1 401 UNAUTHORIZED
Server: Werkzeug/2.3.7 Python/3.10.11
Date: Fri, 17 Jul 2026 11:36:12 GMT
Content-Type: application/json
Content-Length: 73
Connection: close

{
  "error": "Missing API token",
  "hint": "Provide X-API-Key header"
}

可見未攜帶 token 的請求會回應 "401 UNAUTHORIZED". 主體 (body) 內容為 JSON 訊息, error 鍵之值表示沒有收到 API token; 而 hit 鍵之值則表示必須在標頭攜帶 X-API-Key, 可在 curl.exe 指令中用 -H 參數指定此 token, 例如 : 

PS D:\antigravity_cli\projects\serverless> curl.exe -i http://localhost:5000/function/hello -H "X-API-Key: test-token-123"
HTTP/1.1 200 OK
Server: Werkzeug/2.3.7 Python/3.10.11
Date: Fri, 17 Jul 2026 15:41:03 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 12
Connection: close

Hello World!

可見若有攜帶正確的 X-API-Key 就會執行該函式, 回應 200 OK. 


4. 線上管理 API Key : 

在第一版的 serverless 函式執行平台就已實作了線上管理 serverless.db 的功能, 除了提供超連結可刪除資料表與紀錄外, 還可以執行 SQL 指令以新增, 更新, 或刪除紀錄. 

按資料表列表頁面下方的 "執行 SQL" :




輸入 SQL 指令後按 "執行" 即可, 例如要修改 owner=developer 使用中的 token 的 SQL 指令 :

UPDATE api_tokens 
SET token = 'new-test-token' WHERE owner='developer' AND is_active=1;




執行結果為成功 :




返回資料表內容檢視頁面可知 API Key (token 欄位) 已經更新了 : 




新增 API key 的 SQL 例如 : 

INSERT INTO api_tokens (token, owner, is_active) 
VALUES ('fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE', 'proj-a', 1);






只要以管理員身分登入, 就可以線上管理 API Key 了. 

以上測試顯示 agy 的重構確實達成了預期的目標, 讓 serverless 平台升級為需要 API Key 才能使用的網路服務, 避免伺服器被濫用或遭到攻擊. 

沒有留言 :