GO 語言 Web 開發實戰一

阿兵雲原生發表於2023-01-20

xdm,我們今天分享一個 golang web 實戰的 demo

go 的 http 包,以前都有或多或多的提到一些,也有一些筆記在我們的歷史文章中,今天來一個簡單的實戰

HTTP 程式設計 Get

先來一個 小例子,簡單的寫一個 Get 請求

  • 拿控制程式碼
  • 設定監聽地址和埠
  • 進行資料處理

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func myHandle(w http.ResponseWriter, req *http.Request){
        defer req.Body.Close()
        par := req.URL.Query()
        fmt.Println("par :",par)
        //回寫資料
        fmt.Fprintln(w,"name",par.Get("name"),"hobby",par.Get("hobby"))
    
    }
    
    // server 端
    func main() {
    
        http.HandleFunc("/", myHandle)
    
        err := http.ListenAndServe("0.0.0.0:9999", nil)
        if err != nil{
            fmt.Printf("ListenAndServe err : %v",err)
            return
        }
    
    }
    

上述的程式碼比較簡單,就是一個簡單的 http get 請求 , 主要處理資料的是 myHandle 函式

Client 客戶端 實現方法 get

  • client.go

    • get方法、post方法、patch方法、head方法、put方法等等,用法基本一致
    • 設定url
    • get (或者其他方法)方法請求 url
    • 處理資料
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "net/http"
        "net/url"
    )
    
    //httpserver 端
    func main() {
    
        //1.處理請求引數
        params := url.Values{}
        params.Set("name", "xiaomotong")
        params.Set("hobby", "乒乓球")
    
        //2.設定請求URL
        rawUrl := "http://127.0.0.1:9999"
        reqURL, err := url.ParseRequestURI(rawUrl)
        if err != nil {
            fmt.Printf("url.ParseRequestURI() 函式執行錯誤,錯誤為:%v\n", err)
            return
        }
    
        //3.整合請求URL和引數
        reqURL.RawQuery = params.Encode()
    
        //4.傳送HTTP請求
        // reqURL.String() String將URL重構為一個合法URL字串。
        fmt.Println("Get url:", reqURL.String())
        resp, err := http.Get(reqURL.String())
        if err != nil {
            fmt.Printf("http.Get()函式執行錯誤,錯誤為:%v\n", err)
            return
        }
        defer resp.Body.Close()
    
        //5.一次性讀取響應的所有內容
        body, err := ioutil.ReadAll(resp.Body)
    
        if err != nil {
            fmt.Printf("ioutil.ReadAll()函式執行出錯,錯誤為:%v\n", err)
            return
        }
    
        fmt.Println("Response: ", string(body))
    }
    

上述編碼中有使用到 reqURL.RawQuery = params.Encode()

Encode 方法將請求引數編碼為 url 編碼格式 ("a=123&b=345"),編碼時會以鍵進行排序

常見狀態碼

  • http.StatusContinue = 100
  • http.StatusOK = 200
  • http.StatusFound = 302
  • http.StatusBadRequest = 400
  • http.StatusUnauthorized = 401
  • http.StatusForbidden = 403
  • http.StatusNotFound = 404
  • http.StatusInternalServerError = 500

HTTP 程式設計 Post 方法

  • 編寫 server 程式碼 server.go
  • 設定控制程式碼
  • 設定監聽地址和埠
  • 處理相應資料

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "net/http"
    )
    
    func handPost(w http.ResponseWriter, req *http.Request) {
        defer req.Body.Close()
    
        if req.Method == http.MethodPost {
            b, err := ioutil.ReadAll(req.Body)
            if err != nil {
                fmt.Printf("ReadAll err %v", err)
                return
            }
    
            fmt.Println(string(b))
    
            resp := `{"status":"200 OK"}`
    
            w.Write([]byte(resp))
    
            fmt.Println("reponse post func")
        } else {
            fmt.Println("can't handle ", req.Method)
            w.Write([]byte(http.StatusText(http.StatusBadRequest)))
        }
    }
    
    //post server
    
    func main() {
    
        http.HandleFunc("/", handPost)
    
        err := http.ListenAndServe("0.0.0.0:9999", nil)
        if err != nil {
            fmt.Printf("ListenAndServe err %v", err)
            return
        }
    }
    

