Go 開發 HTTP

Go 是一門新語言。不少人都是用 Go 來開發 Web 服務。Web 開發不少同窗急於求成,直接使用 beego, echoiris 等知名框架。對標準庫 net/http 的瞭解甚少。這裏我就主要聊一下標準庫 net/http 開發 Web 服務時的使用細節。html

建立 HTTP 服務

在 Go 中,建立 HTTP 服務很簡單:git

package main

// in main.go

import (
    "fmt"
    "net/http"
)

func main(){
    if err := http.ListenAndServe(":12345",nil); err != nil{
        fmt.Println("start http server fail:",err)
    }
}

這樣就會啓動一個 HTTP 服務在端口 12345。瀏覽器輸入 http://localhost:12345/ 就能夠訪問。固然從代碼看出,沒有給這個 HTTP 服務添加實際的處理邏輯,全部的訪問都是默認的 404 Not Foundgithub

<!--more-->golang

添加 http.Handler

添加 HTTP 的處理邏輯的方法簡單直接:web

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, Go HTTP Server"))
	})
    http.HandleFunc("/abc", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, Go HTTP abc"))
	})
	if err := http.ListenAndServe(":123456", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

訪問 http://localhost:12345/ 就能夠看到頁面輸出 Hello, Go HTTP Server 的內容。訪問 http://localhost:12345/abc 就能夠看到頁面輸出 Hello, Go HTTP abc 的內容。可是 Go 默認的路由匹配機制很弱。上面的代碼除了 /abc,其餘的請求都匹配到 / ,不足以使用,確定須要本身寫路由的過程。一個簡單的方式就是寫一個本身的 http.Handler算法

type MyHandler struct{} // 實現 http.Handler 接口的 ServeHTTP 方法

func (mh MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/abc" {
		w.Write([]byte("abc"))
		return
	}
	if r.URL.Path == "/xyz" {
		w.Write([]byte("xyz"))
		return
	}
	w.Write([]byte("index"))
	// 這裏你能夠寫本身的路由匹配規則
}

func main() {
	http.Handle("/", MyHandler{})
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

這樣能夠在本身的 MyHandler 寫複雜的路由規則和處理邏輯。http.ListenAndServe 的第二個參數寫的會更優雅:json

func main() {
	if err := http.ListenAndServe(":12345", MyHandler{}); err != nil {
		fmt.Println("start http server fail:", err)
	}
}
http.ServeMux 路由

net/http 提供了一個很是簡單的路由結構 http.ServeMux。方法 http.HandleFunc()http.Handler() 就是把路由規則和對應函數註冊到默認的一個 http.ServeMux 上。固然,你能夠本身建立 http.ServeMux 來使用:數組

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, HTTP Server")
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", handler)
	http.ListenAndServe(":12345", mux)
}

可是由於 http.ServeMux 路由規則簡單,功能有限,實踐都不會用的,如同雞肋。更推薦使用 httprouter瀏覽器

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

// httprouter.Params 是匹配到的路由參數,好比規則 /user/:id 中 的 :id 的對應值
func handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "hello, httprouter")
}

func main() {
	router := httprouter.New()
	router.GET("/", handle)

	if err := http.ListenAndServe(":12345", router); err != nil {
		fmt.Println("start http server fail:", err)
	}
}
http.Handler/http.HandlerFunc 中間件

Go 的 HTTP 處理過程能夠不只是一個 http.HandlerFunc,並且是一組 http.HandlerFunc,好比:安全

func handle1(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("handle1"))
}

func handle2(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("handle2"))
}

// 把幾個函數組合起來
func makeHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		for _, handler := range handlers {
			handler(w, r)
		}
	}
}

func main() {
	http.HandleFunc("/", makeHandlers(handle1, handle2))
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

這種模式開發的框架能夠參考 negroni。它的中間件都是以實現 http.Handler 的結構體來組合的。

Request

HTTP 過程的操做主要是針對客戶端發來的請求數據在 *http.Request,和返回給客戶端的 http.ResponseWriter 兩部分。

請求數據 *http.Request 有兩個部分:基本數據和傳遞參數。基本數據好比請求的方法、協議、URL、頭信息等能夠直接簡單獲取:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Method:", r.Method)
	fmt.Println("URL:", r.URL, "URL.Path", r.URL.Path) // 這裏是 *net/url.URL 結構,對應的內容能夠查API
	fmt.Println("RemoteAddress", r.RemoteAddr)
	fmt.Println("UserAgent", r.UserAgent())
	fmt.Println("Header.Accept", r.Header.Get("Accept"))
    fmt.Println("Cookies",r.Cookies())
    // 還有不少確定會有的基本數據,能夠查閱 API 找尋一下
}

