更多個人筆記:(僅供參考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note
針對GO中net/http包的學習筆記
基礎快速了解
創建簡單的GOHTTP服務
func main() { http.HandleFunc("/hello", sayHello) http.ListenAndServe(":8080", nil) //創建基本服務
} func sayHello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!"))
}
訪問8080/hello進行測試
Handler接口定義:(這部分后面又詳細解釋)
type Handler interface { ServeHTTP(ResponseWriter, *Request)
}
//只要有ServeHTTP方法就行
可以自己實現這個接口
同時http提供了handlerFunc結構體
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
//本質上就是調用自身,因為也是一個函數,不過serveHTTP的內容自己可以定義改動
和之前的HandleFunc區分,HandleFunc是用來給不同路徑綁定方法的,少一個r
那么只要滿足Handlerfunc 的簽名形式,就可以進行類型轉換
- 類型轉換的正常的理解例子
type yes func(int, int) int
func (y yes) add(a, b int) int { return a + b
}
func multiply(a, b int) int { fmt.Println(a * b)return a * b
}
func main() { multiply(1, 2) //2ans := yes(multiply) //將multiply進行轉換 res := ans.add(1, 2) fmt.Println(res) //3
}
http.HandleFunc("/hello", hello)
這個后面的簽名只要是 func(ResponseWriter, *Request)
就可以了
但是
http.ListenAndServe(":8080", referer)
這個后面的函數需要是滿足Handler接口,有serveHTTP方法
嘗試搭建檢測是在query中有name = red
即http://localhost:8080/hello?name=red
發現會有重復覆蓋路由的問題,因為listenandServe會攔截所有的路由,后面再解決
type CheckQueryName struct {wantname stringhandler http.Handler
}func (this *CheckQueryName) ServeHTTP(w http.ResponseWriter, r *http.Request) {queryParams := r.URL.Query() //獲取get請求的queryname := queryParams.Get("name")if name == "red" {this.handler.ServeHTTP(w, r) //其實就是調用本身,下面變為checkforname了} else {w.Write([]byte("not this name"))}
}func checkforname(w http.ResponseWriter, r *http.Request) {w.Write([]byte("check is ok"))
}func hello(w http.ResponseWriter, r *http.Request) {w.Write([]byte("hello"))
}func main() {thecheck := &CheckQueryName{ //用&因為serveHTTP方法定義在指針接收器上wantname: "red",handler: http.HandlerFunc(checkforname),}http.HandleFunc("/hello", hello) //滿足func(ResponseWriter, *Request)簽名就可以http.ListenAndServe(":8080", thecheck) //直接監視8080的所有端口,攔截所有路由
}
編寫簡單的GET請求客戶端
利用defaultclient或者自己定義client都可以
func main() {resp, err := http.DefaultClient.Get("https://api.github.com")if err != nil {panic(err)}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {panic(err)}fmt.Println(string(body))time.Sleep(time.Second * 2) //等待獲取請求
}
但是如果把網址換成baidu.com就會獲取不到,這是因為轉發,以及沒有User-agent的問題
編寫自定義的GET請求客戶端
利用http.Client可以進行自定義
func main() {client := &http.Client{// 允許重定向CheckRedirect: func(req *http.Request, via []*http.Request) error {return nil},}req, err := http.NewRequest("GET", "https://www.baidu.com", nil)if err != nil {panic(err)}// 添加請求頭req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")resp, err := client.Do(req) //do執行HTTP請求的整個周期包括請求準備,建立連接,發送請求,請求重定向,接收響應等等defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {panic(err)}fmt.Println(string(body))time.Sleep(time.Second * 2)
}
發現可以接受到baidu的網頁html信息
編寫默認的post請求客戶端
func main() {postData := strings.NewReader(`{"name": "張三", "age": 25}`)resp, err := http.DefaultClient.Post("http://localhost:8080/users","application/json", postData,)if err != nil {fmt.Printf("POST請求失敗: %v\n", err)return}defer resp.Body.Close()body, _ := ioutil.ReadAll(resp.Body)fmt.Printf("POST響應: %s\n", string(body))
}
string.NewReader是一種方法將格式改為io reader可以讀取的形式,接收的話也可以postData.Read讀取。
這一類的方式比較多,不一一匯總
對應的server.go (需要在終端中go run server.go )兩個終端分別運行服務端,客戶端
func main() {// 處理 /users 路徑的 POST 請求http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {// 只允許 POST 方法if r.Method != http.MethodPost {w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "只支持 POST 方法")return}// 讀取請求體body, err := ioutil.ReadAll(r.Body)if err != nil {w.WriteHeader(http.StatusBadRequest)fmt.Fprintf(w, "讀取請求失敗: %v", err)return}defer r.Body.Close()// 解析 JSON 數據,放入user中var user Userif err := json.Unmarshal(body, &user); err != nil {w.WriteHeader(http.StatusBadRequest) //寫入狀態碼fmt.Fprintf(w, "JSON 解析失敗: %v", err)return}// 設置響應頭w.Header().Set("Content-Type", "application/json")// 構造響應數據response := map[string]interface{}{"message": "success","data": map[string]interface{}{"name": user.Name,"age": user.Age,},}// 返回 JSON 響應json.NewEncoder(w).Encode(response) //將 response 對象轉換為 JSON 格式并寫入響應//等價于:// jsonData, err := json.Marshal(response)// if err != nil {// w.WriteHeader(http.StatusInternalServerError)// return// }// w.Write(jsonData)})// 啟動服務器fmt.Println("服務器啟動在 :8080 端口...")if err := http.ListenAndServe(":8080", nil); err != nil {panic(err)}
}
多路復用器
DefaultServeMux一般不會使用,因為會有沖突等等問題,所以一般用NewServeMux直接創建
type apiHandler struct{}func (apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "application/json")fmt.Fprintf(w, `{"message": "API response"}`)
}func main() {mux := http.NewServeMux()mux.Handle("/api/", apiHandler{}) //多引入結構體,后面會知道有好處// mux.HandleFunc("/api/", func(w http.ResponseWriter, req *http.Request) {// w.Header().Set("Content-Type", "application/json")// fmt.Fprintf(w, `{"message": "API response from HandleFunc"}`)// }) //和上面等效mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {if req.URL.Path != "/" {http.NotFound(w, req)return}fmt.Fprintf(w, "Welcome to the home page!")})fmt.Println("Server running on :8080")http.ListenAndServe(":8080", mux)//用server:= &http.Server創建地址和handler,然后server.ListenAndServe也是一種表現方式}
mux和http.HandleFunc()的區別:
mux 可以創建多個路由器實例,用于不同的目的。同時可以為不同的路由器配置不同的中間件(用著先)
第三方有庫httprouter,比如可以解決url不能是變量代表的問題,了解就行
更多都是使用restful API進行開發的~目前的了解有個概念就行
處理器函數
Handle
注冊處理器過程中調用的函數:Handle
type username struct {name string
}func (this *username) ServeHTTP(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "%s", this.name)
}func main() {mux := http.NewServeMux()mux.Handle("/jack", &username{name: "jack"}) //會調用對應的serveHTTP方法mux.Handle("/lily", &username{name: "lily"})//可以為不同的路徑使用相同的處理器結構體,但傳入不同的參數//這就是比用handleFunc()更靈活的地方server := &http.Server{Addr: ":8080",Handler: mux,}if err := server.ListenAndServe(); err != nil { //防止錯誤panic(err)}
}
HandlleFunc
處理器函數:HandleFunc
(注意不是HandlerFunc,沒有r !! HandleFunc 是處理器函數)
之前已經學習過,定義就是
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
深入源代碼會發現內部也是借助serveMux對象,從而實現了Handler的ServeHTTP()方法的
Handler
Handler就是處理器接口,實現ServeHTTP方法的,之前展示過
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
HandlerFunc
HandlerFunc是結構體,用于實現接口的
定義:
type HandlerFunc func(ResponseWriter, *Request)
用于連接處理器Handle和處理器函數HandleFunc,它實現了 Handler 接口,使得函數可以直接當作處理器使用:
- 理解“連接連接處理器Handle和處理器函數HandleFunc”:(之前也學過)
// 方式一:普通函數
func hello(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello!")
}
// 注冊方式一:將函數轉換為 HandlerFunc
http.Handle("/hello", http.HandlerFunc(hello))// 方式二:直接使用 HandleFunc
http.HandleFunc("/hello", hello)
處理請求
請求分為:請求頭,請求URL,請求體等
html表單的enctype屬性
在postman的body部分可以查看
- application/x-www-form-urlencode
url方式編碼,較為通用,get和post都可以用 - multipart/form-data
通常適配post方法提交 - text/plain
適合傳遞大量數據
ResponseWriter接口涉及方法
- 補充:
fmt.Fprint和fmt.Fprintln能夠寫入ResponseWriter是因為ResponseWriter實現了io.Writer
接口,fmt.Fprint/Fprintln將數據按格式轉換為字節流(如字符串、數字等),最終調用io.Writer的Write方法
Writeheader
curl -i localhost:8080/noAuth
或者使用postman進行驗證
func noAuth(w http.ResponseWriter, r *http.Request) {w.WriteHeader(401)fmt.Fprint(w, "沒有授權,你需要認證后訪問")
}func main() {http.HandleFunc("/noAuth", noAuth)err := http.ListenAndServe(":8080", nil)if err != nil {fmt.Println(err)}
}
Header
調用了Writeheader后的話就不能對響應頭進行修改了
curl -i http://localhost:8081/redirect
(可以直接看到301)或者postman驗證
- 重定向代碼
func Redirect(w http.ResponseWriter, r *http.Request) {w.Header().Set("Location", "http://localhost:8080/hello")// 必須使用包括http的完整的URL!w.WriteHeader(301)
}
func main() {http.HandleFunc("/redirect", Redirect)if err := http.ListenAndServe(":8081", nil); err != nil {panic(err)}
}
- 主服務代碼
func sayHello(w http.ResponseWriter, r *http.Request) {w.Write([]byte("hello !!"))
}func main() {http.HandleFunc("/hello", sayHello)http.ListenAndServe(":8080", nil) //創建基本服務
}
write
之前都有demo,就是寫入返回,注意需要是[]byte()這樣的表示形式
如果不知道content-type格式可以通過數據的前512 比特進行確認
除了一般的文本字符串之外,還可以返回html和json,下面給出json的示范
type language struct {Language string `json:"language"` //反引號// 字段名首字母需要大寫才能被 JSON 序列化!!!!
}func uselanguage(w http.ResponseWriter, r *http.Request) {uselanguageis := language{Language: "en"}message, err := json.Marshal(uselanguageis)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}w.Header().Set("Content-Type", "application/json") //通過json形式傳遞w.Write(message)
}func main() {http.HandleFunc("/lan", uselanguage)if err := http.ListenAndServe(":8080", nil); err != nil {panic(err)}
}
注意一些格式上的細節,比如字段名首字母需要大寫才能被 JSON 序列化