鍍金池/ 教程/ GO/ 12.2 網(wǎng)站錯(cuò)誤處理
7 文本處理
3 Web基礎(chǔ)
14 擴(kuò)展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫(kù)進(jìn)行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫(kù)
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語(yǔ)言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計(jì)一個(gè)Web框架
14.3 表單及驗(yàn)證支持
12 部署與維護(hù)
10 國(guó)際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫(kù)操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗(yàn)證表單的輸入
10.1 設(shè)置默認(rèn)地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測(cè)試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯(cuò)誤處理,調(diào)試和測(cè)試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項(xiàng)目規(guī)劃
13.4 日志和配置設(shè)計(jì)
7.6 字符串處理
13.2 自定義路由器設(shè)計(jì)
6.3 session存儲(chǔ)
3.4 Go的http包詳解
8.2 WebSocket
10.3 國(guó)際化站點(diǎn)
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計(jì)
2.6 interface
14.4 用戶認(rèn)證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯(cuò)誤處理
9.5 存儲(chǔ)密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯(cuò)誤處理
6 session和數(shù)據(jù)存儲(chǔ)
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實(shí)現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語(yǔ)言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫(kù)
14.7 小結(jié)
3.2 Go搭建一個(gè)Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫(kù)
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫(kù)
4.6 小結(jié)
8.4 RPC

12.2 網(wǎng)站錯(cuò)誤處理

我們的Web應(yīng)用一旦上線之后,那么各種錯(cuò)誤出現(xiàn)的概率都有,Web應(yīng)用日常運(yùn)行中可能出現(xiàn)多種錯(cuò)誤,具體如下所示:

  • 數(shù)據(jù)庫(kù)錯(cuò)誤:指與訪問數(shù)據(jù)庫(kù)服務(wù)器或數(shù)據(jù)相關(guān)的錯(cuò)誤。例如,以下可能出現(xiàn)的一些數(shù)據(jù)庫(kù)錯(cuò)誤。

    • 連接錯(cuò)誤:這一類錯(cuò)誤可能是數(shù)據(jù)庫(kù)服務(wù)器網(wǎng)絡(luò)斷開、用戶名密碼不正確、或者數(shù)據(jù)庫(kù)不存在。
    • 查詢錯(cuò)誤:使用的SQL非法導(dǎo)致錯(cuò)誤,這樣子SQL錯(cuò)誤如果程序經(jīng)過嚴(yán)格的測(cè)試應(yīng)該可以避免。
    • 數(shù)據(jù)錯(cuò)誤:數(shù)據(jù)庫(kù)中的約束沖突,例如一個(gè)唯一字段中插入一條重復(fù)主鍵的值就會(huì)報(bào)錯(cuò),但是如果你的應(yīng)用程序在上線之前經(jīng)過了嚴(yán)格的測(cè)試也是可以避免這類問題。
  • 應(yīng)用運(yùn)行時(shí)錯(cuò)誤:這類錯(cuò)誤范圍很廣,涵蓋了代碼中出現(xiàn)的幾乎所有錯(cuò)誤??赡艿膽?yīng)用錯(cuò)誤的情況如下:

    • 文件系統(tǒng)和權(quán)限:應(yīng)用讀取不存在的文件,或者讀取沒有權(quán)限的文件、或者寫入一個(gè)不允許寫入的文件,這些都會(huì)導(dǎo)致一個(gè)錯(cuò)誤。應(yīng)用讀取的文件如果格式不正確也會(huì)報(bào)錯(cuò),例如配置文件應(yīng)該是ini的配置格式,而設(shè)置成了json格式就會(huì)報(bào)錯(cuò)。
    • 第三方應(yīng)用:如果我們的應(yīng)用程序耦合了其他第三方接口程序,例如應(yīng)用程序發(fā)表文章之后自動(dòng)調(diào)用接發(fā)微博的接口,所以這個(gè)接口必須正常運(yùn)行才能完成我們發(fā)表一篇文章的功能。
  • HTTP錯(cuò)誤:這些錯(cuò)誤是根據(jù)用戶的請(qǐng)求出現(xiàn)的錯(cuò)誤,最常見的就是404錯(cuò)誤。雖然可能會(huì)出現(xiàn)很多不同的錯(cuò)誤,但其中比較常見的錯(cuò)誤還有401未授權(quán)錯(cuò)誤(需要認(rèn)證才能訪問的資源)、403禁止錯(cuò)誤(不允許用戶訪問的資源)和503錯(cuò)誤(程序內(nèi)部出錯(cuò))。
  • 操作系統(tǒng)出錯(cuò):這類錯(cuò)誤都是由于應(yīng)用程序上的操作系統(tǒng)出現(xiàn)錯(cuò)誤引起的,主要有操作系統(tǒng)的資源被分配完了,導(dǎo)致死機(jī),還有操作系統(tǒng)的磁盤滿了,導(dǎo)致無法寫入,這樣就會(huì)引起很多錯(cuò)誤。
  • 網(wǎng)絡(luò)出錯(cuò):指兩方面的錯(cuò)誤,一方面是用戶請(qǐng)求應(yīng)用程序的時(shí)候出現(xiàn)網(wǎng)絡(luò)斷開,這樣就導(dǎo)致連接中斷,這種錯(cuò)誤不會(huì)造成應(yīng)用程序的崩潰,但是會(huì)影響用戶訪問的效果;另一方面是應(yīng)用程序讀取其他網(wǎng)絡(luò)上的數(shù)據(jù),其他網(wǎng)絡(luò)斷開會(huì)導(dǎo)致讀取失敗,這種需要對(duì)應(yīng)用程序做有效的測(cè)試,能夠避免這類問題出現(xiàn)的情況下程序崩潰。

