LESSON-26 · 自主延伸

Apps Script form-encoded 本地測試串接

把「Apps Script form-encoded 本地測試串接」拆成 4 個可獨立完成、可重設、可驗證的自主學習 module。

1. 現在要做什麼

成果:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

需要的檔案:files/starter/index.html(整合起點,保留跨 module 的最小缺口。)

完成後:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

0 / 8 個操作已記錄

2. 先備自我檢查

逐項展開;答不出來時先走補救連結,不必硬做。

已完成本章基礎練習並能找到 Starter。

自我檢查:我能否指出本章 Starter 入口與唯一 TODO?

不足時補救:回到本章 README,先完成基礎練習與入口導覽。

能開啟本機 HTML 並讀取至少一種瀏覽器證據。

自我檢查:我能否開啟 Elements、Console 或 Network 並找到本頁?

不足時補救:先開 DevTools,重新整理一次,確認目前文件與第一筆 request。

3. 完整學習目標與 Learning Map

每一個目標只對應一個 module;成功狀態必須能指出證據。

能把 name、email、message 編碼為表單欄位,並說明 Apps Script 為何可由 e.parameter 讀到同名值。

為什麼重要
若前端送 JSON、後端卻讀 e.parameter,欄位會全部變成空值;名稱不一致也會造成同樣症狀。
完成條件
只使用本頁 payload 執行 URLSearchParams,本 Lab 不呼叫 Apps Script URL。
成功狀態
operation=encode-form、inputCount=3、remoteRequests=0,result 同時包含 name、email、message 的百分比編碼。
證據位置
inputCount=3、remoteRequests=0、result 含 name=、email=、message=

能以四組本地案例分辨必填、Email 格式、honeypot 與有效資料,並確認公開前端不含 secret。

為什麼重要
前端驗證可被關閉或繞過;接收端若不重驗,垃圾資料與惡意輸入仍會進入後續流程。
完成條件
只讀取四筆預先建立的本地 validation fixture,不送出、不中繼任何真實個資。
成功狀態
count=4、uniqueCount=4、valid=true,result 分別為 required、email、honeypot、accepted。
證據位置
count=4、uniqueCount=4、text=required|email|honeypot|accepted

能從本地對照表說明 /dev 與 /exec 的用途、執行身分,以及 c、sid 保留參數風險。

為什麼重要
把 editor 測試 URL 當正式入口會讓非編輯者無法使用;錯誤執行身分也會改變資料權限與責任。
完成條件
只檢查本頁 metadata,不開啟或請求任何 script.google.com URL。
成功狀態
count=3、uniqueCount=3、valid=true,三筆 kind 為 dev、exec、reserved。
證據位置
count=3、uniqueCount=3、text=dev|exec|reserved;本 action 無 Network request

能檢查本地成功/失敗 response 都符合 `{ ok, message, error? }`,並說明正式 fetch 必須允許 redirect。

為什麼重要
成功與錯誤若回傳不同型態,前端會散落多套判斷;忽略 ContentService redirect 也可能誤判最終 response。
完成條件
只讀取兩筆本地 JSON fixture,不呼叫 ContentService 或 Google redirect URL。
成功狀態
count=2、uniqueCount=2、valid=true,兩筆 schema 為 ok-message 與 ok-message-error。
證據位置
count=2、uniqueCount=2、text=ok-message|ok-message-error;沒有外部 request

4. 4 個知識模組與 Micro Labs

依序展開;每次只改一項,操作後確認該模組自己的 DOM、progress 與 evidence。

1URLSearchParams 與 e.parameter

完整解釋

白話:前端把三個有名字的欄位排成表單字串,後端用相同名字逐格取回。

正式說法:`URLSearchParams` 產生 `application/x-www-form-urlencoded` 形式的 name/value pairs;Apps Script web app 的 event object 會將單值參數提供於 `e.parameter`。

何時使用:原生表單以 fetch 送到 Apps Script,且 doPost 使用 e.parameter 接收時。

修改前後:Before:request body 是 JSON,但 doPost 查 e.parameter.email。After:body 與接收端都使用同一組表單欄名。

常見混淆:`e.parameters` 的值是陣列,`e.parameter` 取每個參數的第一個值;本章單值表單使用後者。

名詞與最小範例

URLSearchParams 與 e.parameter Form-encoded request and event parameters

