Go語(yǔ)言使用Request,Response處理web頁(yè)面請(qǐng)求
Go語(yǔ)言處理web頁(yè)面請(qǐng)求
Request和Response
http Requset和Response的內(nèi)容包括以下幾項(xiàng):
- Request or response line
- Zero or more headers
- An empty line, followed by …
- … an optional message body
例如一個(gè)http Request:
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1 Host: www.w3.org User-Agent: Mozilla/5.0 (empty line)
如果是POST方法,在empty line后還包含請(qǐng)求體。
一個(gè)http Response:
HTTP/1.1 200 OK Content-type: text/html Content-length: 24204 (empty line) and then 24,204 bytes of HTML code
go http包分為兩種角色:http Client和http Server。http Client可以發(fā)送請(qǐng)求,比如寫(xiě)爬蟲(chóng)程序時(shí)語(yǔ)言扮演的角色就是http Client;http Server用來(lái)提供web服務(wù),可以處理http請(qǐng)求并響應(yīng)。
對(duì)于Request,作為http客戶端(如編寫(xiě)爬蟲(chóng)類(lèi)工具)常需要關(guān)注的是URL和User-Agent以及其它幾個(gè)Header;作為http服務(wù)端(web服務(wù)端,處理請(qǐng)求)常需要關(guān)注的幾項(xiàng)是:
URL Header Body Form,、PostForm、MultipartForm
以下是完整的Request結(jié)構(gòu)以及相關(guān)的函數(shù)、方法:混個(gè)眼熟就好了
type Request struct { Method string URL *url.URL Header Header Body io.ReadCloser GetBody func() (io.ReadCloser, error) // Server: x, Cleint: √ ContentLength int64 TransferEncoding []string Close bool // Server: x, Cleint: √ Host string Form url.Values PostForm url.Values MultipartForm *multipart.Form Trailer Header RemoteAddr string RequestURI string // x TLS *tls.ConnectionState Cancel <-chan struct{} // x Response *Response // x } func NewRequest(method, url string, body io.Reader) (*Request, error) func ReadRequest(b *bufio.Reader) (*Request, error) func (r *Request) AddCookie(c *Cookie) func (r *Request) BasicAuth() (username, password string, ok bool) func (r *Request) Context() context.Context func (r *Request) Cookie(name string) (*Cookie, error) func (r *Request) Cookies() []*Cookie func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) func (r *Request) FormValue(key string) string func (r *Request) MultipartReader() (*multipart.Reader, error) func (r *Request) ParseForm() error func (r *Request) ParseMultipartForm(maxMemory int64) error func (r *Request) PostFormValue(key string) string func (r *Request) ProtoAtLeast(major, minor int) bool func (r *Request) Referer() string func (r *Request) SetBasicAuth(username, password string) func (r *Request) UserAgent() string func (r *Request) WithContext(ctx context.Context) *Request func (r *Request) Write(w io.Writer) error func (r *Request) WriteProxy(w io.Writer) error
注意有哪些字段和方法,字段的詳細(xì)說(shuō)明見(jiàn)go doc http.Request
。上面打了"x"的表示不需要了解的或者廢棄的。
有一個(gè)特殊的字段Trailer
,它是Header類(lèi)型的,顯然它存放的是一個(gè)個(gè)請(qǐng)求header,它表示請(qǐng)求發(fā)送完成之后再發(fā)送的額外的header。對(duì)于Server來(lái)說(shuō),讀取了request.Body之后才會(huì)讀取Trailer。很少有瀏覽器支持HTTP Trailer功能。
以下是完整的Response結(jié)構(gòu)以及相關(guān)的函數(shù)、方法:混個(gè)眼熟就好了
type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 Header Header Body io.ReadCloser ContentLength int64 TransferEncoding []string Close bool Uncompressed bool Trailer Header Request *Request TLS *tls.ConnectionState } func Get(url string) (resp *Response, err error) func Head(url string) (resp *Response, err error) func Post(url string, contentType string, body io.Reader) (resp *Response, err error) func PostForm(url string, data url.Values) (resp *Response, err error) func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) func (r *Response) Cookies() []*Cookie func (r *Response) Location() (*url.URL, error) func (r *Response) ProtoAtLeast(major, minor int) bool func (r *Response) Write(w io.Writer) error
其實(shí)有些直接從字面意思看就知道了。
Http Header
Request和Response結(jié)構(gòu)中都有Header字段,Header是一個(gè)map結(jié)構(gòu)。
type Header map[string][]string A Header represents the key-value pairs in an HTTP 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
key是Header字段名,value是Header字段的值,同個(gè)字段多個(gè)值放在string的slice中。
Add()、Del()、Get()、Set()意義都很明確。
Write()是將Header寫(xiě)進(jìn)Writer中,比如從網(wǎng)絡(luò)連接中發(fā)送出去。WriteSubSet()和Write()類(lèi)似,但可以指定exclude[headerkey]==true
排除不寫(xiě)的字段。
下面是一個(gè)示例:
package main import ( "fmt" "net/http" ) func headers(w http.ResponseWriter, r *http.Request) { for key := range r.Header { fmt.Fprintf(w, "%s: %s\n", key, r.Header[key]) } fmt.Fprintf(w, "--------------\n") fmt.Fprintf(w, "the key: %s\n", r.Header["Accept-Encoding"]) fmt.Fprintf(w, "the key: %s\n", r.Header.Get("Accept-Encoding")) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/headers", headers) server.ListenAndServe() }
瀏覽器中訪問(wèn)http://127.0.0.1:8080/headers
的結(jié)果:
Connection: [keep-alive] Cache-Control: [max-age=0] Upgrade-Insecure-Requests: [1] User-Agent: [Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36] Accept: [text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding: [gzip, deflate, br] Accept-Language: [zh-CN,zh;q=0.9,en;q=0.8] -------------- the key: [gzip, deflate, br] the key: gzip, deflate, br
Http Body
Request和Response結(jié)構(gòu)中都有Body字段,它們都是io.ReadCloser接口類(lèi)型。從名字可以看出,io.ReadCloser由兩個(gè)接口組成:Reader和Closer,意味著它實(shí)現(xiàn)了Reader接口的Read()方法,也實(shí)現(xiàn)了Closer接口的Close()方法。這意味著B(niǎo)ody的實(shí)例可以調(diào)用Read()方法,也可以調(diào)用Close()方法。
例如,下面寫(xiě)一個(gè)handler,從請(qǐng)求中讀取Body并輸出:
package main import ( "fmt" "net/http" ) func body(w http.ResponseWriter, r *http.Request) { len := r.ContentLength body := make([]byte, len) r.Body.Read(body) fmt.Fprintf(w, "%s\n", string(body)) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/body", body) server.ListenAndServe() }
因?yàn)槭褂肏TTP Get方法的Request沒(méi)有Body,所以這里使用curl的"-d"選項(xiàng)來(lái)構(gòu)造一個(gè)POST請(qǐng)求,并發(fā)送Request Body:
$ curl -id "name=lognshuai&age=23" 127.0.0.1:8080/body HTTP/1.1 200 OK Date: Mon, 26 Nov 2018 09:04:40 GMT Content-Length: 22 Content-Type: text/plain; charset=utf-8 name=lognshuai&age=23
Go和HTML Form
在Request結(jié)構(gòu)中,有3個(gè)和form有關(guān)的字段:
// Form字段包含了解析后的form數(shù)據(jù),包括URL的query、POST/PUT提交的form數(shù)據(jù) // 該字段只有在調(diào)用了ParseForm()之后才有數(shù)據(jù) Form url.Values // PostForm字段不包含URL的query,只包括POST/PATCH/PUT提交的form數(shù)據(jù) // 該字段只有在調(diào)用了ParseForm()之后才有數(shù)據(jù) PostForm url.Values // Go 1.1 // MultipartForm字段包含multipart form的數(shù)據(jù) // 該字段只有在調(diào)用了ParseMultipartForm()之后才有數(shù)據(jù) MultipartForm *multipart.Form
所以,一般的邏輯是:
- 先調(diào)用ParseForm()或ParseMultipartForm()解析請(qǐng)求中的數(shù)據(jù)
- 按需訪問(wèn)Request結(jié)構(gòu)中的Form、PostForm或MultipartForm字段
除了先解析再訪問(wèn)字段的方式,還可以直接使用Request的方法:
- FormValue(key)
- PostFormValue(key)
稍后解釋這兩個(gè)方法。
取Form和PostForm字段
給定一個(gè)html文件,這個(gè)html文件里是form表單:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Go Web</title> </head> <body> <form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="name" value="longshuai"/> <input type="text" name="age" value="23"/> <input type="submit"/> </form> </body> </html>
在這個(gè)form里,action指定了要訪問(wèn)的url,其中path=process,query包含name和boyfriend兩個(gè)key。除此之外,form表單的input屬性里,也定義了name和age兩個(gè)key,由于method為post,這兩個(gè)key是作為request body發(fā)送的,且因?yàn)閑nctype指定為application/x-www-form-urlencoded
,這兩個(gè)key會(huì)按照URL編碼的格式進(jìn)行組織。
下面是web handler的代碼:
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Fprintf(w, "%s\n", r.Form) fmt.Fprintf(w, "%s\n", r.PostForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
上面先使用ParseForm()方法解析Form,再訪問(wèn)Request中的Form字段和PostForm字段。
打開(kāi)前面的Html文件,點(diǎn)擊"提交"后,將輸出:
map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] map[name:[longshuai] age:[23]]
如果這時(shí),將application/x-www-form-urlencoded
改成multipart/form-data
,再點(diǎn)擊提交,將輸出:
map[name:[xiaofang] boyfriend:[longshuai]] map[]
顯然,使用multipart/form-data
編碼form的時(shí)候,編碼的內(nèi)容沒(méi)有放進(jìn)Form和PostForm字段中,或者說(shuō)編碼的結(jié)果沒(méi)法放進(jìn)這兩個(gè)字段中。
取MultipartForm字段
要取MultipartForm字段的數(shù)據(jù),先使用ParseMultipartForm()方法解析Form,解析時(shí)會(huì)讀取所有數(shù)據(jù),但需要指定保存在內(nèi)存中的最大字節(jié)數(shù),剩余的字節(jié)數(shù)會(huì)保存在臨時(shí)磁盤(pán)文件中。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(1024) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
將html文件的enctype改為multipart/form-data
后,重新點(diǎn)開(kāi)html文件,將輸出:
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
前兩行結(jié)果意味著ParseMultipartForm()方法也調(diào)用了ParseForm()方法,使得除了設(shè)置MultipartForm字段,也會(huì)設(shè)置Form字段和PostForm字段。
注意上面的第三行,返回的是一個(gè)struct,這個(gè)struct中有兩個(gè)map,第一個(gè)map是來(lái)自form的key/value,第二個(gè)map為空,這個(gè)見(jiàn)后面的File。
最后還需注意的是,enctype=multipart/form-data
和enctype=application/x-www-form-urlencoded
時(shí),Request.Form字段中key的保存順序是不一致的:
// application/x-www-form-urlencoded map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] // multipart/form-data map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
FormValue()和PostFormValue()
前面都是先調(diào)用ParseForm()或ParseMultipartForm()解析Form后再調(diào)用Request中對(duì)應(yīng)字段的。還可以直接調(diào)用FormValue()或PostFormValue()方法。
- FormValue(key)
- PostFormValue(key)
這兩個(gè)方法在需要時(shí)會(huì)自動(dòng)調(diào)用ParseForm()或ParseMultipartForm(),所以使用這兩個(gè)方法取Form數(shù)據(jù)的時(shí)候,可以不用顯式解析Form。
FormValue()返回form數(shù)據(jù)和url query組合后的第一個(gè)值。要取得完整的值,還是需要訪問(wèn)Request.Form或Request.PostForm字段。但因?yàn)镕ormValue()已經(jīng)解析過(guò)Form了,所以無(wú)需再顯式調(diào)用ParseForm()再訪問(wèn)request中Form相關(guān)字段。
PostFormValue()返回form數(shù)據(jù)的第一個(gè)值,因?yàn)樗荒茉L問(wèn)form數(shù)據(jù),所以忽略URL的query部分。
先看FormValue()方法的使用。注意,下面調(diào)用了FormValue()之后沒(méi)有調(diào)用ParseForm()和ParseMultipartForm()解析Form,就可以直接訪問(wèn)Request中和Form相關(guān)的3個(gè)字段。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w,"%s\n",r.FormValue("name")) fmt.Fprintf(w,"%s\n",r.FormValue("age")) fmt.Fprintf(w,"%s\n",r.FormValue("boyfriend")) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
當(dāng)enctype=multipart/form-data
時(shí),會(huì)自動(dòng)調(diào)用ParseMultipartForm(),輸出結(jié)果:
xiaofang 23 longshuai map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
當(dāng)enctype=application/x-www-form-urlencoded
時(shí),會(huì)自動(dòng)調(diào)用ParseForm(),輸出結(jié)果:
longshuai 23 longshuai map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] map[name:[longshuai] age:[23]] %!s(*multipart.Form=<nil>)
仍然注意,因?yàn)閮煞Nenctype導(dǎo)致的Request.Form存儲(chǔ)key時(shí)的順序不一致,使得訪問(wèn)有多個(gè)值的key得到的結(jié)果不一致。
再看PostFormValue()方法的使用。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w,"%s\n",r.PostFormValue("name")) fmt.Fprintf(w,"%s\n",r.PostFormValue("age")) fmt.Fprintf(w,"%s\n",r.PostFormValue("boyfriend")) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
當(dāng)enctype=multipart/form-data
時(shí),會(huì)自動(dòng)調(diào)用ParseMultipartForm(),輸出結(jié)果:
longshuai 23 map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
當(dāng)enctype=application/x-www-form-urlencoded
時(shí),會(huì)自動(dòng)調(diào)用ParseForm(),輸出結(jié)果:
longshuai 23 map[age:[23] boyfriend:[longshuai] name:[longshuai xiaofang]] map[name:[longshuai] age:[23]] %!s(*multipart.Form=<nil>)
注意,由于PostFormValue()方法只能訪問(wèn)form數(shù)據(jù),上面調(diào)用了PostFormValue()后,無(wú)法使用PostFormValue()訪問(wèn)URL中的query的Key/value,盡管request中的字段都合理設(shè)置了。
Files
multipart/form-data
最常用的場(chǎng)景可能是上傳文件,比如在form中使用file標(biāo)簽。以下是html文件:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Go Web Programming</title> </head> <body> <form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai method="post" enctype="multipart/form-data"> <input type="text" name="name" value="longshuai"/> <input type="text" name="age" value="23"/> <input type="file" name="file_to_upload"> <input type="submit"/> </form> </body> </html>
下面是服務(wù)端接收上傳文件數(shù)據(jù)的代碼:
package main import ( "fmt" "io/ioutil" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(1024) fileHeader := r.MultipartForm.File["file_to_upload"][0] file, err := fileHeader.Open() if err == nil { dataFromFile, err := ioutil.ReadAll(file) if err == nil { fmt.Fprintf(w, "%s\n", dataFromFile) } } fmt.Fprintf(w, "%s\n", r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
上面先調(diào)用ParseMultipartForm()解析multipart form,然后訪問(wèn)request的MultipartForm字段,這個(gè)字段的類(lèi)型是*multipart.Form
,該類(lèi)型定義在mime/multipart/formdata.go文件中:
$ go doc multipart.Form package multipart // import "mime/multipart" type Form struct { Value map[string][]string File map[string][]*FileHeader }
Form類(lèi)型表示解析后的multipart form,字段File和Value都是map類(lèi)型的,其中File的map value是*FileHeader
類(lèi)型:
$ go doc multipart.fileheader package multipart // import "mime/multipart" type FileHeader struct { Filename string Header textproto.MIMEHeader Size int64 // Has unexported fields. } A FileHeader describes a file part of a multipart request. func (fh *FileHeader) Open() (File, error)
它實(shí)現(xiàn)了Open()方法,所以可以直接調(diào)用Open()來(lái)打開(kāi)multipart.Form的File部分。即:
fileHeader := r.MultipartForm.File["file_to_upload"][0] file, err := fileHeader.Open()
然后讀取這段數(shù)據(jù),響應(yīng)給客戶端。注意,有了File后,request.MultipartForm字段的第二個(gè)map就有了值,第二個(gè)map對(duì)應(yīng)的就是multipart.Form.File的內(nèi)容。
整個(gè)返回結(jié)果如下:
FormFile()
類(lèi)似于FormValue()和PostFormValue()方法的便捷,讀取multipart.Form也有快捷方式:
$ go doc http.formfile func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) FormFile returns the first file for the provided form key. FormFile calls ParseMultipartForm and ParseForm if necessary.
FormFile()方法會(huì)在需要的時(shí)候自動(dòng)調(diào)用parseMultipartForm()或ParseForm()。注意它的返回值。因?yàn)榈谝粋€(gè)返回值為multipart.File
,說(shuō)明至少實(shí)現(xiàn)了io.Reader接口,可以直接讀取這個(gè)文件。
修改上一節(jié)的示例:
func form(w http.ResponseWriter, r *http.Request) { file, _, err := r.FormFile("file_to_upload") if err != nil { panic(err) } dataFromFile, err := ioutil.ReadAll(file) if err != nil { panic(err) } fmt.Fprintf(w, "%s\n", dataFromFile) }
ResponseWriter
ResponseWriter接口用于發(fā)送響應(yīng)數(shù)據(jù)、響應(yīng)header。它有3個(gè)方法:
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } A ResponseWriter interface is used by an HTTP handler to construct an HTTP response. A ResponseWriter may not be used after the Handler.ServeHTTP method has returned.
Header()用于構(gòu)造response header,構(gòu)造好的header會(huì)在稍后自動(dòng)被WriteHeader()發(fā)送出去。比如設(shè)置一個(gè)Location字段:
w.Header().Set("Location", "http://google.com")
Write()用于發(fā)送響應(yīng)數(shù)據(jù),例如發(fā)送html格式的數(shù)據(jù),json格式的數(shù)據(jù)等。
str := `<html> <head><title>Go Web Programming</title></head> <body><h1>Hello World</h1></body> </html>` w.Write([]byte(str))
WriteHeader(int)可以接一個(gè)數(shù)值HTTP狀態(tài)碼,同時(shí)它會(huì)將構(gòu)造好的Header自動(dòng)發(fā)送出去。如果不顯式調(diào)用WriteHeader(),會(huì)自動(dòng)隱式調(diào)用并發(fā)送200 OK。
下面是一個(gè)示例:
package main import ( "fmt" "encoding/json" "net/http" ) func commonWrite(w http.ResponseWriter, r *http.Request) { str := `<html> <head> <title>Go Web</title> </head> <body> <h1>Hello World</h1> </body> </html>` w.Write([]byte(str)) } func writeHeader(w http.ResponseWriter,r *http.Request){ w.WriteHeader(501) fmt.Fprintln(w,"not implemented service") } func header(w http.ResponseWriter,r *http.Request){ w.Header().Set("Location","http://www.baidu.com") w.WriteHeader(302) } type User struct { Name string Friends []string } func jsonWrite(w http.ResponseWriter, r *http.Request) { var user = &User{ Name: "longshuai", Friends: []string{"personA", "personB", "personC"}, } w.Header().Set("Content-Type", "application/json") jsonData, _ := json.Marshal(user) w.Write(jsonData) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/commonwrite", commonWrite) http.HandleFunc("/writeheader", writeHeader) http.HandleFunc("/header", header) http.HandleFunc("/jsonwrite", jsonWrite) server.ListenAndServe() }
commonWrite()這個(gè)handler用于輸出帶html格式的數(shù)據(jù)。訪問(wèn)結(jié)果:
writeheader()這個(gè)handler用于顯式發(fā)送501狀態(tài)碼。訪問(wèn)結(jié)果:
$ curl -i 127.0.0.1:8080/writeheader HTTP/1.1 501 Not Implemented Date: Tue, 27 Nov 2018 03:36:57 GMT Content-Length: 24 Content-Type: text/plain; charset=utf-8 not implemented service
header()這個(gè)handler用于設(shè)置響應(yīng)的Header,這里設(shè)置了302重定向,客戶端收到302狀態(tài)碼后會(huì)找Location字段的值,然后重定向到http://www.baidu.com
。
jsonWrite()這個(gè)handler用于發(fā)送json數(shù)據(jù),所以發(fā)送之前先設(shè)置了Content-Type: application/json
。
更多關(guān)于Go語(yǔ)言使用Request,Response處理web頁(yè)面請(qǐng)求的方法請(qǐng)查看下面的相關(guān)鏈接
相關(guān)文章
Go語(yǔ)言for range(按照鍵值循環(huán))遍歷操作
這篇文章主要介紹了Go語(yǔ)言for range(按照鍵值循環(huán))遍歷操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12golang如何通過(guò)viper讀取config.yaml文件
這篇文章主要介紹了golang通過(guò)viper讀取config.yaml文件,圍繞golang讀取config.yaml文件的相關(guān)資料展開(kāi)詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-03-03GoRoutines高性能同時(shí)進(jìn)行多個(gè)Api調(diào)用實(shí)現(xiàn)
這篇文章主要為大家介紹了GoRoutines高性能同時(shí)進(jìn)行多個(gè)Api調(diào)用實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03深入解析快速排序算法的原理及其Go語(yǔ)言版實(shí)現(xiàn)
這篇文章主要介紹了快速排序算法的原理及其Go語(yǔ)言版實(shí)現(xiàn),文中對(duì)于快速算法的過(guò)程和效率有較為詳細(xì)的說(shuō)明,需要的朋友可以參考下2016-04-04golang語(yǔ)言實(shí)現(xiàn)的文件上傳與文件下載功能示例
這篇文章主要介紹了golang語(yǔ)言實(shí)現(xiàn)的文件上傳與文件下載功能,結(jié)合實(shí)例形式分析了Go語(yǔ)言實(shí)現(xiàn)的文件傳輸相關(guān)操作技巧,需要的朋友可以參考下2020-02-02go語(yǔ)言beego框架web開(kāi)發(fā)語(yǔ)法筆記示例
這篇文章主要為大家介紹了go語(yǔ)言beego框架web開(kāi)發(fā)語(yǔ)法筆記示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例
這篇文章主要為大家介紹了golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04