錯(cuò)誤處理的目標(biāo)

在實(shí)現(xiàn)錯(cuò)誤處理之前,我們必須明確錯(cuò)誤處理想要達(dá)到的目標(biāo)是什么,錯(cuò)誤處理系統(tǒng)應(yīng)該完成以下工作:

  • 通知訪問用戶出現(xiàn)錯(cuò)誤了:不論出現(xiàn)的是一個(gè)系統(tǒng)錯(cuò)誤還是用戶錯(cuò)誤,用戶都應(yīng)當(dāng)知道Web應(yīng)用出了問題,用戶的這次請(qǐng)求無法正確的完成了。例如,對(duì)于用戶的錯(cuò)誤請(qǐng)求,我們顯示一個(gè)統(tǒng)一的錯(cuò)誤頁(yè)面(404.html)。出現(xiàn)系統(tǒng)錯(cuò)誤時(shí),我們通過自定義的錯(cuò)誤頁(yè)面顯示系統(tǒng)暫時(shí)不可用之類的錯(cuò)誤頁(yè)面(error.html)。
  • 記錄錯(cuò)誤:系統(tǒng)出現(xiàn)錯(cuò)誤,一般就是我們調(diào)用函數(shù)的時(shí)候返回err不為nil的情況,可以使用前面小節(jié)介紹的日志系統(tǒng)記錄到日志文件。如果是一些致命錯(cuò)誤,則通過郵件通知系統(tǒng)管理員。一般404之類的錯(cuò)誤不需要發(fā)送郵件,只需要記錄到日志系統(tǒng)。
  • 回滾當(dāng)前的請(qǐng)求操作:如果一個(gè)用戶請(qǐng)求過程中出現(xiàn)了一個(gè)服務(wù)器錯(cuò)誤,那么已完成的操作需要回滾。下面來看一個(gè)例子:一個(gè)系統(tǒng)將用戶遞交的表單保存到數(shù)據(jù)庫(kù),并將這個(gè)數(shù)據(jù)遞交到一個(gè)第三方服務(wù)器,但是第三方服務(wù)器掛了,這就導(dǎo)致一個(gè)錯(cuò)誤,那么先前存儲(chǔ)到數(shù)據(jù)庫(kù)的表單數(shù)據(jù)應(yīng)該刪除(應(yīng)告知無效),而且應(yīng)該通知用戶系統(tǒng)出現(xiàn)錯(cuò)誤了。
  • 保證現(xiàn)有程序可運(yùn)行可服務(wù):我們知道沒有人能保證程序一定能夠一直正常的運(yùn)行著,萬(wàn)一哪一天程序崩潰了,那么我們就需要記錄錯(cuò)誤,然后立刻讓程序重新運(yùn)行起來,讓程序繼續(xù)提供服務(wù),然后再通知系統(tǒng)管理員,通過日志等找出問題。