http.HandleFunc("/", HttpHandle)
if err := http.ListenAndServe(":12345", nil); err != nil {
	fmt.Println("start http server fail:", err)
}
表單數據

請求傳遞的參數,也就是 表單數據,保存在 *http.Request.Form*http.Request.PostForm (POST 或 PUT 的數據),相似 PHP 的 $_REQUEST 和 $_POST/$_PUT 兩個部分。

例如 GET /?abc=xyz,獲取這個數據並打印到返回內容:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	value := r.FormValue("abc")
	w.Write([]byte("GET: abc=" + value))
}

訪問 http://localhost:12345/?abc=123 就能夠看到頁面內容 GET: abc=123。POST 的表單數據也是相似:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	v1 := r.FormValue("abc")
	v2 := r.PostFormValue("abc")
	v3 := r.Form.Get("abc")
	v4 := r.PostForm.Get("abc")
	fmt.Println(v1 == v2, v1 == v3, v1 == v4)
	w.Write([]byte("POST: abc=" + v1))
}

注意,這四個值 v1,v2,v3,v4 是相同的值。

若是同一個表單域傳遞了多個值,須要直接操做 r.Formr.PostForm,好比 GET /?abc=123&abc=abc&abc=xyz

func HttpHandle(w http.ResponseWriter, r *http.Request) {
    // 這裏必定要記得 ParseForm,不然 r.Form 是空的
    // 調用 r.FormValue() 的時候會自動執行 r.ParseForm()
	r.ParseForm()
	values := r.Form["abc"]
	w.Write([]byte("GET abc=" + strings.Join(values, ","))) // 這裏記得 import "strings"
}

訪問 http://localhost:12345/?abc=123&abc=abc&abc=xyz 能夠看到內容 GET abc=123,abc,xyz

表單數據存儲在 r.Form,是 map[string][]string 類型,即支持一個表單域多個值的狀況。r.FormValue() 只獲取第一個值

表單數據是簡單的 kv 對應,很容易實現 kv 到 結構體的一一對應,例如使用庫 https://github.com/mholt/binding

type User struct {
	Id   int
	Name string
}

func (u *User) FieldMap(req *http.Request) binding.FieldMap {
	return binding.FieldMap{
		&u.Id: "user_id",
		&u.Name: binding.Field{
			Form:     "name",
			Required: true,
		},
	}
}

func handle(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	errs := binding.Bind(r, user)
	if errs.Handle(w) {
		return
	}
}
Body 消息體

不管表單數據,仍是上傳的二進制數據,都是保存在 HTTP 的 Body 中的。操做 *http.Request.Body 能夠獲取到內容。可是注意 *http.Request.Bodyio.ReadCloser 類型,只能一次性讀取完整,第二次就是空的

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("read body fail:", err)
		w.WriteHeader(500)
		return
	}
    DoSomething(body) // 儘可能使用已經讀出的 body 內容,不要再去讀取 r.Body 
	w.Write([]byte("body:"))
	w.Write(body)
}

使用 curl 命令行發送 POST 數據到服務器,curl -X POST --data "abcdefg" http://localhost:12345/,能夠看到返回內容 body:abcdefg

根據 HTTP 協議,若是請求的 Content-Type: application/x-www-form-urlencoded,Body 中的數據就是相似 abc=123&abc=abc&abc=xyz 格式的數據,也就是常規的 表單數據。這些使用 r.ParseForm() 而後操做 r.Form 處理數據。若是是純數據,好比文本abcdefgJSON 數據等,你才須要直接操做 Body 的。好比接收 JSON 數據:

type User struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	// Body 裏的內容是 JSON 數據:
	// {"id":123,"name":"xyz"}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("read body fail:", err)
		w.WriteHeader(500)
		return
	}
	var u User
	if err = json.Unmarshal(body, &u); err != nil {
		fmt.Println("json Unmarshal fail:", err)
		w.WriteHeader(500)
		return
	}
	w.Write([]byte("user.id:" + strconv.Itoa(u.Id) + " "))
	w.Write([]byte("user.name:" + u.Name))
    // 返回內容是: user.id:123 user.name:xyz
}

若是須要對 Body 的數據作直接處理,JSON 數據例子是通用的模式。

上傳文件

