前面的測試已驗證了重構後的程式碼基本上達成了讓平台具備 API Key 功能的目標, 本篇要來測試本地自動化佈署功能, 在此過程中發現了原系統的一些缺陷, 都利用 agy 請 Claude Sonnet 修正, 除了主程式 serverless.py 外, 還包括 functions 下的 update_function.py 與 save_function.py.
6. 本地自動化佈署 :
第一版的 serverless 系統在新增或修改函式時都需登入平台, 透過手動操作網頁來完成, 在本次重構加上 API Key 機制後, 系統安全性增強, 可以在本地撰寫一個自動化佈署程式, 攜帶 API Key 經過驗證完成函式的佈署. 只要寫一個 Python 函式讀取要佈署的函式檔, 然後攜帶 API key 呼叫 update_function 或 save_function 即可完成函式的佈署.
(1). 與 Claude 討論 :
> 我執行下列佈署程式 :
# deploy.py
import os
import requests
# 1. 設定伺服器資訊與 API Token
# 地端測試可用 http://127.0.0.1:5000,上雲端後改成你的 Render 網址
SERVER_URL="http://127.0.0.1:5000"
API_TOKEN="new-test-token"
# 2. 定義要更新的函式名稱與本地檔案路徑
TARGET_FUNCTION_NAME="hello"
LOCAL_FILE_PATH="hello.py"
def deploy_function():
# 檢查本地檔案是否存在
if not os.path.exists(LOCAL_FILE_PATH):
print(f"❌ 找不到本地檔案: {LOCAL_FILE_PATH}")
return
# 讀取本地最新的程式碼內容
with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as f:
new_code=f.read()
# 3. 準備發送給 update_function 的端點與資料
# 路由設計是動態載入的 /function/update_function
url=f"{SERVER_URL}/function/update_function"
headers={
"X-API-Key": API_TOKEN,
"Content-Type": "application/json"
}
payload={
"func_name": TARGET_FUNCTION_NAME,
"code": new_code
}
print(f"🚀 正在將 {LOCAL_FILE_PATH} 部署至 {SERVER_URL}...")
try:
# 發送 POST 請求更新遠端函式
response=requests.post(url, json=payload, headers=headers)
# 檢查伺服器回應狀態
if response.status_code == 200:
print("✅ 函式更新成功!")
print("伺服器回應:", response.json())
else:
print(f"❌ 更新失敗 (狀態碼: {response.status_code})")
try:
print("錯誤原因:", response.json())
except ValueError:
print("非 JSON 回應內容:", response.text)
except requests.exceptions.RequestException as e:
print(f"💥 連線發生異常: {e}")
deploy_function()
===
出現如下錯誤 :
D:\python\test>python deploy.py
🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
❌ 更新失敗 (狀態碼: 401)
錯誤原因: {'error': 'Authentication required', 'login_url': '/login'}
===
在提供有效 API Key 情況下應該要能通過不受 login 阻攔, 請修改
▸ Thought for 29s
The user is experiencing a 401 error with the message "Authentication required" ...
問題很清楚。看一下目前的驗證順序:
● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)
根本原因找到了:
步驟 1(L165)先攔截, update_function 在 PROTECTED_FUNCTIONS 裡,API Client 沒有 Session →
直接 401,根本輪不到步驟 2 的 Token 驗證。
解法:將兩層驗證合併,一次判斷 Session 或 Token 擇一通過,同時可以移除 TOKEN_PROTECTED_FUNCTIONS
(已不需要):
● Read(D:/antigravity_cli/projects/serverless/serverless.py)
if __name__ == "__main__":
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)
完成。來驗證最終結果:
● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)
還有一個小問題:L184 的步驟編號 # 3. 與 L182 的 # 3. 重複了,順手修正:
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)
完成。以下是這次修改的完整說明:
──────
## 問題根源
原本流程(有 Bug):
步驟 1:update_function 在 PROTECTED_FUNCTIONS? → 是
check_auth() = False(API Client 無 Session)
→ 直接 401 ❌ (Token 驗證根本輪不到)
## 修正後的流程
新流程(handle_function 前三步):
步驟 1:一次性完成雙重驗證
is_session_auth = check_auth()
if 無 Session → 嘗試讀 X-API-Key → 查資料庫 → is_token_auth
is_authorized = is_session_auth OR is_token_auth
步驟 2:管理模組 → 需要 is_authorized,否則 401
步驟 3:一般函式 → 需要 is_authorized,否則 401
## 各情境結果
呼叫者 | Session | API Key | 結果
-----------------------------|----------------------|----------------------|----------------------
瀏覽器管理者(update/save) | ✅ | ❌ | ✅ 放行
deploy.py API Client | ❌ | ✅ 有效 | ✅ 放行
未認證請求 | ❌ | ❌ | ❌ 401
無效 Token | ❌ | ❌ 無效 | ❌ 401
────────────────────────────────────────────────────────────
> 成功了, 但出現連線異常:
D:\python\test>python deploy.py
🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
✅ 函式更新成功!
💥 連線發生異常: Expecting value: line 1 column 1 (char 0)
這要修改 deploy.py 還是系統的 update_function.py 較好?
▸ Thought for 27s
The user is seeing a successful update (✅) but then a connection error. Let me...
先看清楚錯誤發生在哪一行:
deploy.py 收到 200 → 印出 ✅ → 再執行 response.json() 時爆炸,代表 update_function.py
回傳的是 HTML 而非 JSON。
讓我確認 update_function.py 的完整內容:
● Read(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)
問題找到了,而且有兩個 bug 同時存在:
# | 問題 | 說明
---|--------------|-------------------------------------------------------------------------------
1 | 欄位名稱不符 | deploy.py 送 func_name ,但 update_function.py 讀 module_name →
| | 讀到空字串
2 | 輸入格式不符 | deploy.py 送 JSON body,但 update_function.py 只讀 request.form →
| | 永遠讀不到資料
3 | 回應格式不符 | 成功時回傳 HTML, deploy.py 呼叫 response.json() 爆炸
──────
## 修哪個?
修 update_function.py ,理由:
• 這是 server 端的問題(不認識 JSON 輸入、不回傳 JSON)
• 瀏覽器用表單呼叫、API Client 用 JSON 呼叫,server 應該兩者都支援
• deploy.py 的設計是正確的,不需要改
● Edit(D:/antigravity_cli/projects/serverless/functions/update_function.py)
● Create(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)
完成。以下是修改摘要:
──────
## 修改的檔案
### update_function.py
修正了三個問題:
# | 問題 | 修正方式
---|--------------------------------------|-------------------------------------------------------
1 | 只讀 request.form ,不懂 JSON | 用 request.is_json 判斷,JSON 時改讀
| | request.get_json()
2 | 只認 module_name ,不認 func_name | JSON 輸入時同時嘗試讀取 func_name 與 module_name
3 | 成功時只回傳 HTML | JSON 請求時回傳 {"message": "...", "func_name":
| | "..."}
──────
## 預期執行結果
D:\python\test>python deploy.py
🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
✅ 函式更新成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}
────────────────────────────────────────────────────────────
> update_function 是更新已有的函式, 如果要新增函式則是使用 add_function 與 save_function,
如果要佈署新函式, deploy.py 要怎麼改? 同時請分析 add_function.py 與 save_function.py 在執行 deploy.p
佈署方面是否有需要配合修改之處?
▸ Thought for 1s
The user wants to understand the deployment flow for new functions. Let me first...
先讀取三個相關檔案:
● Read(D:/antigravity_cli/projects/serverless/functions/add_function.py)
● Read(D:/antigravity_cli/projects/serverless/functions/save_function.py) (ctrl+o to expand)
分析完畢。以下是完整報告:
──────
## 架構釐清
add_function → 純 UI 頁面,回傳 HTML 表單,action 指向 save_function
save_function → 實際寫檔(新增),只接受 form POST
update_function → 實際寫檔(覆蓋),已修改支援 JSON
deploy.py 要新增函式,只需呼叫 save_function ,不需碰 add_function (那是給瀏覽器用的 UI)。
──────
## save_function.py 的問題(與 update_function 一樣)
# | 問題 | 影響
-----------------------------|--------------------------------------|-----------------------------
1 | 只讀 request.form ,不懂 JSON | API Client 送來的資料讀不到
2 | 只認 module_name ,不認 func_name | deploy.py 欄位名稱不符
3 | 成功/失敗都回傳 HTML | response.json() 會爆炸
──────
## 修改計劃
修改 save_function.py :與 update_function.py 相同邏輯,支援 JSON/form 雙輸入。
修改 deploy.py :加入「新增 or 更新」判斷邏輯,自動選擇呼叫 save_function 或 update_function
。
開始修改:
● Create(D:/antigravity_cli/projects/serverless/functions/save_function.py) (ctrl+o to expand)
現在修改 deploy.py ,加入自動判斷新增或更新:
● Create(D:/python/test/deploy.py) (ctrl+o to expand)
完成。以下是完整摘要:
──────
## 修改了哪些檔案
檔案 | 修改內容
--------------------------------|-----------------------------------------------------------------
save_function.py | 支援 JSON/form 雙輸入、相容 func_name / module_name 、回傳
| JSON(201)或 HTML
deploy.py | 新增 function_exists() 探測,自動選 save_function 或
| update_function
functions/update_function.py | 上一輪已修改,本次不動
add_function.py | 不需修改,它只是瀏覽器 UI 頁面
──────
## deploy.py 新流程
執行 deploy.py
↓
function_exists():GET /function/{func_name}
├─ 404 → 函式不存在 → POST /function/save_function → 回 201 ✅
└─ 其他 → 函式已存在 → POST /function/update_function → 回 200 ✅
──────
## 預期執行結果
# 第一次佈署(函式不存在)
🚀 正在將 hello.py 新增至 http://127.0.0.1:5000...
✅ 函式新增成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功建立'}
# 第二次佈署(函式已存在)
🚀 正在將 hello.py 更新至 http://127.0.0.1:5000...
✅ 函式更新成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}
(2). 本地佈署測試 :
上面程序中 Claude 已經修改 serverless.py, update_function.py, 以及 save_function.py, 且修正了原先使用的佈署程式 deploy.py. 接下來要實測看看是否可在本機用佈署程式 deploy.py 將 hello.py 發布到後端平台上.
先複習一下 serverless 平台的函式檔基本架構 :
# func_module.py
def main(request, **kwargs):
config=kwargs.get('config', {}) # 預設為空 dict
result='do something'
return result
注意, main() 的參數一定要傳入 request 與關鍵字參數 **kwargs :
- request :
用來接收 HTTP 請求的相關資訊 (例如前端傳進來的 Payload, Query Parameters, Headers等). - **kwargs 與 config :
這是一個極具擴充性的設計, 透過 kwargs.get('config', {}) 可以在伺服器端 (Flask 主程式) 執行該函式時動態注入系統級的配置或環境變數 (例如資料庫連線字串, 加密金鑰等), 而不需要把這些敏感資訊寫死在 hello.py 裡面.
為了展示此架構中 main() 的兩個參數用途, 先開啟 .env 檔, 添加一個環境變數 DB_TIMEOUT :
DB_TIMEOUT=30
然後按照上面的函式架構寫了一個新的 hello.py 函式檔 (其實只是加上讀取環境變數 DB_TIMEOUT 而已) :
# hello.py
def main(request, **kwargs):
# 讀取系統注入的設定(如果有的話)
config=kwargs.get('config', {})
# 這裡可以安全地取得環境變數
db_timeout=config.get('DB_TIMEOUT', 30)
# 先從請求字串擷取 name 參數 (?name=)
name=request.args.get('name')
if not name: # 請求字串沒有 name 參數
# 嘗試從 RESTful 子路徑 (例如 /function/hello/Tony) 中擷取 name 參數
subpath=request.view_args.get('subpath', '') # 取得 Flask 傳入的 subpath 參數
parts=subpath.strip('/').split('/') # 拆解成串列例如 ['Tony']
if parts:
name=parts[0] # 取第一段作為 name (例如 'Tony')
# 從 request 也沒有找到
if not name:
name='World'
result=f"Hello {name}! Database timeout is set to {db_timeout}s."
return result
然後用下列 Claude 修正過的 deploy.py 程式來佈署此新的 hello.py (注意 API_TOKEN 必須填入有效的 API Key) :
# deploy.py
import os
import requests
# 1. 設定伺服器資訊與 API Token
# 地端測試可用 http://127.0.0.1:5000,上雲端後改成你的 Render 網址
SERVER_URL="http://127.0.0.1:5000"
API_TOKEN="new-test-token"
# 2. 定義要佈署的函式名稱與本地檔案路徑
TARGET_FUNCTION_NAME="hello"
LOCAL_FILE_PATH="hello.py"
def function_exists(func_name):
"""檢查遠端伺服器上的函式是否已存在(透過 GET 探測)"""
url=f"{SERVER_URL}/function/{func_name}"
headers={"X-API-Key": API_TOKEN}
try:
response=requests.get(url, headers=headers)
# 404 表示不存在,其他狀態(200/400/500)表示檔案存在
return response.status_code != 404
except requests.exceptions.RequestException:
return False # 連線失敗時保守假設不存在
def deploy_function():
# 檢查本地檔案是否存在
if not os.path.exists(LOCAL_FILE_PATH):
print(f"❌ 找不到本地檔案: {LOCAL_FILE_PATH}")
return
# 讀取本地最新的程式碼內容
with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as f:
new_code=f.read()
headers={
"X-API-Key": API_TOKEN,
"Content-Type": "application/json"
}
payload={
"func_name": TARGET_FUNCTION_NAME,
"code": new_code
}
# 3. 自動判斷:函式已存在 → update,不存在 → save(新增)
if function_exists(TARGET_FUNCTION_NAME):
url=f"{SERVER_URL}/function/update_function"
action="更新"
else:
url=f"{SERVER_URL}/function/save_function"
action="新增"
print(f"🚀 正在將 {LOCAL_FILE_PATH} {action}至 {SERVER_URL}...")
try:
response=requests.post(url, json=payload, headers=headers)
# 新增成功是 201,更新成功是 200
if response.status_code in (200, 201):
print(f"✅ 函式{action}成功!")
print("伺服器回應:", response.json())
else:
print(f"❌ {action}失敗 (狀態碼: {response.status_code})")
try:
print("錯誤原因:", response.json())
except ValueError:
print("非 JSON 回應內容:", response.text)
except requests.exceptions.RequestException as e:
print(f"💥 連線發生異常: {e}")
if __name__ == "__main__":
deploy_function()
可見 Claude 修改後的 deploy.py 是透過讀取 functions 資料夾下有無該函式來判斷是要新增 (無) 還是更新 (有), 目前 functions 下有 hello.py, 所以執行
執行結果 :
D:\python\test>python deploy.py
🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
✅ 函式更新成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}
接下來驗證此新佈署的 hello.py 函式功能是否正確 :
可見不同的參數傳遞方式功能都正確, 且 .env 中的環境變數 DB_TIMEOUT 也正確讀到了. 接下來測試 save_function 是否能順利佈署新函式, 先把 functions 底下的 hello.py 刪除, 然後再執行一次 deploy.py :
D:\python\test>python deploy.py
🚀 正在將 hello.py 新增至 http://127.0.0.1:5000...
✅ 函式新增成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功建立'}
檢視 functions 下果然 hello.py 已被建立, 再次執行網頁測試結果相同.



沒有留言 :
張貼留言