easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求
之前這個系列的文章一直在講用Go語言怎么編寫HTTP服務器來提供服務,如何給服務器配置路由來匹配請求到對應的處理程序,如何添加中間件把一些通用的處理任務從具體的Handler中解耦出來,以及如何更規范地在項目中應用數據庫。不過一直漏掉了一個環節是服務器接收到請求后如何解析請求拿到想要的數據,Go語言使用net/http包中的Request結構體對象來表示HTTP請求,通過Request結構對象上定義的方法和數據字段,應用程序能夠便捷地訪問和設置HTTP請求中的數據。
一般服務端解析請求的需求有如下幾種
- HTTP請求頭中的字段值
- URL 查詢字符串中的字段值
- 請求體中的Form表單數據
- 請求體中的JSON格式數據
- 讀取客戶端的上傳的文件
今天這篇文章我們就按照這幾種常見的服務端對HTTP請求的操作來說一下服務器應用程序如何通過Request對象解析請求頭和請求體。
Request 結構定義
在說具體操作的使用方法之前我們先來看看net/http包中Request結構體的定義,了解一下Request擁有什么樣的數據結構。Request結構在源碼中的定義如下。
type Request struct {Method stringURL *url.URLProto string // "HTTP/1.0"ProtoMajor int // 1ProtoMinor int // 0Header HeaderBody io.ReadCloserGetBody func() (io.ReadCloser, error)ContentLength int64TransferEncoding []stringClose boolHost stringForm url.ValuesPostForm url.ValuesMultipartForm *multipart.FormTrailer HeaderRemoteAddr stringRequestURI stringTLS *tls.ConnectionStateCancel <-chan struct{}Response *Responsectx context.Context }我們快速地了解一下每個字段大致的含義,了解了每個字段的含義在不同的應用場景下需要讀取訪問HTTP請求的不同部分時就能夠有的放矢了。
Method
指定HTTP方法(GET,POST,PUT等)。
URL
URL指定要請求的URI(對于服務器請求)或要訪問的URL(用于客戶請求)。它是一個表示URL的類型url.URL的指針,url.URL的結構定義如下:
type URL struct {Scheme stringOpaque stringUser *UseriHost stringPath stringRawPath stringForceQuery bool RawQuery stringFragment string }Proto
Proto,ProtoMajor,ProtoMinor三個字段表示傳入服務器請求的協議版本。對于客戶請求,這些字段將被忽略。 HTTP客戶端代碼始終使用HTTP / 1.1或HTTP / 2。
Header
Header包含服務端收到或者由客戶端發送的HTTP請求頭,該字段是一個http.Header類型的指針,http.Header類型的聲明如下:
type Header map[string][]string是map[string][]string類型的別名,http.Header類型實現了GET,SET,Add等方法用于存取請求頭。如果服務端收到帶有如下請求頭的請求:
Host: example.com accept-encoding: gzip, deflate Accept-Language: en-us fOO: Bar foo: two那么Header的值為:
Header = map[string][]string{"Accept-Encoding": {"gzip, deflate"},"Accept-Language": {"en-us"},"Foo": {"Bar", "two"}, }對于傳入的請求,Host標頭被提升為Request.Host字段,并將其從Header對象中刪除。HTTP 定義頭部的名稱是不區分大小寫的。Go使用CanonicalHeaderKey實現的請求解析器使得請求頭名稱第一個字母以及跟隨在短橫線后的第一個字母大寫其他都為小寫形式,比如:Content-Length。對于客戶端請求,某些標頭,例如Content-Length和Connection會在需要時自動寫入,并且標頭中的值可能會被忽略。
Body
這個字段的類型是io.ReadCloser,Body是請求的主體。對于客戶端發出的請求,nil主體表示該請求沒有Body,例如GET請求。 HTTP客戶端的傳輸會負責調用Close方法。對于服務器接收的請求,請求主體始終為非nil,但如果請求沒有主體,則將立即返回EOF。服務器將自動關閉請求主體。服務器端的處理程序不需要關心此操作。
GetBody
客戶端使用的方法的類型,其聲明為:
GetBody func() (io.ReadCloser, error)ContentLength
ContentLength記錄請求關聯內容的長度。值-1表示長度未知。值>=0表示從Body 中讀取到的字節數。對于客戶請求,值為0且非nil的Body也會被視為長度未知。
TransferEncoding
TransferEncoding為字符串切片,其中會列出從最外層到最內層的傳輸編碼,TransferEncoding通常可以忽略;在發送和接收請求時,分塊編碼會在需要時自動被添加或者刪除。
Close
Close表示在服務端回復請求或者客戶端讀取到響應后是否要關閉連接。對于服務器請求,HTTP服務器會自動處理 并且處理程序不需要此字段。對于客戶請求,設置此字段為true可防止重復使用到相同主機的請求之間的TCP連接,就像已設置Transport.DisableKeepAlives一樣。
Host
對于服務器請求,Host指定URL所在的主機,為防止DNS重新綁定攻擊,服務器處理程序應驗證Host標頭具有的值。 http庫中的ServeMux(復用器)支持注冊到特定Host的模式,從而保護其注冊的處理程序。對于客戶端請求,Host可以用來選擇性地覆蓋請求頭中的Host,如果不設置,Request.Write使用URL.Host來設置請求頭中的Host。
Form
Form包含已解析的表單數據,包括URL字段的查詢參數以及PATCH,POST或PUT表單數據。此字段僅在調用Request.ParseForm之后可用。HTTP客戶端會忽略Form并改用Body。Form字段的類型是url.Values類型的指針。url.Values類型的聲明如下:
type Values map[string][]string也是map[string][]string類型的別名。url.Values類型實現了GET,SET,Add,Del等方法用于存取表單數據。
PostForm
PostForm類型與Form字段一樣,包含來自PATCH,POST的已解析表單數據或PUT主體參數。此字段僅在調用ParseForm之后可用。HTTP客戶端會忽略PostForm并改用Body。
MultipartForm
MultipartForm是已解析的多部分表單數據,包括文件上傳。僅在調用Request.ParseMultipartForm之后,此字段才可用。HTTP客戶端會忽略MultipartForm并改用Body。該字段的類型是*multipart.Form。
RemoteAddr
RemoteAddr允許HTTP服務器和其他軟件記錄發送請求的網絡地址,通常用于記錄。 net/http包中的HTTP服務器在調用處理程序之前將RemoteAddr設置為“ IP:端口”, HTTP客戶端會忽略此字段。
RequestURI
RequestURI是未修改的request-target客戶端發送的請求行(RFC 7230,第3.1.1節)。在服務器端,通常應改用URL字段。在HTTP客戶端請求中設置此字段是錯誤的。
Response
Response字段類型為*Response,它指定了導致此請求被創建的重定向響應,此字段僅在客戶端發生重定向時被填充。
ctx
ctx 是客戶端上下文或服務器上下文。它應該只通過使用WithContext復制整個Request進行修改。這個字段未導出以防止人們錯誤使用Context并更改同一請求的調用方所擁有的上下文。
讀取請求頭
上面分析了Go將HTTP請求頭存儲在Request結構體對象的Header字段里,Header字段實質上是一個Map,請求頭的名稱為Mapkey,Map Value的類型為字符串切片,有的請求頭像Accept會有多個值,在切片中就對應多個元素。
Header類型的Get方法可以獲取請求頭的第一個值,
func exampleHandler(w http.ResponseWriter, r *http.Request) {ua := r.Header.Get("User-Agent")... }或者是獲取值時直接通過key獲取對應的切片值就好,比如將上面的改為:
ua := r.Header["User-Agent"]下面我們寫個遍歷請求頭信息的示例程序,同時也會通上面介紹的Request結構中定義的Method,URL,Host,RemoteAddr等字段把請求的通用信息打印出來。在我們一直使用的http_demo項目中增加一個DisplayHeadersHandler,其源碼如下:
package handlerimport ("fmt""net/http" )func DisplayHeadersHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Method: %s URL: %s Protocol: %s n", r.Method, r.URL, r.Proto)// 遍歷所有請求頭for k, v := range r.Header {fmt.Fprintf(w, "Header field %q, Value %qn", k, v)}fmt.Fprintf(w, "Host = %qn", r.Host)fmt.Fprintf(w, "RemoteAddr= %qn", r.RemoteAddr)// 通過 Key 獲取指定請求頭的值fmt.Fprintf(w, "nnFinding value of "Accept" %q", r.Header["Accept"]) }將其處理程序綁定到/index/display_headers路由上:
indexRouter.HandleFunc("/display_headers", handler.DisplayHeadersHandler)然后啟動項目,打開瀏覽器訪問:
http://localhost:8000/index/display_headers可以看到如下輸出:
http_demo項目中已經添加了本文中所有示例的源碼,關注文末公眾號回復gohttp06可以獲取源碼的下載鏈接。獲取URL參數值
GET請求中的URL查詢字符串中的參數可以通過url.Query(),我們來看一下啊url.Query()函數的源碼:
func (u *URL) Query() Values {v, _ := ParseQuery(u.RawQuery)return v }它通過ParseQuery函數解析URL參數然后返回一個url.Values類型的值。url.Values類型上面我們已經介紹過了是map[string][]string類型的別名,實現了GET,SET,Add,Del等方法用于存取數據。
所以我們可以使用r.URL.Query().Get("ParamName")獲取參數值,也可以使用r.URL.Query()["ParamName"]。兩者的區別是Get只返回切片中的第一個值,如果參數對應多個值時(比如復選框表單那種請求就是一個name對應多個值),記住要使用第二種方式。
我們通過運行一個示例程序display_url_params.go來看一下兩種獲取URL參數的區別
package handlerimport ( "fmt" "net/http" )func DisplayUrlParamsHandler(w http.ResponseWriter, r *http.Request) {for k, v := range r.URL.Query() {fmt.Fprintf(w, "ParamName %q, Value %qn", k, v)fmt.Fprintf(w, "ParamName %q, Get Value %qn", k, r.URL.Query().Get(k))} }將其處理程序綁定到/index/display_url_params路由上:
indexRouter.HandleFunc("/display_url_params", handler.DisplayUrlParamsHandler)打開瀏覽器訪問
http://localhost:8000/index/display_url_params?a=b&c=d&a=c瀏覽器會輸出:
ParamName "a", Value ["b" "c"] ParamName "a", Get Value "b" ParamName "c", Value ["d"] ParamName "c", Get Value "d"我們為參數a傳遞了兩個參數值,可以看到通過url.Query.Get()只能讀取到第一個參數值。
獲取表單中的參數值
Request結構的Form字段包含已解析的表單數據,包括URL字段的查詢參數以及PATCH,POST或PUT表單數據。此字段僅在調用Request.ParseForm之后可用。不過Request對象提供一個FormValue方法來獲取指定名稱的表單數據,FormValue方法會根據Form字段是否有設置來自動執行ParseForm方法。
func (r *Request) FormValue(key string) string {if r.Form == nil {r.ParseMultipartForm(defaultMaxMemory)}if vs := r.Form[key]; len(vs) > 0 {return vs[0]}return "" }可以看到FormValue方法也是只返回切片中的第一個值。如果需要獲取字段對應的所有值,那么需要通過字段名訪問Form字段。如下:
獲取表單字段的單個值
r.FormValue(key)獲取表單字段的多個值
r.ParseForm()r.Form["key"]下面是我們的示例程序,以及對應的路由:
//handler/display_form_data.go package handlerimport ("fmt""net/http" )func DisplayFormDataHandler(w http.ResponseWriter, r *http.Request) {if err := r.ParseForm(); err != nil {panic(err)}for key, values := range r.Form {fmt.Fprintf(w, "Form field %q, Values %qn", key, values)fmt.Fprintf(w, "Form field %q, Value %qn", key, r.FormValue(key))} }//router.go indexRouter.HandleFunc("/display_form_data", handler.DisplayFormDataHandler)我們在命令行中使用cURL命令發送表單數據到處理程序,看看效果。
curl -X POST -d 'username=James&password=123' http://localhost:8000/index/display_form_data返回的響應如下:
Form field "username", Values ["James"] Form field "username", Value "James" Form field "password", Values ["123"] Form field "password", Value "123"獲取 Cookie
Request對象專門提供了一個Cookie方法用來訪問請求中攜帶的Cookie數據,方法會返回一個*Cookie類型的值以及error。Cookie類型的定義如下:
type Cookie struct {Name stringValue stringPath string // optionalDomain string // optionalExpires time.Time // optionalRawExpires string // for reading cookies onlyMaxAge intSecure boolHttpOnly boolSameSite SameSiteRaw stringUnparsed []string }所以要讀取請求中指定名稱的Cookie值,只需要
cookie, err := r.Cookie(name) // 錯誤檢查 ... value := cookie.ValueRequest.Cookies()方法會返回[]*Cookie切片,其中會包含請求中所有的Cookie
下面的示例程序,會打印請求中所有的Cookie
// handler/read_cookie.go package handlerimport ("fmt""net/http" )func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {for _, cookie := range r.Cookies() {fmt.Fprintf(w, "Cookie field %q, Value %qn", cookie.Name, cookie.Value)} } //router/router.go indexRouter.HandleFunc("/read_cookie", handler.ReadCookieHandler)我們通過cURL在命令行請求http://localhost:8000/index/read_cookie
curl --cookie "USER_TOKEN=Yes" http://localhost:8000/index/read_cookie執行命令后會返回:
Cookie field "USER_TOKEN", Value "Yes"解析請求體中的JSON數據
現在前端都傾向于把請求數據以JSON格式放到請求主體中傳給服務器,針對這個使用場景,我們需要把請求體作為json.NewDecoder()的輸入流,然后將請求體中攜帶的JSON格式的數據解析到聲明的結構體變量中
//handler/parse_json_request package handlerimport ("encoding/json""fmt""net/http" )type Person struct {Name stringAge int }func DisplayPersonHandler(w http.ResponseWriter, r *http.Request) {var p Person// 將請求體中的 JSON 數據解析到結構體中// 發生錯誤,返回400 錯誤碼err := json.NewDecoder(r.Body).Decode(&p)if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}fmt.Fprintf(w, "Person: %+v", p) }// router/router.go indexRouter.HandleFunc("/parse_json_request", handler.ParseJsonRequestHandler)在命令行里用cURL命令測試我們的程序:
curl -X POST -d '{"name": "James", "age": 18}' -H "Content-Type: application/json" http://localhost:8000/index/parse_json_request返回響應如下:
Person: {Name:James Age:18}%讀取上傳文件
服務器接收客戶端上傳的文件,使用Request定義的FormFile()方法。該方法會自動調用r.ParseMultipartForm(32 << 20)方法解析請求多部表單中的上傳文件,并把文件可讀入內存的大小設置為32M(32向左位移20位),如果內存大小需要單獨設置,就要在程序里單獨調用ParseMultipartForm()方法才行。
func ReceiveFile(w http.ResponseWriter, r *http.Request) {r.ParseMultipartForm(32 << 20) var buf bytes.Bufferfile, header, err := r.FormFile("file")if err != nil {panic(err)}defer file.Close()name := strings.Split(header.Filename, ".")fmt.Printf("File name %sn", name[0])io.Copy(&buf, file)contents := buf.String()fmt.Println(contents)buf.Reset()return }Go語言解析HTTP請求比較常用的方法我們都介紹的差不多了。因為想總結全一點,篇幅還是有點長,不過整體不難懂,而且也可以下載程序中的源碼自己運行調試,動手實踐一下更有助于理解吸收。HTTP客戶端發送請求要設置的內容也只今天講的Request結構體的字段,Request對象也提供了一些設置相關的方法供開發人員使用,今天就先說這么多了。
關注下方公眾號回復gohttp06可以下載文章中項目的源碼,趕快下載下來自己試一試吧。
前文回顧
深入學習用Go編寫HTTP服務器
Web服務器路由
十分鐘學會用Go編寫Web中間件
Go Web編程--應用ORM
總結
以上是生活随笔為你收集整理的easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 默纳克系统服务器怎么改不了参数,默纳克3
- 下一篇: iphone字体_iPhone 适合老人