更新於 2024.08.27
簡述
使用 Google Apps Script 作為 API 並以 curl 測試卻遇到 HTTP 405 回應。
把以下程式碼佈署為網頁應用程式:
function doGet(request) {
return ContentService.createTextOutput('m-GET').setMimeType(ContentService.MimeType.TEXT);
}
function doPost(request) {
return ContentService.createTextOutput('m-POST').setMimeType(ContentService.MimeType.TEXT);
}
使用瀏覽器、curl <url>
的 GET
請求,或是使用 fetch(<url>, {method: 'POST'})
的 POST
請求都符合預期,唯獨
curl -X POST <url>
收到 HTTP 405 的回應,網頁內容為「很抱歉,目前無法開啟這個檔案。 請檢查地址並再試一次。」
更特別的是同樣為 POST
請求的 curl -d <payloadData> <url>
命令卻能正常運行。
資料說明
結合 Google Apps Script return 405 for POST request by red Heiko Theißen 2022.12.22 //stackoverflow.com 的結論:
- HTTP 302 轉址規範對於第二個請求應具有的方法不明確,請求方法可能從 POST 變更為 GET,如果要避免此情況應使用 HTTP 307。
- HTTP 405 描述請求方法不被允許。 也就是說 Google Apps Script 期望第二個請求是 GET 請求。
-X <method>
選項可能會使curl
固定使用同種方法來請求,尤其凸顯在轉址這種情況。
實測資訊
下列的有無 -X POST
實測資訊差異在第二次的轉址請求方法有所不同。
-X POST
的轉址請求仍使用 POST
方法,而省略後則使用 GET
方法。
另外在有 -X POST
命令的頭一行訊息為「沒有必要使用 -X
或 --request
,POST
已經被推斷出來。」
有 -X POST
的 curl
請求
$ curl https://script.google.com/macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec -L -X POST -d empty --verbose
Note: Unnecessary use of -X or --request, POST is already inferred.
...
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://script.google.com/macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: script.google.com]
* [HTTP/2] [1] [:path: /macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec]
* [HTTP/2] [1] [user-agent: curl/8.9.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-length: 5]
* [HTTP/2] [1] [content-type: application/x-www-form-urlencoded]
> POST /macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec HTTP/2
> Host: script.google.com
> User-Agent: curl/8.9.1
> Accept: */*
> Content-Length: 5
> Content-Type: application/x-www-form-urlencoded
...
< HTTP/2 302
...
* Need to rewind upload for next request
< location: https://script.googleusercontent.com/macros/echo?user_content_key=jbHy1T2JTSqSoV-QWlQdvia41aONdQNnSSfMvG12Ab4Bi0GJNtnvqBnvLGt1Wi67u6ZFO46Izlz6frdZVUAA1Hh09eCeO9spm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB
_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k
...
* Connection #0 to host script.google.com left intact
* Issue another request to this URL: 'https://script.googleusercontent.com/macros/echo?user_content_key=jbHy1T2JTSqSoV-QWlQdvia41aONdQNnSSfMvG12Ab4Bi0GJNtnvqBnvLGt1Wi67u6ZFO46Izlz6frdZVUAA1Hh09eCeO9spm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwb
UTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k'
* Switch from POST to GET
...
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://script.googleusercontent.com/macros/echo?user_content_key=jbHy1T2JTSqSoV-QWlQdvia41aONdQNnSSfMvG12Ab4Bi0GJNtnvqBnvLGt1Wi67u6ZFO46Izlz6frdZVUAA1Hh09eCeO9spm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVH
toRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: script.googleusercontent.com]
* [HTTP/2] [1] [:path: /macros/echo?user_content_key=jbHy1T2JTSqSoV-QWlQdvia41aONdQNnSSfMvG12Ab4Bi0GJNtnvqBnvLGt1Wi67u6ZFO46Izlz6frdZVUAA1Hh09eCeO9spm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_y
xDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k]
* [HTTP/2] [1] [user-agent: curl/8.9.1]
* [HTTP/2] [1] [accept: */*]
> POST /macros/echo?user_content_key=jbHy1T2JTSqSoV-QWlQdvia41aONdQNnSSfMvG12Ab4Bi0GJNtnvqBnvLGt1Wi67u6ZFO46Izlz6frdZVUAA1Hh09eCeO9spm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeI
HPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k HTTP/2
> Host: script.googleusercontent.com
> User-Agent: curl/8.9.1
> Accept: */*
...
< HTTP/2 405
...
省略 -X POST
的 curl
請求
$ curl https://script.google.com/macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec -L -d empty --verbose
...
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://script.google.com/macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: script.google.com]
* [HTTP/2] [1] [:path: /macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec]
* [HTTP/2] [1] [user-agent: curl/8.9.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-length: 5]
* [HTTP/2] [1] [content-type: application/x-www-form-urlencoded]
> POST /macros/s/AKfycbzS1JmQRIynNBjCyBxzfZAJkwSaTl-WtB_r7oERYo6lAHK_0ytbJK0DgYvzMCCvuPy_/exec HTTP/2
> Host: script.google.com
> User-Agent: curl/8.9.1
> Accept: */*
> Content-Length: 5
> Content-Type: application/x-www-form-urlencoded
...
< HTTP/2 302
...
* Need to rewind upload for next request
< location: https://script.googleusercontent.com/macros/echo?user_content_key=O763s5f53pdvw86tT61xFIAZC-6Q2IqFMdx6oyagrUyGvbS_-W0QyxT0XCn7Qvv2p8EsAcY9-Lg3ze1goB6psV0iKyU5UZrgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB
_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k
...
* Connection #0 to host script.google.com left intact
* Issue another request to this URL: 'https://script.googleusercontent.com/macros/echo?user_content_key=O763s5f53pdvw86tT61xFIAZC-6Q2IqFMdx6oyagrUyGvbS_-W0QyxT0XCn7Qvv2p8EsAcY9-Lg3ze1goB6psV0iKyU5UZrgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwb
UTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k'
* Switch from POST to GET
...
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://script.googleusercontent.com/macros/echo?user_content_key=O763s5f53pdvw86tT61xFIAZC-6Q2IqFMdx6oyagrUyGvbS_-W0QyxT0XCn7Qvv2p8EsAcY9-Lg3ze1goB6psV0iKyU5UZrgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVH
toRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: script.googleusercontent.com]
* [HTTP/2] [1] [:path: /macros/echo?user_content_key=O763s5f53pdvw86tT61xFIAZC-6Q2IqFMdx6oyagrUyGvbS_-W0QyxT0XCn7Qvv2p8EsAcY9-Lg3ze1goB6psV0iKyU5UZrgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_y
xDwkD9yNh6ZAsfeIHPMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k]
* [HTTP/2] [1] [user-agent: curl/8.9.1]
* [HTTP/2] [1] [accept: */*]
> GET /macros/echo?user_content_key=O763s5f53pdvw86tT61xFIAZC-6Q2IqFMdx6oyagrUyGvbS_-W0QyxT0XCn7Qvv2p8EsAcY9-Lg3ze1goB6psV0iKyU5UZrgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnHVE4_e-RUbwHtpEOeIEbwbUTYVHtoRTW3T9OdEXQkwB6tnCB_Gd4q9TzbcbpHObGvR-P0Zh_yxDwkD9yNh6ZAsfeIH
PMWxMIw&lib=MNfocbcpbdSNZeZrz997BmaJum2dZ3P_k HTTP/2
> Host: script.googleusercontent.com
> User-Agent: curl/8.9.1
> Accept: */*
...
< HTTP/2 200
...