這是一個 Full Stack Cycle Developer 的時代了, 所以對於身為前端工程師的我來說寫一點 backend 加上又是潮潮的 Go 也是理所當然的。 不過跟像是 Node.js 這種 Full Cycle Developer 比較常見的環境比起來, Go 在實務上還是有很多不一樣的地方要注意的。 這裡來寫一個如果你跟我一樣不喜歡 RTFM 就一定會撞到的問題。

一個簡單的範例

在 backend service 中發出 HTTP request 來使用其他服務的 API 是非常常見的, Node.js 環境中大家用好用滿的 http client library 通常就是 axios了。 可是如果你是從這種環境過來的人, 你一定無法第一眼就發現下面這段 code 有什麼問題:

resp, err := http.Get("https://httpbin.org/uuid")
if err != nil {
	log.Fatal(err)
}

decoder := json.NewDecoder(resp.Body)
defer resp.Body.Close()

var body map[string]interface{}
err = decoder.Decode(&body)
if err != nil {
	log.Fatalf("Error decoding body as JSON: %s", err.Error())
}

log.Printf("Response: %v", body)

錯誤也 handle 了,JSON 也 decode 了, 理論上應該沒有什麼問題了吧! 一直到有一天你就會遇上,你想要 parse body 的時候發現他不是正確的 JSON 格式之類的錯誤:

Error decoding body as JSON: EOF

然後你就會開始思考為什麼 response body 會打不開呢! 我的 HTTP GET 又沒有吐 error!

早就被你忘記的 Status Code

在 axios 這些 http client library 的預設設定, 通常發了 http request 出去之後, 只要 server 回的 status code 不是 2xx 都會 trigger error handling 的部分, 所以發 request 的時候抓錯誤不但可以抓到連線的錯誤也可以抓到 response 非預期 status code 的錯誤。 但是在 Go 的 net/http 文件裡面有清楚的寫到:

An error is returned if caused by client policy (such as CheckRedirect), or failure to speak HTTP (such as a network connectivity problem). A non-2xx status code doesn’t cause an error.

上面的 quote 我順便幫大家畫重點了, 意思就是,status code 並不在 error 範圍內!

記得檢查 Status Code

所以,上面的那段程式碼正確的作法應該還要再加上 status code 檢查才對:

resp, err := http.Get("https://httpbin.org/uuid")
if err != nil {
	log.Fatal(err)
}

if resp.StatusCode != 200 {
	log.Fatalf("Unexpected response status code: %v", resp.StatusCode)
}

decoder := json.NewDecoder(resp.Body)
defer resp.Body.Close()

var body map[string]interface{}
err = decoder.Decode(&body)
if err != nil {
	log.Fatalf("Error decoding body as JSON: %s", err.Error())
}

log.Printf("Response: %v", body)

這樣的 error handling 才可以稍微跟 axios 類似一點囉! (先不要說完全一樣好了,因為我也還在學…)