白話
前端把三個有名字的欄位排成表單字串,後端用相同名字逐格取回。
正式定義
`URLSearchParams` 產生 `application/x-www-form-urlencoded` 形式的 name/value pairs;Apps Script web app 的 event object 會將單值參數提供於 `e.parameter`。
何時使用
原生表單以 fetch 送到 Apps Script,且 doPost 使用 e.parameter 接收時。
最小範例
`new URLSearchParams({ name: '小安', email: 'an@example.test', message: '想詢價' })`
驗收證據
inputCount=3、remoteRequests=0、result 含 name=、email=、message=
常混淆
`e.parameters` 的值是陣列,`e.parameter` 取每個參數的第一個值;本章單值表單使用後者。

Micro Lab|URLSearchParams 與 e.parameter

情境:前端 Network 看得到 JSON body,但 Apps Script 回覆『Email 必填』。

起始狀態:三個測試欄位只存在記憶體物件,尚未編碼。

  1. 在本地把三欄 payload 編碼,讀取 result 並確認沒有任何遠端 request。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:URLSearchParams 與 e.parameter

尚未執行
尚未執行;DOM data-state=starter。

預期:operation=encode-form、inputCount=3、remoteRequests=0,result 同時包含 name、email、message 的百分比編碼。

證據:inputCount=3、remoteRequests=0、result 含 name=、email=、message=

第一個檢查:先比較 request Content-Type/body 與 doPost 實際讀取方式是否相同。

重設:丟棄編碼 result 並恢復本地 payload;不觸碰測試或正式 deployment。

卡住時

  • 症狀:doPost 收到 request,e.parameter 卻沒有欄位。
    可能原因:前端用 JSON.stringify,或 input 缺 name/前後端欄名不同。
    先檢查:在本地先列出 URLSearchParams keys,再對照 doPost 讀取名稱。
    修正:統一使用 form-encoded payload 與 name/email/message 三個契約欄位。

理解檢查

doPost 使用 e.parameter.email 時,前端最直接應送什麼?

尚未作答。

查看解釋

接收端與 request encoding、欄名必須是同一份契約。

本模組官方來源:Google Apps Script|Web Apps

2前後端雙重驗證、honeypot 與無前端秘密

完整解釋

白話:瀏覽器先幫使用者抓錯,伺服端再把所有資料當成不可信重新檢查;藏起來的誘餌欄有人填就拒絕。

正式說法:Defense in depth 將 UX constraint validation 與 server-side authorization/validation 分層;honeypot 是正常使用者不應填寫、用來辨識自動化提交的欄位。

何時使用:任何公開聯絡、詢價或訂閱表單。

修改前後:Before:只依 required 屬性,直接相信 request。After:UI 與 doPost 各自驗證,honeypot 非空立即拒絕。

常見混淆:Honeypot 只能降低部分機器提交,不是 CAPTCHA、rate limit 或完整安全邊界。

名詞與最小範例

前後端雙重驗證、honeypot 與無前端秘密 Defense-in-depth validation and honeypot

白話
瀏覽器先幫使用者抓錯,伺服端再把所有資料當成不可信重新檢查;藏起來的誘餌欄有人填就拒絕。
正式定義
Defense in depth 將 UX constraint validation 與 server-side authorization/validation 分層;honeypot 是正常使用者不應填寫、用來辨識自動化提交的欄位。
何時使用
任何公開聯絡、詢價或訂閱表單。
最小範例
`if (String(e.parameter.website || '').trim()) return fail('spam')`,秘密只放 Script Properties。
驗收證據
count=4、uniqueCount=4、text=required|email|honeypot|accepted
常混淆
Honeypot 只能降低部分機器提交,不是 CAPTCHA、rate limit 或完整安全邊界。

Micro Lab|前後端雙重驗證、honeypot 與無前端秘密

情境:有人直接組 request 繞過 required,或把 API key 寫在公開 app.js。

起始狀態:四組去識別測試案例已存在 DOM,尚未執行分支盤點。

  1. 讀取四個本地案例的 data-result,確認每條驗證分支都有唯一、可辨識結果。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:前後端雙重驗證、honeypot 與無前端秘密

  1. 姓名空白
  2. Email 格式錯誤
  3. website 誘餌有值
  4. 有效去識別資料
尚未執行
尚未執行;DOM data-state=starter。