上傳的文件通過 Go 的解析保存在 *http.Request.MultipartForm 中,經過 r.FormFile() 去獲取收到的文件信息和數據流,並處理:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	// 這裏必定要記得 r.ParseMultipartForm(), 不然 r.MultipartForm 是空的
	// 調用 r.FormFile() 的時候會自動執行 r.ParseMultipartForm()
	r.ParseMultipartForm(32 << 20) 
    // 寫明緩衝的大小。若是超過緩衝,文件內容會被放在臨時目錄中,而不是內存。過大可能較多佔用內存,太小可能增長硬盤 I/O
	// FormFile() 時調用 ParseMultipartForm() 使用的大小是 32 << 20,32MB
	file, fileHeader, err := r.FormFile("file") // file 是上傳表單域的名字
	if err != nil {
		fmt.Println("get upload file fail:", err)
		w.WriteHeader(500)
		return
	}
	defer file.Close() // 此時上傳內容的 IO 已經打開,須要手動關閉!!

	// fileHeader 有一些文件的基本信息
	fmt.Println(fileHeader.Header.Get("Content-Type"))

	// 打開目標地址,把上傳的內容存進去
	f, err := os.OpenFile("saveto.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("save upload file fail:", err)
		w.WriteHeader(500)
		return
	}

	defer f.Close()
	if _, err = io.Copy(f, file); err != nil {
		fmt.Println("save upload file fail:", err)
		w.WriteHeader(500)
		return
	}
	w.Write([]byte("upload file:" + fileHeader.Filename + " - saveto : saveto.txt"))
}

上傳文件信息中,文件大小 信息是沒有的。而文件大小是上傳限制中必要的條件,因此須要一些方法來獲取文件大小:

// 使用接口檢查是否有 Size() 方法
type fileSizer interface {
	Size() int64
}

// 從 multipart.File 獲取文件大小
func getUploadFileSize(f multipart.File) (int64, error) {
    // 從內存讀取出來
	// if return *http.sectionReader, it is alias to *io.SectionReader
	if s, ok := f.(fileSizer); ok {
		return s.Size(), nil
	}
    // 從臨時文件讀取出來
	// or *os.File
	if fp, ok := f.(*os.File); ok {
		fi, err := fp.Stat()
		if err != nil {
			return 0, err
		}
		return fi.Size(), nil
	}
	return 0, nil
}

r.FormFile() 只返回第一個上傳的文件,若是同一個表單域上傳多個文件,只能直接操做 r.MultipartForm

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(32 << 20)
	var (
		file multipart.File
		err  error
	)
	for _, fileHeader := range r.MultipartForm.File["file"] {
		if file, err = fileHeader.Open(); err != nil {
			fmt.Println("open upload file fail:", fileHeader.Filename, err)
			continue
		}
		SaveFile(file) // 仿照上面單個文件的操做,處理 file
		file.Close() // 操做結束必定要 Close,for 循環裏不要用 defer file.Close()
		file = nil
		w.Write([]byte("save:" + fileHeader.Filename + " "))
	}
}

ResponseWriter

http.ResponseWriter 是一個接口,你能夠根據接口,添加一些本身須要的行爲:

type ResponseWriter interface {
	Header() Header // 添加返回頭信息
	Write([]byte) (int, error) // 添加返回的內容
	WriteHeader(int) // 設置返回的狀態碼
}

w.WriteHeader() 是一次性的,不能重複設置狀態碼,不然會有提示信息:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200) // 設置成功
	w.WriteHeader(404) // 提示:http: multiple response.WriteHeader calls 
	w.WriteHeader(503) // 提示:http: multiple response.WriteHeader calls 
}

並且須要在 w.Write() 以前設置 w.WriteHeader(),不然是 200。(要先發送狀態碼,再發送內容)

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
	w.WriteHeader(404) // 提示:http: multiple response.WriteHeader calls,由於 w.Write() 已發佈 HTTP 200
}

http.ResponseWriter 接口過於簡單,實際使用會本身實現 ResponseWriter 來使用,好比獲取返回的內容:

type MyResponseWriter struct {
	http.ResponseWriter
	bodyBytes *bytes.Buffer
}

// 覆寫 http.ResponseWriter 的方法
func (mrw MyResponseWriter) Write(body []byte) (int, error) {
	mrw.bodyBytes.Write(body) // 記錄下返回的內容
	return mrw.ResponseWriter.Write(body)
}

