---
tags: Blue 的學習紀錄, Heroku
---
# Heroku 除錯記錄:H18 Server Request Interrupted
:::danger
問題沒有被解決,伺服器換成 Linode 後,一模一樣的程式碼就沒有問題了。猜測是 Heroku 的 middleware 造成的問題,但沒有明確的證據
:::
## 環境
Node.js v14.15.1
```json
"connect-redis": "^5.2.0",
"dotenv": "^9.0.2",
"dropzone": "https://github.com/blueBlue0102/dropzone.git",
"ejs": "^3.1.6",
"express": "^4.17.1",
"express-session": "^1.17.1",
"formidable": "github:node-formidable/formidable",
"joi": "^17.3.0",
"passport": "^0.4.1",
"passport-google-oauth20": "^2.0.0",
"pg": "^8.6.0",
"pg-hstore": "^2.3.3",
"redis": "^3.1.2",
"sequelize": "^6.6.2",
"winston": "^3.3.3"
```
## 問題
在有帳號系統的服務上,透過 HTTP POST 上傳圖片
當使用者上傳的圖片數量超過設定的上限時
理想上,預期 Server 會回傳 403 並且告知已超過上傳上限
在 local 端時一切運作正常,但是部屬到 Heroku 後,無法正確地顯示錯誤訊息
且收到的 status code 為 503 Service Unavailable
而不是我設定好的 403 Forbidden
Logs 上顯示
`
sock=backend at=error code=H18 desc="Server Request Interrupted" method=POST
`
## 解決過程
### Docker
這是一個典型 local 和 remote 兩端執行結果不一的問題,也許 Docker 可以幫助我解決這問題?
研究了一陣子後,將環境要改成 Docker 來跑又需要解決更多的問題,所以決定擱置
### 官方文件中對於 H18 的說明
<https://github.com/hunterloftis/heroku-node-errcodes/tree/master/h18>
裡面提到,發生的主因是 socket 在 response 完成之前就被 destroy
文件內提到解決的方法,是在 server response 的各環節中 catch 所有 error
但是即使我設置了各層 try catch block
甚至放大招來 catch 所有 error:
```javascript=
process
.on("unhandledRejection", (reason, p) => {
console.error(reason, "Unhandled Rejection at Promise", p);
})
.on("uncaughtException", (err) => {
console.error(err, "Uncaught Exception thrown");
process.exit(1);
});
```
仍然沒有任何 error stack 出現在記錄裡
### Remote Debug
後來試著進行 remote debug,直接 trace 遠端上的 code 查看是怎麼跑
([remote debug 的操作記錄在這篇](https://hackmd.io/@vegetableBird/ryR9X0gKO))
終於得到了一點線索,stack trace 有東西出來了
源頭是 `express-session` 這個套件
得到 `Error: write ECONNRESET` 這個訊息
但明明是同樣的操作,這個 error stack 也不是每次都出來,行蹤不定
### Stack overflow
估狗搜尋,雖然 stack overflow 有很多相關討論,但每篇解答都不太一樣
有說 Heroku 平台本身有 bug 的、只要先讀取過 req.body 就沒事的、CRLF 問題的,眾說紛紜
逐一嘗試也仍未解決,消耗完了我所有能量...,有種招式盡出已山窮水盡的感覺
### TCP Socket?
回歸原點,既然官方文件說明這個問題的根本原因是
> An H18 signifies that the socket connected, some data was sent as part of a response by the app, but then the socket was destroyed without completing the response.
剛好我對 TCP socket 也不熟悉,所以開始找些資源來學這些概念
經過一陣子的線上課程影片後,雖然略懂 TCP socket 的概念了,但還是不知道怎麼解決問題...
### 解決
最後,再次估狗 `Server request interrupted` 之後,在一篇 stack overflow 找到一個說法
server 的 response 不要立即回傳,而是讓他 sleep 個幾秒再回傳
==終於,延遲幾秒後再回傳 response,就能正常顯示 403 訊息了==
## 小結
結合前面總總嘗試得來的線索
1. socket 在 response 尚未結束前就斷掉了
2. express-session 出現 write 相關的 error
3. 在回傳 response 之前,server sleep 個幾秒就正常運作
估計是 express-session 這個管理 cookie 的套件,沒有讓 server 等他執行完 cookie 的操作,server 若結束 response 就會產生 error
在 local 端時,因為速度夠快,所以沒有發生問題
但到了 heroku 端,因為資料庫都是在遠端而非本機端,所以需要來回連線的時間,而 server 又沒有等待與資料庫的連線完成才結束 response,因此造成 error
## Root Cause
雖然設定 setTimeout 可以避免錯誤產生,但這不是一個根本的解決方法
問題的源頭是,client 尚未 post 完 data,server 就已不對後續的 data 感興趣,而把 connection 關閉
## 問題懸而未決
換成 Linode 的主機後,且資料庫(Postgre、Redis)也在同個主機上,沒有產生 error
雖然我自認已經做好,但也許實際上還是沒有 handle 好「與資料庫溝通」相關的 error。可是既然在 Linode 上都沒問題的話,就不想再花時間處理 Heroku 上的這個問題了