預期:count=4、uniqueCount=4、valid=true,result 分別為 required、email、honeypot、accepted。

證據:count=4、uniqueCount=4、text=required|email|honeypot|accepted

第一個檢查:先查看後端是否在寫入前獨立檢查每個欄位與 honeypot。

重設:清除盤點 facts;不保留任何學生輸入。

卡住時

  • 症狀:瀏覽器顯示驗證通過,測試 Sheet 仍出現空值或垃圾資料。
    可能原因:只做前端 constraint validation,doPost 沒有重驗或 honeypot 判斷。
    先檢查:逐一追蹤四組 fixture 是否在寫入前得到不同 result。
    修正:先建立接收端 validation function,再把 append 放在 accepted 分支。

理解檢查

為什麼前端 required 不能取代 doPost 驗證?

尚未作答。

查看解釋

伺服端必須把外部輸入視為不可信,獨立執行驗證。

本模組官方來源:Google Apps Script|Properties Service

3/dev、/exec、部署權限與保留參數

完整解釋

白話:/dev 是編輯者測最新版的門;/exec 是已部署版本的門。能不能進、用誰的權限,必須在部署設定確認。

正式說法:Apps Script test deployment `/dev` 僅對具編輯權限者提供最新儲存程式;versioned web app `/exec` 依部署版本、execute-as 與 access 設定執行。

何時使用:建立測試部署、交付部署 URL、排查 401/403 或 event parameter 異常。

修改前後:Before:把兩種 URL 混用並猜測權限。After:在部署紀錄明確標示用途、版本、execute-as、access。

常見混淆:`/dev` 不是 staging 主機,也不是可以公開分享的正式 URL。

名詞與最小範例

/dev、/exec、部署權限與保留參數 Test deployment, deployed URL, and reserved parameters

白話
/dev 是編輯者測最新版的門;/exec 是已部署版本的門。能不能進、用誰的權限,必須在部署設定確認。
正式定義
Apps Script test deployment `/dev` 僅對具編輯權限者提供最新儲存程式;versioned web app `/exec` 依部署版本、execute-as 與 access 設定執行。
何時使用
建立測試部署、交付部署 URL、排查 401/403 或 event parameter 異常。
最小範例
部署紀錄:`kind=exec, version=v3, executeAs=owner, access=approved testers`。
驗收證據
count=3、uniqueCount=3、text=dev|exec|reserved;本 action 無 Network request
常混淆
`/dev` 不是 staging 主機,也不是可以公開分享的正式 URL。

Micro Lab|/dev、/exec、部署權限與保留參數

情境:講師可以用 /dev,學生打開同一網址卻看到未授權;表單又帶了保留欄名 sid。

起始狀態:本地規則表已列出三種情況,沒有真實 deployment URL。

  1. 讀取三筆本地部署規則的 data-kind,確認 dev、exec、reserved 各自只有一筆。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:/dev、/exec、部署權限與保留參數

僅編輯者測試最新程式
已部署版本與核准 access
欄位避免 c 與 sid
尚未執行
尚未執行;DOM data-state=starter。

預期:count=3、uniqueCount=3、valid=true,三筆 kind 為 dev、exec、reserved。

證據:count=3、uniqueCount=3、text=dev|exec|reserved;本 action 無 Network request

第一個檢查:先看 URL 結尾,再查部署設定的 execute-as 與 access,而不是反覆重送。

重設:清除規則 facts;不變更任何 Apps Script 部署與權限。

卡住時

  • 症狀:擁有者測試成功,其他測試者卻無法開啟。
    可能原因:分享了 /dev,或 access/execute-as 與預期不符。
    先檢查:記錄 URL kind、部署 ID、版本、execute-as、access 四項。
    修正:在核准的測試部署重新確認權限;不要把 /dev 當公開入口。

理解檢查

哪個網址只供具編輯權限者測最新儲存程式?

尚未作答。

查看解釋

Google 官方文件將 /dev 定義為 test deployment 的開發測試入口。

本模組官方來源:Google Apps Script|Web Apps

4ContentService redirect 與單一 JSON 契約

完整解釋

白話:不管成功失敗都用同一個盒子回答;真正連線時記得內容可能先被轉到一次性網址。

正式說法:Response contract 定義可預期 JSON shape;Apps Script ContentService 內容會重新導向至 `script.googleusercontent.com`,HTTP client 必須跟隨 redirect。