Client 客戶端 實現

  • client.go

    • get方法、post方法、patch方法、head方法、put方法等等,用法基本一致
    • 設定 url
    • post 方法請求
    • 處理資料
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "net/http"
        "strings"
    )
    
    //post client
    
    func main() {
    
        reqUrl := "http://127.0.0.1:9999"
        contentType := "application/json"
        data := `{"name":"xiaomotong","age":18}`
    
        resp, err := http.Post(reqUrl, contentType, strings.NewReader(data))
        if err != nil {
            fmt.Printf("Post err %v", err)
            return
        }
        defer resp.Body.Close()
    
        b, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("ReadAll err %v", err)
            return
        }
    
        fmt.Println(string(b))
    
    }
    

上述 post 方法的編碼 明顯 比 get 方法的編碼傳參多了很多,我們一起來看看官方原始碼是如何做的

func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
    return DefaultClient.Post(url, contentType, body)
}
  • url

請求地址

  • contentType

內容的型別,例如 application/json

  • body

具體的請求體內容,此處是 io.Reader 型別的,因此我們傳入資料的時候,也需要轉成這個型別

表單 form 的處理

既然是 web 相關的實戰,表單肯定是一個離不開的話題 , golang 裡面當然有對錶單的實際處理功能

  • 前面邏輯一樣,服務端開啟服務,監聽埠
  • 每個路由對應這個處理函式
  • 處理函式中 request.ParseForm() 解析表單的具體資料
package main

import (
    "fmt"
    "io"
    "net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="out"/>
                     <input type="submit" value="Submit"/>
             </form></html></body>`

func HomeServer(w http.ResponseWriter, request *http.Request) {
     io.WriteString(w, "<h1>/test1 或者/test2</h1>")
}

func SimpleServer(w http.ResponseWriter, request *http.Request) {
    io.WriteString(w, "<h1>hello, xiaomotong</h1>")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    switch request.Method {
    case "GET":
        io.WriteString(w, form)
    case "POST":
        request.ParseForm()
        fmt.Println("request.Form[in]:", request.Form["in"])
        io.WriteString(w, request.Form["in"][0])
        io.WriteString(w, "\n")
        io.WriteString(w, request.Form["out"][0])
    }
}
func main() {
    http.HandleFunc("/", HomeServer)
    http.HandleFunc("/test1", SimpleServer)
    http.HandleFunc("/test2", FormServer)
    err := http.ListenAndServe(":9999", nil)
    if err != nil {
        fmt.Printf("http.ListenAndServe()函式執行錯誤,錯誤為:%v\n", err)
        return
    }
}

上述編碼解析表單的邏輯是:

對於 POST、PUT 和P ATCH 請求,它會讀取請求體並解析它,作為一個表單,會將結果放入r.PostFormr.Form

請求體 r.Form 中的引數優先於 URL 查詢字串值

先來看看 Request 的結構 ,引數會比較多

type Request struct {
    Method string
    URL *url.URL
   
    .... 此處省略多行 ...
    
    ContentLength int64

    //Form包含解析過的表單資料,包括URL欄位的查詢引數和PATCH、POST或PUT表單資料。
    //此欄位僅在呼叫 ParseForm 後可用
    Form url.Values

    //PostForm包含來自 PATCH、POST或PUT主體引數的解析表單資料。
    //此欄位僅在呼叫 ParseForm 後可用。
    PostForm url.Values

    //MultipartForm是解析的多部分表單,包括檔案上傳。
    //該欄位僅在呼叫 parsemmultipartform 後可用。
    MultipartForm *multipart.Form

    Trailer Header
    RemoteAddr string
    RequestURI string
    TLS *tls.ConnectionState
    Cancel <-chan struct{}
    Response *Response
    ctx context.Context
}

下面是具體實現的原始碼,感興趣的 xdm 可以開啟 goland 看起來

實際處理邏輯在 func parsePostForm(r *Request) (vs url.Values, err error) {

這裡需要注意

  • 請求提的大小上限為10MB , 需要注意請求體的大小是否會被 MaxBytesReader 限制

模板

聽到 模板 這個名詞應該不陌生了吧,很多元件或者語言裡面都有模板的概念

感興趣的可以琢磨一下,我們放在下一篇補充

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是阿兵雲原生,歡迎點贊關注收藏,下次見~

相關文章