// Body 獲取返回的內容,這個是本身添加的方法
func (mrw MyResponseWriter) Body() []byte {
	return mrw.bodyBytes.Bytes()
}

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	m := MyResponseWriter{
		ResponseWriter: w,
		bodyBytes:      bytes.NewBuffer(nil),
	}
	m.Header().Add("Content-Type", "text/html") // 要輸出HTML記得加頭信息
	m.Write([]byte("<h1>Hello World</h1>"))
	m.Write([]byte("abcxyz"))
	fmt.Println("body:", string(m.Body()))
}
輸出其餘內容

net/http 提供一些便利的方法能夠輸出其餘的內容,好比 cookie:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	c := &http.Cookie{
		Name:     "abc",
		Value:    "xyz",
		Expires:  time.Now().Add(1000 * time.Second),
		MaxAge:   1000,
		HttpOnly: true,
	}
	http.SetCookie(w, c)
}

好比服務端返回下載文件:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "download.txt")
}

或者是生成的數據流,好比驗證碼,看成文件返回:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	captchaImageBytes := createCaptcha() // 假設生成驗證碼的函數,返回 []byte
	buf := bytes.NewReader(captchaImageBytes)
	http.ServeContent(w, r, "captcha.png", time.Now(), buf)
}

還有一些狀態碼的直接操做:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/abc", 302)
}

func HttpHandle2(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

返回 JSON, XML 和 渲染模板的內容等的代碼例子,能夠參考 HTTP Response Snippets for Go

Context

Go 1.7 添加了 context 包,用於傳遞數據和作超時、取消等處理。*http.Request 添加了 r.Context()r.WithContext() 來操做請求過程須要的 context.Context 對象。

傳遞數據

context 能夠在 http.HandleFunc 之間傳遞數據:

func handle1(w http.ResponseWriter, r *http.Request) {
	ctx := context.WithValue(r.Context(), "abc", "xyz123") // 寫入 string 到 context
	handle2(w, r.WithContext(ctx))                         // 傳遞給下一個 handleFunc
}

func handle2(w http.ResponseWriter, r *http.Request) {
	str, ok := r.Context().Value("abc").(string) // 取出的 interface 須要推斷到 string
	if !ok {
		str = "not string"
	}
	w.Write([]byte("context.abc = " + str))
}

func main() {
	http.HandleFunc("/", handle1)
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}
處理超時的請求

利用 context.WithTimeout 能夠建立會超時結束的 context,用來處理業務超時的狀況:

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	ctx, cancelFn := context.WithTimeout(r.Context(), 1*time.Second)

	// cancelFn 關掉 WithTimeout 裏的計時器
	// 若是 ctx 超時,計時器會自動關閉,可是若是沒有超時就執行到 <-resCh,就須要手動關掉
	defer cancelFn()

	// 把業務放到 goroutine 執行, resCh 獲取結果
	resCh := make(chan string, 1)
	go func() {
        // 故意寫業務超時
		time.Sleep(5 * time.Second)
		resCh <- r.FormValue("abc")
	}()

	// 看 ctx 超時仍是 resCh 的結果先到達
	select {
	case <-ctx.Done():
		w.WriteHeader(http.StatusGatewayTimeout)
		w.Write([]byte("http handle is timeout:" + ctx.Err().Error()))
	case r := <-resCh:
		w.Write([]byte("get: abc = " + r))
	}
}
帶 context 的中間件

Go 的不少 HTTP 框架使用 context 或者本身定義的 Context 結果做爲 http.Handler 中間件之間數據傳遞的媒介,好比 xhandler:

import(
	"context"
	"github.com/rs/xhandler"
	
)

type myMiddleware struct {
    next xhandler.HandlerC
}

func (h myMiddleware) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    ctx = context.WithValue(ctx, "test", "World")
    h.next.ServeHTTPC(ctx, w, r)
}

func main() {
    c := xhandler.Chain{}
	c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC {
        return myMiddleware{next: next}
    })
	xh := xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
        value := ctx.Value("test").(string) // 使用 context 傳遞的數據
        w.Write([]byte("Hello " + value))
    })
	http.Handle("/", c.Handler(xh)) // 將 xhandler.Handler 轉化爲 http.Handler
	if err := http.ListenAndServe(":12345", nil); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

xhandler 封裝 ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) 用於相似 http.HandlerServeHTTP(w http.ResponseWriter, r *http.Request) 的行爲,處理 HTTP 的過程。

Hijack