何時使用:doPost 回傳 JSON、前端分類 success/error,以及以 Network 排查 ContentService。

修改前後:Before:成功回字串、錯誤回 HTML。After:兩者皆為 JSON,差異只在 ok 與可選 error。

常見混淆:HTTP redirect 是傳輸路徑;`ok` 是應用程式結果,不能只靠 200 判斷提交成功。

名詞與最小範例

ContentService redirect 與單一 JSON 契約 Content Service redirect and JSON response contract

白話
不管成功失敗都用同一個盒子回答;真正連線時記得內容可能先被轉到一次性網址。
正式定義
Response contract 定義可預期 JSON shape;Apps Script ContentService 內容會重新導向至 `script.googleusercontent.com`,HTTP client 必須跟隨 redirect。
何時使用
doPost 回傳 JSON、前端分類 success/error,以及以 Network 排查 ContentService。
最小範例
`{ ok: false, message: '請檢查欄位', error: 'INVALID_EMAIL' }`
驗收證據
count=2、uniqueCount=2、text=ok-message|ok-message-error;沒有外部 request
常混淆
HTTP redirect 是傳輸路徑;`ok` 是應用程式結果,不能只靠 200 判斷提交成功。

Micro Lab|ContentService redirect 與單一 JSON 契約

情境:Network 最終得到 200,但 JSON 的 ok=false,介面卻顯示送出成功。

起始狀態:兩筆去識別 response fixture 已存在,尚未核對 schema。

  1. 讀取兩筆本地 response schema,確認共用 ok/message,只有失敗案例包含 error。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:ContentService redirect 與單一 JSON 契約

{"ok":true,"message":"已收到"}
{"ok":false,"message":"請檢查欄位","error":"INVALID_EMAIL"}
尚未執行
尚未執行;DOM data-state=starter。

預期:count=2、uniqueCount=2、valid=true,兩筆 schema 為 ok-message 與 ok-message-error。

證據:count=2、uniqueCount=2、text=ok-message|ok-message-error;沒有外部 request

第一個檢查:先分開檢查 HTTP 狀態、final URL、JSON parse、body.ok 四層。

重設:清除 schema facts;不保留 response 或呼叫正式服務。

卡住時

  • 症狀:fetch 沒丟例外,UI 卻把驗證失敗當成成功。
    可能原因:只看 HTTP fulfilled,沒有 parse JSON 與檢查 body.ok。
    先檢查:依序記錄 response.redirected、response.url、parsed body、body.ok。
    修正:集中一個 parser 驗證 `{ok,message,error?}`,再依 ok 更新狀態。

理解檢查

HTTP 200 是否保證 Apps Script 表單成功?

尚未作答。

查看解釋

傳輸成功與應用程式驗證結果是兩個不同層次。

本模組官方來源:Google Apps Script|Content Service

5. Apps Script form-encoded 本地測試串接|整合 Lab

情境:在同一份章節作品中整合 4 個 module,排除單項通過但組合失敗的問題。

交付:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

