POST /blogs/handle_push_fail.json
狀態:Caller 與 Legacy contract 已確認;PHP 8 尚未實作。
最後更新:2026-07-31
快速結論
這支不是部落格 API,而是 AWS SNS 的手機推播失敗 webhook。
AWS SNS push failure
→ POST /blogs/handle_push_fail.json
→ INSERT push_endpoints_fail_logs
→ HTTP 200,body `ok`- 前端不會呼叫。
- AWS SNS request 不帶 PRO360 API key;Legacy 明確將此 path 排除在 API key guard 外。
- 功能只有保存失敗通知,不重送推播、不停用 endpoint、不寫 queue/activity/notification。
- Legacy 不驗 SNS signature、TopicArn 或 request method;這是風險,但 migration 預設先保持一致。
Caller 與 Production 證據
2026-07-31 AWS SNS list-subscriptions:
| 環境 endpoint | Topic |
|---|---|
| Production | PushNotificationFail |
| Staging | PushNotificationFail-staging |
| Staging | PushNotificationFail |
Staging 同時訂閱 Staging 與 Production topic,可能接到正式 push failure。這是既有 AWS 設定,本次不修改;切流前要由 owner 確認是否刻意保留。
Production ALB 2026-06-29~2026-07-28:
- 55,957 POST calls,全部走 Legacy。
- 55,952 筆 2xx、5 筆 5xx、0 筆 4xx。
- 最後看到流量:2026-07-28 01:44:57 UTC。
Legacy host ip-10-5-2-19 retained access logs:12,821 筆,User-Agent 全部是 Amazon Simple Notification Service Agent。這是單機保留紀錄,數量不可代替完整 30 天 ALB 數字;caller 身分已一致指向 AWS SNS。
程式位置
Legacy
Root:/Users/mattsu/Documents/Site/get-lancer-docker-2/get-lancer
app/Controller/BlogsController.phphandle_push_fail():208-235。- 讀 raw body/outer JSON:210-211。
- 取
Subject、nestedMessage:213-223。 - DB insert:225-233。
- response:234。
app/Controller/AppController.php- JSON API guard:145-181。
handle_push_fail.jsonskip API key:155-177。
app/Model/PushLog.php:2-24- action 只借 Cake model connection 執行 raw SQL;
PushLog本身的 table/addCount()不是本 API 邏輯。
- action 只借 Cake model connection 執行 raw SQL;
PHP 8 現況
Endpoint/V1/Blogs.php:19-110- 只有
index()、stat()、logout()、payment_notify();沒有handle_push_fail()。
- 只有
Lib/Common/RouterRule/Mapping.php:20-351- 目前沒有本 API 的明確 mapping。
Lib/Common/RouterRule/RouterRuleAbstract.php:24-27- action 預設為
index。
- action 預設為
Lib/Common/RouterRule/File.php:58-75- method 不存在時不會選到
handle_push_fail,因此現況會錯落到Blogs::index()。
- method 不存在時不會選到
Endpoint/V1/Blogs.php:24-44index()要求 API key且回 blog list,完全不符合 webhook contract。
Lib/Common/Request/RequestHttp.php:18-24,55-58- 已保存 raw request body,可由
getBody()取得原始 bytes。
- 已保存 raw request body,可由
Lib/Model/PushLog.php:16-35- 對應
push_logs,不可為本 API 直接改 shared table behavior;後續應新增專用 model/method。
- 對應
Guard 與 request
| 項目 | Legacy 行為 |
|---|---|
| URL | /blogs/handle_push_fail.json |
| 正式 method | POST |
| method guard | action 內沒有;Production 只觀察到 POST |
| API key | 不要求 |
| user session | 不要求 |
| SNS signature | 不驗證 |
| TopicArn allowlist | 不驗證 |
| body | 直接保存 php://input 原始內容 |
預期 SNS shape:
{
"Subject": "<subject>",
"Message": "{\"EndpointArn\":\"<endpoint>\",\"FailureType\":\"<type>\"}"
}解析規則:
- Outer body 用 associative JSON decode。
Subject不存在時用空字串。Message存在時再做一次 associative JSON decode。- Nested
EndpointArn/FailureType不存在或 decode 失敗時用空字串。 - Outer body malformed/empty 時仍執行 insert;
context保存收到的原始字串。
不可自行新增 required-field validation、去重、signature verification或 topic allowlist;若要改善,需先另外取得 migration decision。
DB write
本地 DB schema 已於 2026-07-31 以容器內 PHP 8.2、DBFactory 的 SHOW COLUMNS 確認。
| 欄位 | 型別 | 寫入來源 |
|---|---|---|
id | int auto increment | DB |
created | datetime nullable | SQL NOW() |
endpoint | varchar(255) nullable | nested Message.EndpointArn,缺少時 "" |
subject | varchar(255) nullable | outer Subject,缺少時 "" |
failure_type | varchar(255) nullable | nested Message.FailureType,缺少時 "" |
context | text nullable | raw HTTP body,不重新 encode |
Legacy SQL 語意:
INSERT INTO push_endpoints_fail_logs
(created, endpoint, subject, failure_type, context)
VALUES
(NOW(), :endpoint, :subject, :failure_type, :context);- 每次 request 寫一筆;沒有 uniqueness check或 transaction。
- 不更新
push_endpoints/push_endpoints_inactives。 - 沒有 payment、queue、activity、下游 notification。
- DB error 沒有 endpoint try/catch;Production 30 天有 5 筆 5xx,原因尚待查 log。
Config/deployment
- Legacy action沒有讀
Configure::read()、template、queue或 provider credential。 - PHP 8不需要新增 endpoint專用
ProConfig;只依賴既有 DB connection。 - 外部 deployment設定是 AWS SNS subscription。URL domain不變時不必改 subscription;ALB必須將精確 path送到已完成 parity的 PHP 8 target。
- Staging/Production topic與 subscription不可因程式部署自動新增、刪除或互換。
Response 與 log
成功 action 直接:
ok- Legacy source可確認 body,但 HTTP
Content-Type尚待 Staging raw header實測。 die('ok')後不走一般 JSON response。- Legacy
AppController會建立<request-url>::POST的 info log;skip API path的 context只包含 Cake解析出的data/form。 - 原始 SNS body完整保存在
push_endpoints_fail_logs.context。
PHP 8 RouterV3.php:183-225 會額外寫 generic request log,並嘗試寫 client_usage_users。AWS request沒有 API key/session;是否保留 generic log與空 key usage side effect需在實作前確認,不能默認當成 Legacy行為。
PHP 8 預計實作邊界
尚未修改程式。實作時至少需要:
- 精確且 anchored route:
^blogs/handle_push_fail\.json$,不可用/blogs/handle*一次涵蓋其他 webhook。 Blogs::handle_push_fail()或專用 webhook endpoint;外部 URL仍維持原路徑。- 不套用一般 API key/session guard。
- 從 request object讀 raw body,保存原始 bytes。
- 新增專用 DB model/method;不可改壞既有
PushLog的push_logs行為。 - 成功回 raw
ok,不可改成{"error":0,...}。
測試與上線驗證
Core:
- 無 API key的合法 SNS payload會 insert一筆,五個欄位值與型別正確,raw context完全一致,response body為
ok。
Other:
- malformed/empty JSON仍 insert,解析欄位為空字串。
- missing/invalid nested
Message。 - method與
.jsonroute parity;錯誤 action不可落到Blogs::index()。 - DB failure/response與 log行為。
- 確認不更新 push endpoint、不新增 queue/activity/notification。
Staging smoke test:
- 先確認 SNS subscription與處理 target。
- 使用專用 Staging topic或經核准的 sanitized payload測試,不向 Production topic發布測試事件。
- 核對 HTTP status、raw body、
x-php-env、新增 DB row及 raw context。 - 確認 AWS SNS沒有因 response差異重試。
Production切流只匹配 /blogs/handle_push_fail.json。PHP 8 parity與 Staging SNS實測完成前,不修改 ALB或 SNS subscription。
尚待確認
- 5 筆 Production 5xx的實際原因。
- Legacy Staging成功 response的完整 headers/content type。
- Staging同時訂閱 Production topic是否為刻意設定。
- Router generic log與
client_usage_users的 migration decision。 - 是否維持 Legacy無 SNS signature/TopicArn驗證;若要強化,必須在 parity完成後另案決定。