DEV Community

codemee
codemee

Posted on

Apps Script Web Apps 回應文字內容的轉址問題

Google 的 Apps Script 是個很不錯的服務,很方便就可以建立後端應用,不過如果回應純文字內容,就會因為安全考量而改用轉址方式傳回真正的文字。底下以我所撰寫從 Yahoo 股市新聞擷取新聞標題與摘要的簡易服務為例說明,這個服務可以透過網址尾端的查詢字串加入兩個參數:

參數名稱 用途
topic 必要參數,傳入 "news" 表示擷取標題,會用 JSON 陣列傳回標題字串;傳入 "news_detail" 則是擷取標題與摘要,會以 JSON 陣列傳回物件,每個物件內含 titledescription 屬性記錄標題與摘要
num 新聞筆數,如果沒有指定,預設為 10 筆

使用 curl 觀察

我們可以最方便的 curl 工具觀察:

curl -i "https://script.google.com/macros/s/AKfycbyA8mGQqVpc9CLd3FH0Zp8Vp9zpCfNpS2HeK99YW3kiAJEc8d_oRBZlFjTn9bx1L5Cl/exec?topic=news&num=1"
Enter fullscreen mode Exit fullscreen mode

結果如下:

HTTP/2 302 
content-type: text/html; charset=UTF-8
access-control-allow-origin: *
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Sat, 12 Apr 2025 02:53:14 GMT
location: https://script.googleusercontent.com/macros/echo?user_content_key=AehSKLjVgOxSMUs1O_P0daWDd58ucki0Mg0mvSceJNN0RIM7k6iriCi_Sy1nsu8Jc5JIK-xmpxBekGXJ-_W-KG0ps9cs7PUclTP5eBBtmIeP18UUe5T4R0Oc6q15d4vG2H4w6gtBVTe4UMlbmURZyx8w3j2MfB1PsSc77QSCNqnKZ5zFlyjnxI-1tybILXgpeTuvYCR01iMcTCwNJUcPdk78aQ3IX3feeKO4Co9bZf8nfTu6ytuy849A3rin6m6Bw_n4Rwj5as8NacK-pOwECGvW2Ixei7xpT3wZIvWlS8_BzqmlvMvcblQ8AfjNIplpcA&lib=M1YLor6O1PntGCCrJMyICgDA3Wcg5FMSu
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self'
x-xss-protection: 1; mode=block
server: GSE
accept-ranges: none
vary: Accept-Encoding

<HTML>
<HEAD>
<TITLE>Moved Temporarily</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<!-- GSE Default Error -->
<H1>Moved Temporarily</H1>
The document has moved <A HREF="https://script.googleusercontent.com/macros/echo?user_content_key=AehSKLjVgOxSMUs1O_P0daWDd58ucki0Mg0mvSceJNN0RIM7k6iriCi_Sy1nsu8Jc5JIK-xmpxBekGXJ-_W-KG0ps9cs7PUclTP5eBBtmIeP18UUe5T4R0Oc6q15d4vG2H4w6gtBVTe4UMlbmURZyx8w3j2MfB1PsSc77QSCNqnKZ5zFlyjnxI-1tybILXgpeTuvYCR01iMcTCwNJUcPdk78aQ3IX3feeKO4Co9bZf8nfTu6ytuy849A3rin6m6Bw_n4Rwj5as8NacK-pOwECGvW2Ixei7xpT3wZIvWlS8_BzqmlvMvcblQ8AfjNIplpcA&amp;lib=M1YLor6O1PntGCCrJMyICgDA3Wcg5FMSu">here</A>.
</BODY>
</HTML>
Enter fullscreen mode Exit fullscreen mode

可以看到回應 302 狀態碼,表示網頁已搬移,並且在 Location 表頭以及回覆內容中都有註明搬遷的新網址。

如果使用 -L 讓 curl 跟隨新網址轉址,就可以取得真正的結果:

curl -i -L "https://script.google.com/macros/s/AKfycbyA8mGQqVpc9CLd3FH0Zp8Vp9zpCfNpS2HeK99YW3kiAJEc8d_oRBZlFjTn9bx1L5Cl/exec?topic=news&num=1"
Enter fullscreen mode Exit fullscreen mode

結果如下:

HTTP/2 302 
content-type: text/html; charset=UTF-8
access-control-allow-origin: *
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Sat, 12 Apr 2025 02:08:29 GMT
location: https://script.googleusercontent.com/macros/echo?user_content_key=AehSKLi-hIwVxJfg-121RHpjMNfukz0wd_LqlUr-40Apl6eH_OUdonYOgNBLsXm_ezL3MeCDFYMm5dytM7a1BVmlaX732wWiVAe6bBXdU6WugzbewnenV7K-sjspO_-oxcKzzcQhomudwC9GKrrixSMBlU5CLQG6wIgziidspL_ayDaSZfekw_Lqexodg0yz7E829G8RyY5KOhGxfgWDmorATU5aZs2Cflv-mpDFE_WhNfPzlc_-sA8e2OoCo05T5Xxe46T33xk6BB7zjZTlpQ-UXPDf0veLLjY46-fF58nD85dEjG8I8oNize4mLkNKCA&lib=M1YLor6O1PntGCCrJMyICgDA3Wcg5FMSu
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self'
x-xss-protection: 1; mode=block
server: GSE
accept-ranges: none
vary: Accept-Encoding

HTTP/2 200 
content-type: application/json; charset=utf-8
x-content-type-options: nosniff
access-control-allow-origin: *
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Sat, 12 Apr 2025 02:08:29 GMT
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self'
x-xss-protection: 1; mode=block
server: GSE
accept-ranges: none
vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site,Accept-Encoding

["外資回補305億今年次多 大盤周跌1769點"]
Enter fullscreen mode Exit fullscreen mode

你可以清楚看到第一次得到回覆 302 後,就接著連網新網址,因此得到回覆 200,並取得新聞標題。

Apps Script 的轉址還有時間限制

要注意的是,Apps Script 的轉址設定有時間限制,如果沒有立刻連往新網址,這個新網址也會失效(目前沒有找到正式的官方文件明確表示限制時間的長度,我自己實測是 30 秒)。底下以 Python 示範:

import requests
url = 'https://script.google.com/macros/s/AKfycbyA8mGQqVpc9CLd3FH0Zp8Vp9zpCfNpS2HeK99YW3kiAJEc8d_oRBZlFjTn9bx1L5Cl/exec?topic=news&num=1'
Enter fullscreen mode Exit fullscreen mode

首先利用 requests.get 並停用自動轉址:

r = requests.get(
    url,
    allow_redirects=False
)
print(r.status_code)
print(r.headers["Location"])
Enter fullscreen mode Exit fullscreen mode

可以得到如下的結果

302
https://script.googleusercontent.com/macros/echo?user_content_key=AehSKLilc0B7RGQPA7hqmbrSW9XYGSWu6ldPdTL0LzR70fXtlNd5WBi0m8eYWV0E70eJkztTRwFZfoTFlxyDN0VLqhk_eqsxw9y57-mMXmcZ9XQQpetX4pC-fHAmisEuVUT929XMcUtDq3FNqRuyUhLFVdJbNv2RgDQWSLLYNDixCjTqPcdv0rGUHmQgAotkeKHP0aCptNGJTnYAQdxMnwXB_-uu7n2K3yUNknQsqGniZJQiUYKDmrfXwksGmf2M4v0o9RyYYM_ur0vn3o853l4dp4iTbM3QkDxp47s7tKq-ExT99rdD_tcxdGF46ZcnYA&lib=M1YLor6O1PntGCCrJMyICgDA3Wcg5FMSu
Enter fullscreen mode Exit fullscreen mode

如同剛剛使用 curl 觀察到的結果,並可以取得搬移後的新網址,如果沒有立刻循該網址連接過去,而是過了一段時間才連接:

r = requests.get(
    r.headers["Location"],
    allow_redirects=False
)

print(r.status_code)
print(r.text)
Enter fullscreen mode Exit fullscreen mode

就會再次得到 302 回覆:

302
https://script.google.com/macros/s/AKfycbyA8mGQqVpc9CLd3FH0Zp8Vp9zpCfNpS2HeK99YW3kiAJEc8d_oRBZlFjTn9bx1L5Cl/exec?topic=news&num=1
Enter fullscreen mode Exit fullscreen mode

如果你注意看,會發現它又轉回最原本的網址,所以如果繼續循這個新網址,就會再重複剛剛的轉址流程了。

如果在適當的時間內連接新網址,就可以取得正確結果:

r = requests.get(
    url,
    allow_redirects=False
)

r = requests.get(
    r.headers["Location"],
    allow_redirects=False
)