尚未執行整合步驟。
  1. 1觀察整合 Starter 並找出第一個未通過 module。

    檔案:files/starter/index.html 定位:#observe

    操作:逐項讀取 outcomes,執行一次現況操作。

    // OBSERVE:不修改程式,先記錄第一個失敗 moduleId。

    預期:能指出第一個未通過 module。

    證據:第一個 moduleId 與缺少的 evidence。

    原因:先觀察可避免未重現就直接改答案。

    卡住先查:確認開啟的是 files/starter/index.html。

  2. 2依 module 順序修改,一次只加入一個責任。

    檔案:files/starter/index.html 定位:#modify

    操作:在本地把三欄 payload 編碼,讀取 result 並確認沒有任何遠端 request。 → 讀取四個本地案例的 data-result,確認每條驗證分支都有唯一、可辨識結果。 → 讀取三筆本地部署規則的 data-kind,確認 dev、exec、reserved 各自只有一筆。 → 讀取兩筆本地 response schema,確認共用 ok/message,只有失敗案例包含 error。

    // 26-01-urlsearchparams-e-parameter: 在本地把三欄 payload 編碼,讀取 result 並確認沒有任何遠端 request。
    // 26-02-honeypot: 讀取四個本地案例的 data-result,確認每條驗證分支都有唯一、可辨識結果。
    // 26-03-dev-exec: 讀取三筆本地部署規則的 data-kind,確認 dev、exec、reserved 各自只有一筆。
    // 26-04-contentservice-redirect-json: 讀取兩筆本地 response schema,確認共用 ok/message,只有失敗案例包含 error。

    預期:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

    證據:inputCount=3、remoteRequests=0、result 含 name=、email=、message=;count=4、uniqueCount=4、text=required|email|honeypot|accepted;count=3、uniqueCount=3、text=dev|exec|reserved;本 action 無 Network request;count=2、uniqueCount=2、text=ok-message|ok-message-error;沒有外部 request

    原因:逐項整合能把回歸定位到明確 module。

    卡住先查:回到第一個未通過的 module,只修該處。

  3. 3驗證所有 module 在整合後仍成立。

    檔案:files/solution/index.html 定位:#verify

    操作:重新整理並依 outcomes 順序重跑所有證據。

    // VERIFY:逐項記錄 pass/fail,不以看到畫面代替 evidence。

    預期:4/4 module 通過。

    證據:每個 moduleId 都有實際結果與重設後重現紀錄。

    原因:單項通過不代表整合後沒有 selector、id 或狀態衝突。

    卡住先查:若失敗,回到第一個失敗 module,不同時修多項。

  4. 4把本章責任轉用到不同內容情境。

    檔案:files/starter/index.html 定位:#challenge

    操作:替另一個商家情境重做,仍達成:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

    // CHALLENGE:只替換內容與資料,不新增框架或跳過 self-check。

    預期:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

    證據:轉用後的作品與全數 module self-check。

    原因:能轉用才代表理解責任,而非只記住原範例。

    卡住先查:先確認共同基底與 module id 未被改壞。

重設方法:重新載入 files/starter/index.html;若 localStorage 不可用,使用頁面內記憶狀態並提供重設按鈕。

6. 自學挑戰與三層提示

挑戰:將同一組責任轉用到另一個商家情境,仍達成:完成 form-encoded、雙重驗證、部署邊界與單一 JSON contract 的本地串接設計;自學頁全程 remote write=0。

限制:不新增框架。;保留 module id 與驗收證據。

提交證據:提交可直接開啟的作品與逐項 self-check。

提示 1|方向提示

拆小:在「Apps Script form-encoded 本地測試串接」先完成第一個尚未通過的 module:URLSearchParams 與 e.parameter,不同時修改其他責任。

提示 2|關鍵片段

串接:依 26-01-urlsearchparams-e-parameter → 26-02-honeypot → 26-03-dev-exec → 26-04-contentservice-redirect-json 核對各自輸入與證據。

提示 3|完整解答與原因

完整解答與原因:依序執行 在本地把三欄 payload 編碼,讀取 result 並確認沒有任何遠端 request。 → 讀取四個本地案例的 data-result,確認每條驗證分支都有唯一、可辨識結果。 → 讀取三筆本地部署規則的 data-kind,確認 dev、exec、reserved 各自只有一筆。 → 讀取兩筆本地 response schema,確認共用 ok/message,只有失敗案例包含 error。;這個順序能把每個結果對回單一 module,避免整合後無法定位。

完整解答預設收合;先留下自己的嘗試,再開第三層。

7. 症狀式除錯指南

8. 理解測驗、驗收證據與下一步

哪個狀態代表 26-01-urlsearchparams-e-parameter 完成?

尚未作答。

查看解釋

若前端送 JSON、後端卻讀 e.parameter,欄位會全部變成空值;名稱不一致也會造成同樣症狀。

哪個狀態代表 26-02-honeypot 完成?

尚未作答。

查看解釋

前端驗證可被關閉或繞過;接收端若不重驗,垃圾資料與惡意輸入仍會進入後續流程。

哪個狀態代表 26-03-dev-exec 完成?

尚未作答。

查看解釋

把 editor 測試 URL 當正式入口會讓非編輯者無法使用;錯誤執行身分也會改變資料權限與責任。

哪個狀態代表 26-04-contentservice-redirect-json 完成?

尚未作答。

查看解釋

成功與錯誤若回傳不同型態,前端會散落多套判斷;忽略 ContentService redirect 也可能誤判最終 response。

驗收清單

官方來源

回到課程地圖選擇下一章