一些時候須要直接操做 Go 的 HTTP 鏈接時,使用 Hijack() 將 HTTP 對應的 TCP 取出。鏈接在 Hijack() 以後,HTTP 的相關操做會受到影響,鏈接的管理須要用戶本身操做,並且例如 w.Write([]byte) 不會返回內容,須要操做 Hijack() 後的 *bufio.ReadWriter

func HttpHandle(w http.ResponseWriter, r *http.Request) {
	hj, ok := w.(http.Hijacker)
	if !ok {
		return
	}
	conn, buf, err := hj.Hijack()
	if err != nil {
		w.WriteHeader(500)
		return
	}
	defer conn.Close()       // 須要手動關閉鏈接
	w.Write([]byte("hello")) // 會提示 http: response.Write on hijacked connection

	// 返回內容須要
	buf.WriteString("hello")
	buf.Flush()
}

Hijack 主要看到的用法是對 HTTP 的 Upgrade 時在用,好比從 HTTP 到 Websocket 時,golang.org/x/net/websocket:

func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
	rwc, buf, err := w.(http.Hijacker).Hijack()
	if err != nil {
		panic("Hijack failed: " + err.Error())
	}
	// The server should abort the WebSocket connection if it finds
	// the client did not send a handshake that matches with protocol
	// specification.
	defer rwc.Close()
	conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
	if err != nil {
		return
	}
	if conn == nil {
		panic("unexpected nil conn")
	}
	s.Handler(conn)
}

http.Server 的使用細節

上面全部的代碼我都是用的 http.ListenAndServe 來啓動 HTTP 服務。實際上執行這個過程的 *http.Server 這個結構。有些時候咱們不是使用默認的行爲,會給 *http.Server 定義更多的內容。

http.ListenAndServe 默認的 *http.Server 是沒有超時設置的。一些場景下你必須設置超時,不然會遇到太多鏈接句柄的問題:

func main() {
	server := &http.Server{
		Handler:      MyHandler{}, // 使用實現 http.Handler 的結構處理 HTTP 數據
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	// 監聽 TCP 端口,把監聽器交給 *http.Server 使用
	ln, err := net.Listen("tcp", ":12345")
	if err != nil {
		panic("listen :12345 fail:" + err.Error())
	}
	if err = server.Serve(ln); err != nil {
		fmt.Println("start http server fail:", err)
	}
}

有朋友用 Beego 的時候但願同時監聽兩個端口提供同樣數據操做的 HTTP 服務。這個需求就能夠利用 *http.Server 來實現:

import (
	"fmt"
	"net/http"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/context"
)

func main() {
	beego.Get("/", func(ctx *context.Context) {
		ctx.WriteString("abc")
	})
	go func() { // server 的 ListenAndServe 是阻塞的,應該在另外一個 goroutine 開啓另外一個server
		server2 := &http.Server{
			Handler: beego.BeeApp.Handlers, // 使用實現 http.Handler 的結構處理 HTTP 數據
			Addr:    ":54321",
		}
		if err := server2.ListenAndServe(); err != nil {
			fmt.Println("start http server2 fail:", err)
		}
	}()
	server1 := &http.Server{
		Handler: beego.BeeApp.Handlers, // 使用實現 http.Handler 的結構處理 HTTP 數據
		Addr:    ":12345",
	}
	if err := server1.ListenAndServe(); err != nil {
		fmt.Println("start http server1 fail:", err)
	}
}

這樣訪問 http://localhost:12345http://localhost:54321 均可以看到返回 abc 的內容。

HTTPS

隨着互聯網安全的問題日益嚴重,許多的網站開始使用 HTTPS 提供服務。Go 建立一個 HTTPS 服務是很簡便的:

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, HTTPS Server")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServeTLS(":12345",
		"server.crt",
		"server.key", nil)
}

ListenAndServeTLS 新增了兩個參數 certFilekeyFile。HTTPS的數據傳輸是加密的。實際使用中,HTTPS利用的是對稱與非對稱加密算法結合的方式,須要加密用的公私密鑰對進行加密,也就是 server.crtserver.key 文件。具體的生成能夠閱讀 openssl 的文檔。

關於 Go 和 HTTPS 的內容,能夠閱讀 Tony BaiGo 和 HTTPS

總結

Go 的 net/http 包爲開發者提供不少便利的方法的,能夠直接開發不復雜的 Web 應用。若是須要複雜的路由功能,及更加集成和簡便的 HTTP 操做,推薦使用一些 Web 框架。

各類 Web 框架 : awesome-go#web-frameworks

我的小站:fuxiaohei.me

相關文章
相關標籤/搜索