Go如何響應http請求?

張君鴻發表於2019-04-03

在Web應用程式中,每個HTTP事務都由請求(Request)和響應(Response)構成,這次我們講講Go如何處理Web中的資料響應。

如果想了解Go如果處理Web請求的,可參考我的另一篇文章《Go Web如何處理Web請求?》

Web資料響應

Web的響應與請求結構是類似的,響應分為三個部分:響應行、響應頭部、響應體。

  1. 響應行:協議、響應狀態碼和狀態描述,如: HTTP/1.1 200 OK
  2. 響應頭部:包含各種頭部欄位資訊,如cookie,Content-Type等頭部資訊。
  3. 響應體:攜帶客戶端想要的資料,格式與編碼由頭部的Content-Type決定。

響應狀態碼的有固定取值和意義:

  • 100~199:表示服務端成功客戶端接收請求,要求客戶端繼續提交下一次請求才能完成整個處理過程。
  • 200~299:表示服務端成功接收請求並已完成整個處理過程。最常用就是:200
  • 300~399:為完成請求,客戶端需進一步細化請求。比較常用的如:客戶端請求的資源已經移動一個新地址使用302表示將資源重定向,客戶端請求的資源未發生改變,使用304,告訴客戶端從本地快取中獲取。
  • 400~499:客戶端的請求有錯誤,如:404表示你請求的資源在web伺服器中找不到,403表示伺服器拒絕客戶端的訪問,一般是許可權不夠。
  • 500~599:伺服器端出現錯誤,最常用的是:500

Go處理Web資料響應

Go將http響應封裝在http.ResponseWriter結構體中,ResponseWriter的定義很簡單,一共只有三個方法。

ResponseWriter

在net/http原始碼包中,http.ResponseWriter的結構資訊定義如下所示:

type ResponseWriter interface {
    Header() Header            //頭部
    Write([]byte) (int, error) //寫入方法
    WriteHeader(statusCode int)//狀態碼
}
複製程式碼
1. Header方法

header方法返回http.Header結構體,用於設定響應頭部資訊,http.Header資料型別map,定義如下:

type Header map[string][]string
複製程式碼

http.Header的方法列表如下:

type Header
    func (h Header) Add(key, value string)
    func (h Header) Del(key string)
    func (h Header) Get(key string) string
    func (h Header) Set(key, value string)
    func (h Header) Write(w io.Writer) error
    func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error
複製程式碼
2.Writer()方法

Write方法定義如下,用於向客戶端返回資料流,與io.Writer中的write方法的定義一致,是Go語言io流中標準方法。

Write([]byte) (int, error)
複製程式碼
3.WriterHeader方法

writerHeader方法的定義如下所示:

WriteHeader(statusCode int)
複製程式碼

引數statusCode表示響應碼,其取值可以為http包中已經定義好的常量值:

const (
    StatusContinue           = 100 // RFC 7231, 6.2.1
    StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
    StatusProcessing         = 102 // RFC 2518, 10.1

    StatusOK                   = 200 // RFC 7231, 6.3.1
    StatusCreated              = 201 // RFC 7231, 6.3.2
    StatusAccepted             = 202 // RFC 7231, 6.3.3
    StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
    StatusNoContent            = 204 // RFC 7231, 6.3.5
    StatusResetContent         = 205 // RFC 7231, 6.3.6
    StatusPartialContent       = 206 // RFC 7233, 4.1
    StatusMultiStatus          = 207 // RFC 4918, 11.1
    StatusAlreadyReported      = 208 // RFC 5842, 7.1
    StatusIMUsed               = 226 // RFC 3229, 10.4.1

    StatusMultipleChoices  = 300 // RFC 7231, 6.4.1
    StatusMovedPermanently = 301 // RFC 7231, 6.4.2
    StatusFound            = 302 // RFC 7231, 6.4.3
    StatusSeeOther         = 303 // RFC 7231, 6.4.4
    StatusNotModified      = 304 // RFC 7232, 4.1
    StatusUseProxy         = 305 // RFC 7231, 6.4.5

    StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
    StatusPermanentRedirect = 308 // RFC 7538, 3

    StatusBadRequest                   = 400 // RFC 7231, 6.5.1
    StatusUnauthorized                 = 401 // RFC 7235, 3.1
    StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
    StatusForbidden                    = 403 // RFC 7231, 6.5.3
    StatusNotFound                     = 404 // RFC 7231, 6.5.4
    StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
    StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
    StatusProxyAuthRequired            = 407 // RFC 7235, 3.2
    StatusRequestTimeout               = 408 // RFC 7231, 6.5.7
    StatusConflict                     = 409 // RFC 7231, 6.5.8
    StatusGone                         = 410 // RFC 7231, 6.5.9
    StatusLengthRequired               = 411 // RFC 7231, 6.5.10
    StatusPreconditionFailed           = 412 // RFC 7232, 4.2
    StatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11
    StatusRequestURITooLong            = 414 // RFC 7231, 6.5.12
    StatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13
    StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
    StatusExpectationFailed            = 417 // RFC 7231, 6.5.14
    StatusTeapot                       = 418 // RFC 7168, 2.3.3
    StatusMisdirectedRequest           = 421 // RFC 7540, 9.1.2
    StatusUnprocessableEntity          = 422 // RFC 4918, 11.2
    StatusLocked                       = 423 // RFC 4918, 11.3
    StatusFailedDependency             = 424 // RFC 4918, 11.4
    StatusTooEarly                     = 425 // RFC 8470, 5.2.
    StatusUpgradeRequired              = 426 // RFC 7231, 6.5.15
    StatusPreconditionRequired         = 428 // RFC 6585, 3
    StatusTooManyRequests              = 429 // RFC 6585, 4
    StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
    StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3

    StatusInternalServerError           = 500 // RFC 7231, 6.6.1
    StatusNotImplemented                = 501 // RFC 7231, 6.6.2
    StatusBadGateway                    = 502 // RFC 7231, 6.6.3
    StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
    StatusGatewayTimeout                = 504 // RFC 7231, 6.6.5
    StatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6
    StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
    StatusInsufficientStorage           = 507 // RFC 4918, 11.5
    StatusLoopDetected                  = 508 // RFC 5842, 7.2
    StatusNotExtended                   = 510 // RFC 2774, 7
    StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
複製程式碼
4. 示例
package main
import (
	"fmt"
	"net/http"
)
func main() {
    http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
        header := writer.Header()
        header.Add("Content-Type","application/json")
        writer.WriteHeader(http.StatusBadGateway)
        fmt.Fprintln(writer,"Web響應")
    })
    http.ListenAndServe(":8080",nil)
}
複製程式碼