print(r.status_code)
print(r.text)
Enter fullscreen mode Exit fullscreen mode

結果如下:

200
["外資回補305億今年次多 大盤周跌1769點"]
Enter fullscreen mode Exit fullscreen mode

如果要透過程式取用使用 Apps Script 架設的服務或是 API,就要記得處理轉址。舉例來說,在較早版本的 MicroPython 韌體中,它的 urequests 模組就沒有自動轉址的功能,可參考〈讓 MicroPython 的 urequests 模組支援 redirection〉一文。

Apps Script 是轉址到不同的主機(host)

另外,要注意的是,Apps Script 服務的網址是:

https://script.google.com/....
Enter fullscreen mode Exit fullscreen mode

但是轉址後的網址是:

https://script.googleusercontent.com/...
Enter fullscreen mode Exit fullscreen mode

兩者的主機(Host)名稱不同,有些程式庫在轉址時會沿用原本網址的主機名稱,就會造成問題,例如若我們強制這樣處理:

r = requests.get(
    url,
    allow_redirects=False
)

r = requests.get(
    r.headers["Location"],
    headers={
        "Host": "script.google.com"
    },
    allow_redirects=False
)

print(r.status_code)
print(r.text)
Enter fullscreen mode Exit fullscreen mode

就會因為在錯誤的主機上找不到對應的路徑而得到 404 回覆:

404
<!DOCTYPE html><html lang="en"><head><meta name="description" content="Web word processing, presentations and spreadsheets"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"><link rel="shortcut icon" href="//docs.google.com/favicon.ico"><title>Page Not Found</title><meta name="referrer" content="origin"><link href="//fonts.googleapis.com/css?family=Product+Sans" rel="stylesheet" type="text/css" nonce="h7bg36YtnBWCE1gbM9zxvQ"><style nonce="h7bg36YtnBWCE1gbM9zxvQ">.goog-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}#drive-logo{margin:18px 0;position:absolute;white-space:nowrap}.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png);-webkit-background-size:116px 41px;background-size:116px 41px;display:inline-block;height:41px;vertical-align:bottom;width:116px}.docs-drivelogo-text{color:#000;display:inline-block;opacity:.54;text-decoration:none;font-family:"Product Sans",Arial,Helvetica,sans-serif;font-size:32px;text-rendering:optimizeLegibility;position:relative;top:-6px;left:-7px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:144dpi){.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_116x41dp.png)}}sentinel{}</style><style type="text/css" nonce="h7bg36YtnBWCE1gbM9zxvQ">body {background-color: #fff; font-family: Arial,sans-serif; font-size: 13px; margin: 0; padding: 0;}a, a:link, a:visited {color: #112ABB;}</style><style type="text/css" nonce="h7bg36YtnBWCE1gbM9zxvQ">.errorMessage {font-size: 12pt; font-weight: bold; line-height: 150%;}</style></head><body><div id="outerContainer"><div id="innerContainer"><div style="position: absolute; top: -80px;"><div id="drive-logo"><a href="/"><span class="docs-drivelogo-img" title="Google logo"></span><span class="docs-drivelogo-text">&nbsp;Drive</span></a></div></div><div align="center"><p class="errorMessage" style="padding-top: 50px">Sorry, unable to open the file at this time.</p><p> Please check the address and try again. </p><div style="background: #F0F6FF; border: 1px solid black; margin-top: 35px; padding: 10px 125px; width: 300px;"><p><strong>Get stuff done with Google Drive</strong></p><p>Apps in Google Drive make it easy to create, store and share online documents, spreadsheets, presentations and more.</p><p>Learn more at <a href="https://drive.google.com/start/apps">drive.google.com/start/apps</a>.</p></div></div></div></div></body><style nonce="h7bg36YtnBWCE1gbM9zxvQ">html {height: 100%; overflow: auto;}body {height: 100%; overflow: auto;}#outerContainer {margin: auto; max-width: 750px;}#innerContainer {margin-bottom: 20px; margin-left: 40px; margin-right: 40px; margin-top: 80px; position: relative;}</style></html>
Enter fullscreen mode Exit fullscreen mode

這個問題在 MicroPython 1.24 的 requests 會出現,可參考〈MicroPython 1.24.0 的 requests 重新導向的問題〉一文,我已經提出 PR 修正,應該在之後的版本就會更新了。

Top comments (0)