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

環境 endpointTopic
ProductionPushNotificationFail
StagingPushNotificationFail-staging
StagingPushNotificationFail

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.php
    • handle_push_fail():208-235。
    • 讀 raw body/outer JSON:210-211。
    • Subject、nested Message:213-223。
    • DB insert:225-233。
    • response:234。
  • app/Controller/AppController.php
    • JSON API guard:145-181。
    • handle_push_fail.json skip API key:155-177。
  • app/Model/PushLog.php:2-24
    • action 只借 Cake model connection 執行 raw SQL;PushLog 本身的 table/addCount() 不是本 API 邏輯。

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
  • Lib/Common/RouterRule/File.php:58-75
    • method 不存在時不會選到 handle_push_fail,因此現況會錯落到 Blogs::index()
  • Endpoint/V1/Blogs.php:24-44
    • index() 要求 API key且回 blog list,完全不符合 webhook contract。
  • Lib/Common/Request/RequestHttp.php:18-24,55-58
    • 已保存 raw request body,可由 getBody() 取得原始 bytes。
  • Lib/Model/PushLog.php:16-35
    • 對應 push_logs,不可為本 API 直接改 shared table behavior;後續應新增專用 model/method。

Guard 與 request

項目Legacy 行為
URL/blogs/handle_push_fail.json
正式 methodPOST
method guardaction 內沒有;Production 只觀察到 POST
API key不要求
user session不要求
SNS signature不驗證
TopicArn allowlist不驗證
body直接保存 php://input 原始內容

預期 SNS shape:

{
  "Subject": "<subject>",
  "Message": "{\"EndpointArn\":\"<endpoint>\",\"FailureType\":\"<type>\"}"
}

解析規則:

  1. Outer body 用 associative JSON decode。
  2. Subject 不存在時用空字串。
  3. Message 存在時再做一次 associative JSON decode。
  4. Nested EndpointArnFailureType 不存在或 decode 失敗時用空字串。
  5. 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、DBFactorySHOW COLUMNS 確認。

欄位型別寫入來源
idint auto incrementDB
createddatetime nullableSQL NOW()
endpointvarchar(255) nullablenested Message.EndpointArn,缺少時 ""
subjectvarchar(255) nullableouter Subject,缺少時 ""
failure_typevarchar(255) nullablenested Message.FailureType,缺少時 ""
contexttext nullableraw 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_endpointspush_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解析出的 dataform
  • 原始 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 預計實作邊界

尚未修改程式。實作時至少需要:

  1. 精確且 anchored route:^blogs/handle_push_fail\.json$,不可用 /blogs/handle* 一次涵蓋其他 webhook。
  2. Blogs::handle_push_fail() 或專用 webhook endpoint;外部 URL仍維持原路徑。
  3. 不套用一般 API key/session guard。
  4. 從 request object讀 raw body,保存原始 bytes。
  5. 新增專用 DB model/method;不可改壞既有 PushLogpush_logs 行為。
  6. 成功回 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與 .json route parity;錯誤 action不可落到 Blogs::index()
  • DB failure/response與 log行為。
  • 確認不更新 push endpoint、不新增 queue/activity/notification。

Staging smoke test:

  1. 先確認 SNS subscription與處理 target。
  2. 使用專用 Staging topic或經核准的 sanitized payload測試,不向 Production topic發布測試事件。
  3. 核對 HTTP status、raw body、x-php-env、新增 DB row及 raw context。
  4. 確認 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完成後另案決定。