如何處理錯(cuò)誤

錯(cuò)誤處理其實(shí)我們已經(jīng)在十一章第一小節(jié)里面有過介紹如何設(shè)計(jì)錯(cuò)誤處理,這里我們?cè)購(gòu)囊粋€(gè)例子詳細(xì)的講解一下,如何來處理不同的錯(cuò)誤:

  • 通知用戶出現(xiàn)錯(cuò)誤:

    通知用戶在訪問頁(yè)面的時(shí)候我們可以有兩種錯(cuò)誤:404.html和error.html,下面分別顯示了錯(cuò)誤頁(yè)面的源碼:

      <html lang="en">
      <head>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
          <title>找不到頁(yè)面</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
      </head>
      <body>
      <div class="container">
          <div class="row">
              <div class="span10">
                  <div class="hero-unit">
                      <h1>404!</h1>
                      <p>{{.ErrorInfo}}</p>
                  </div>
              </div><!--/span-->
          </div>
      </div>
      </body>
      </html>

    另一個(gè)源碼:

      <html lang="en">
      <head>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
          <title>系統(tǒng)錯(cuò)誤頁(yè)面</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
      </head>
      <body>
      <div class="container">
          <div class="row">
              <div class="span10">
                  <div class="hero-unit">
                      <h1>系統(tǒng)暫時(shí)不可用!</h1>
                      <p>{{.ErrorInfo}}</p>
                  </div>
              </div><!--/span-->
          </div>
      </div>
      </body>
      </html>

    404的錯(cuò)誤處理邏輯,如果是系統(tǒng)的錯(cuò)誤也是類似的操作,同時(shí)我們看到在:

      func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
          if r.URL.Path == "/" {
              sayhelloName(w, r)
              return
          }
          NotFound404(w, r)
          return
      }
    
      func NotFound404(w http.ResponseWriter, r *http.Request) {
          log.Error("頁(yè)面找不到")   //記錄錯(cuò)誤日志
          t, _ = t.ParseFiles("tmpl/404.html", nil)  //解析模板文件
          ErrorInfo := "文件找不到" //獲取當(dāng)前用戶信息
          t.Execute(w, ErrorInfo)  //執(zhí)行模板的merger操作
      }
    
      func SystemError(w http.ResponseWriter, r *http.Request) {
          log.Critical("系統(tǒng)錯(cuò)誤")   //系統(tǒng)錯(cuò)誤觸發(fā)了Critical,那么不僅會(huì)記錄日志還會(huì)發(fā)送郵件
          t, _ = t.ParseFiles("tmpl/error.html", nil)  //解析模板文件
          ErrorInfo := "系統(tǒng)暫時(shí)不可用" //獲取當(dāng)前用戶信息
          t.Execute(w, ErrorInfo)  //執(zhí)行模板的merger操作
      }

如何處理異常