在瀏覽器控制檯的Network檢視執行結果:

Go如何響應http請求?

Cookie

注意區分Cookie與Session之間的區別,Cookie用服務端儲存某些資訊到客戶端的,用於識別使用者,一種使用者追蹤機制,而Session則是服務端實現使用者多次請求之間保持會話的機制;兩者可以配合使用,也可以獨立使用。

net/http包提供了SetCookie方法用於向客戶端寫入Cookie.

func SetCookie(w ResponseWriter, cookie *Cookie)
複製程式碼

第一個引數為ResponseWriter結構體,而第二個引數則Cookie結構體,其定義如下:

type Cookie struct {
    Name  string
    Value string

    Path       string    // optional
    Domain     string    // optional
    Expires    time.Time // optional
    RawExpires string    // for reading cookies only

    // MaxAge=0 means no 'Max-Age' attribute specified.
    // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
    // MaxAge>0 means Max-Age attribute present and given in seconds
    MaxAge   int
    Secure   bool
    HttpOnly bool
    SameSite SameSite // Go 1.11
    Raw      string
    Unparsed []string // Raw text of unparsed attribute-value pairs
}
複製程式碼
示例
package main

import (
	"net/http"
	"time"
)

func main() {
    http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
        expire := time.Now()
        expire.AddDate(0,0,3)
        cookie := &http.Cookie{Name:"Auth",Value:"test",Expires:expire}
        http.SetCookie(writer,cookie)
    })
    http.ListenAndServe(":8080",nil)
}
複製程式碼

執行結果

Go如何響應http請求?

JSON響應

Go標準庫並沒有封裝直接向客戶端響應JSON資料的方法,不過,自己封裝一個也很簡單的,可以使用encoding/json包的Encoder結構體,將JSON資料寫入響應資料流。

示例
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/profile", func(writer http.ResponseWriter, request *http.Request) {
        data := map[string]string{
            "username": "小明",
            "email":    "xiaoming@163.com",
        }
        err := JSON(writer, data)
        check(err)
    })
    http.ListenAndServe(":8080", nil)
}

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func JSON(w http.ResponseWriter, data interface{}) error {
	w.Header().Set("Content-Type", "application/json")
	encoder := json.NewEncoder(w)
	return encoder.Encode(data)
}
複製程式碼

HTML模板

雖然在前後端分離的大趨勢下,後端開發更多時候是以介面api響應JSON的方式返回資料給前端,但仍然有些簡單的業務,是由後端直接將HTML模板返回由瀏覽器,由瀏覽器渲染。

Go語言可以使用html/template包渲染HTML模板。

示例
package main

import (
    "html/template"
    "log"
    "net/http"
)

const tpl = `
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>{{.Title}}</title>
	</head>
	<body>
		{{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
	</body>
</html>`

func main() {

    t, err := template.New("webpage").Parse(tpl)
    check(err)

    data := struct {
        Title string
        Items []string
    }{
    Title: "我的第一個HTML頁面",
    Items: []string{
            "我的相簿",
            "我的部落格",
        },
    }

    http.HandleFunc("/profile", func(writer http.ResponseWriter, request *http.Request) {
        err = t.Execute(writer, data)
        check(err)
    })
    http.ListenAndServe(":8080", nil)
}

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

複製程式碼

小結

Go語言在net/http包中對Web開發提供了很好的支援,讓開發者在使用Go進行Web應用開發時,幾乎不需要使用任何的Web框架,便可完成業務邏輯開發的工作。

相關文章