我們知道在很多其他語(yǔ)言中有try...catch關(guān)鍵詞,用來捕獲異常情況,但是其實(shí)很多錯(cuò)誤都是可以預(yù)期發(fā)生的,而不需要異常處理,應(yīng)該當(dāng)做錯(cuò)誤來處理,這也是為什么Go語(yǔ)言采用了函數(shù)返回錯(cuò)誤的設(shè)計(jì),這些函數(shù)不會(huì)panic,例如如果一個(gè)文件找不到,os.Open返回一個(gè)錯(cuò)誤,它不會(huì)panic;如果你向一個(gè)中斷的網(wǎng)絡(luò)連接寫數(shù)據(jù),net.Conn系列類型的Write函數(shù)返回一個(gè)錯(cuò)誤,它們不會(huì)panic。這些狀態(tài)在這樣的程序里都是可以預(yù)期的。你知道這些操作可能會(huì)失敗,因?yàn)樵O(shè)計(jì)者已經(jīng)用返回錯(cuò)誤清楚地表明了這一點(diǎn)。這就是上面所講的可以預(yù)期發(fā)生的錯(cuò)誤。

但是還有一種情況,有一些操作幾乎不可能失敗,而且在一些特定的情況下也沒有辦法返回錯(cuò)誤,也無法繼續(xù)執(zhí)行,這樣情況就應(yīng)該panic。舉個(gè)例子:如果一個(gè)程序計(jì)算x[j],但是j越界了,這部分代碼就會(huì)導(dǎo)致panic,像這樣的一個(gè)不可預(yù)期嚴(yán)重錯(cuò)誤就會(huì)引起panic,在默認(rèn)情況下它會(huì)殺掉進(jìn)程,它允許一個(gè)正在運(yùn)行這部分代碼的goroutine從發(fā)生錯(cuò)誤的panic中恢復(fù)運(yùn)行,發(fā)生panic之后,這部分代碼后面的函數(shù)和代碼都不會(huì)繼續(xù)執(zhí)行,這是Go特意這樣設(shè)計(jì)的,因?yàn)橐獏^(qū)別于錯(cuò)誤和異常,panic其實(shí)就是異常處理。如下代碼,我們期望通過uid來獲取User中的username信息,但是如果uid越界了就會(huì)拋出異常,這個(gè)時(shí)候如果我們沒有recover機(jī)制,進(jìn)程就會(huì)被殺死,從而導(dǎo)致程序不可服務(wù)。因此為了程序的健壯性,在一些地方需要建立recover機(jī)制。

func GetUser(uid int) (username string) {
    defer func() {
        if x := recover(); x != nil {
            username = ""
        }
    }()

    username = User[uid]
    return
}

上面介紹了錯(cuò)誤和異常的區(qū)別,那么我們?cè)陂_發(fā)程序的時(shí)候如何來設(shè)計(jì)呢?規(guī)則很簡(jiǎn)單:如果你定義的函數(shù)有可能失敗,它就應(yīng)該返回一個(gè)錯(cuò)誤。當(dāng)我調(diào)用其他package的函數(shù)時(shí),如果這個(gè)函數(shù)實(shí)現(xiàn)的很好,我不需要擔(dān)心它會(huì)panic,除非有真正的異常情況發(fā)生,即使那樣也不應(yīng)該是我去處理它。而panic和recover是針對(duì)自己開發(fā)package里面實(shí)現(xiàn)的邏輯,針對(duì)一些特殊情況來設(shè)計(jì)。

小結(jié)

本小節(jié)總結(jié)了當(dāng)我們的Web應(yīng)用部署之后如何處理各種錯(cuò)誤:網(wǎng)絡(luò)錯(cuò)誤、數(shù)據(jù)庫(kù)錯(cuò)誤、操作系統(tǒng)錯(cuò)誤等,當(dāng)錯(cuò)誤發(fā)生時(shí),我們的程序如何來正確處理:顯示友好的出錯(cuò)界面、回滾操作、記錄日志、通知管理員等操作,最后介紹了如何來正確處理錯(cuò)誤和異常。一般的程序中錯(cuò)誤和異常很容易混淆的,但是在Go中錯(cuò)誤和異常是有明顯的區(qū)分,所以告訴我們?cè)诔绦蛟O(shè)計(jì)中處理錯(cuò)誤和異常應(yīng)該遵循怎么樣的原則。