GO WBE学习笔记
GoWeb學(xué)習(xí)筆記
學(xué)習(xí)的資料來自楊旭老師在B站的視頻
文章目錄
- GoWeb學(xué)習(xí)筆記
- 創(chuàng)建第一個Web程序(網(wǎng)頁輸出HelloWorld)
- HadleFunc源碼
- 使用HadleFunc,并創(chuàng)建內(nèi)置函數(shù)的形式創(chuàng)建訪問函數(shù)
- 使用HadleFunc,并調(diào)用外部函數(shù)的形式創(chuàng)建訪問函數(shù)
- 創(chuàng)建訪問監(jiān)聽和服務(wù)
- http.ListenAndServe源碼
- http.Server源碼(去除了源碼中的注釋)
- 使用http.ListenAndServe創(chuàng)建監(jiān)聽和服務(wù)
- 使用http.Server創(chuàng)建監(jiān)聽和服務(wù)
- 整合實現(xiàn)
- 直接創(chuàng)建
- 通過外部函數(shù)和使用http.Server實現(xiàn)
- http.Handle源碼
- 使用http.Handle創(chuàng)建一個Hello World
- 注意
- Go語言的五個內(nèi)置Handler
- HTTP消息
- Request(請求)
- FORM字段
- 上傳文件
- POST請求-JSON BODY
- ResponseWriter
- 內(nèi)置的Response
- 內(nèi)置的Response
創(chuàng)建第一個Web程序(網(wǎng)頁輸出HelloWorld)
在go語言中創(chuàng)建一個網(wǎng)頁中輸出的HelloWorld,需要先創(chuàng)建一個訪問的函數(shù),然后指定相應(yīng)的監(jiān)聽和服務(wù)
HadleFunc源碼
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler) }使用HadleFunc,并創(chuàng)建內(nèi)置函數(shù)的形式創(chuàng)建訪問函數(shù)
//HandleFunc一共有兩個參數(shù),第一個參數(shù)是訪問路徑,第二個參數(shù)是路由函數(shù) http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {writer.Write([]byte("Hello World!")) })使用HadleFunc,并調(diào)用外部函數(shù)的形式創(chuàng)建訪問函數(shù)
//自定義的外部函數(shù) func MyHandleFunc(w http.ResponseWriter,r *http.Request){w.Write([]byte("MyHandleFunc")) }func main(){//調(diào)用函數(shù)http.HandleFunc("/myHandleFunc",MyHandleFunc) }創(chuàng)建訪問監(jiān)聽和服務(wù)
創(chuàng)建訪問監(jiān)聽和服務(wù)有兩種方式,一個是調(diào)用http.ListenAndServe方法,需要配置兩個參數(shù),另一個是調(diào)用http.Server,這種方式需要自定http.Server提供的參數(shù),相對于http.ListenAndServe,這種方式更加靈活
http.ListenAndServe源碼
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe() }http.Server源碼(去除了源碼中的注釋)
type Server struct {Addr stringHandler Handler TLSConfig *tls.ConfigReadTimeout time.DurationReadHeaderTimeout time.DurationWriteTimeout time.DurationIdleTimeout time.DurationMaxHeaderBytes intTLSNextProto map[string]func(*Server, *tls.Conn, Handler)ConnState func(net.Conn, ConnState)ErrorLog *log.LoggerBaseContext func(net.Listener) context.ContextConnContext func(ctx context.Context, c net.Conn) context.ContextinShutdown atomicBool disableKeepAlives int32 nextProtoOnce sync.Once nextProtoErr error mu sync.Mutexlisteners map[*net.Listener]struct{}activeConn map[*conn]struct{}doneChan chan struct{}onShutdown []func() }使用http.ListenAndServe創(chuàng)建監(jiān)聽和服務(wù)
//使用nil相當(dāng)與使用了go語言內(nèi)置的http.DefaultServeMux(多路復(fù)用器) //需要傳入兩個參數(shù),分別是訪問的地址和使用的訪問函數(shù) //當(dāng)使用localhost的時候可以寫為http.ListenAndServe(":8080",nil) http.ListenAndServe("localhost:8080",nil)使用http.Server創(chuàng)建監(jiān)聽和服務(wù)
//等同于http.ListenAndServe,但是使用這種方式配置更加靈活,因為可以設(shè)置更多參數(shù)值server := http.Server{Addr: "localhost:8080",Handler: nil,}server.ListenAndServe()整合實現(xiàn)
直接創(chuàng)建
//訪問路徑為localhost:8080 http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {writer.Write([]byte("Hello World!")) }) http.ListenAndServe("localhost:8080",nil)通過外部函數(shù)和使用http.Server實現(xiàn)
//為了方便,寫了一個監(jiān)聽的函數(shù) func Listen(){server := http.Server{Addr: "localhost:8080",Handler: nil,}server.ListenAndServe() } //自定義的訪問函數(shù) func MyHandleFunc(w http.ResponseWriter,r *http.Request){//因為Write()函數(shù)的源碼為Write([]byte) (int, error),所以在輸出string類型的時候需要轉(zhuǎn)換w.Write([]byte("Hello World")) } func main(){http.HandleFunc("/myHandleFunc",MyHandleFunc)Listen() }http.Handle源碼
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }使用http.Handle創(chuàng)建一個Hello World
有http.Handle的源碼可以看出傳入的第二個參數(shù)為Handler,源碼如下:
type Handler interface {ServeHTTP(ResponseWriter, *Request) }Handler是一個ServeHTTP類型的Type,所以傳入Handle的第二個參數(shù)也得是ServeHTTP類型的訪問函數(shù)
//自定義路由 type HelloHandler struct {}//自定義路由 type AboutHandler struct {} //定義訪問控制器 func (m *HelloHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {w.Write([]byte("Hello World!")) } //定義訪問控制器 func (m *AboutHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {w.Write([]byte("About!")) } func MyListen(){mh := HelloHandler{}ma := AboutHandler{}server := http.Server{Addr: ":8080",//不指定訪問路由器,從而達(dá)成通過訪問不同的路由參數(shù)而訪問不同的自定義路由器Handler: nil,}http.Handle("/hello",&mh)http.Handle("/about",&ma)server.ListenAndServe() }注意
在上面使用Handle函數(shù)的時候是使用創(chuàng)建好的ServeHttp,我們也可以使用HandlerFunc來將創(chuàng)建的沒有繼承ServeHTTP的 路由轉(zhuǎn)化
//使用HandlerFunc將自定義的訪問控制轉(zhuǎn)換為一個Handler //HandlerFunc可以將某個具有適當(dāng)簽名的函數(shù)f適配成為一個Handler http.Handle("/my", http.HandlerFunc(MyHandleFunc))func MyHandleFunc(w http.ResponseWriter, r *http.Request) {w.Write([]byte("MyHandleFunc")) }Go語言的五個內(nèi)置Handler
1.NotFoundHandler
返回一個handler,每個請求的相應(yīng)都是404 page not found
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }2.RedirectHandler
返回一個handler,把每一個請求按照狀態(tài)碼跳轉(zhuǎn)到指定的URL
常見的:StatusMovedPermanently、StatusFound、StatusSeeOther
func RedirectHandler(url string, code int) Handler {return &redirectHandler{url, code} }3.StripPrefix
去前綴,返回一個handler,在指定的url中去掉前綴,然后調(diào)用另一個handler
func StripPrefix(prefix string, h Handler) Handler {if prefix == "" {return h}return HandlerFunc(func(w ResponseWriter, r *Request) {if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {r2 := new(Request)*r2 = *rr2.URL = new(url.URL)*r2.URL = *r.URLr2.URL.Path = ph.ServeHTTP(w, r2)} else {NotFound(w, r)}}) }4.TimeoutHandler
返回一個handler,在指定時間內(nèi)運行傳入的handler
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {return &timeoutHandler{handler: h,body: msg,dt: dt,} }5.FileServer
返回一個handler,使用基于root的文件系統(tǒng)來相應(yīng)請求
func FileServer(root FileSystem) Handler {return &fileHandler{root} } type FileSystem interface {Open(name string) (File, error) }在使用時需要用到操作系統(tǒng)的文件系統(tǒng),所以一般交給下面的函數(shù)來用
type Dir string func (d Dir) Open(name string) (File, error) {if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {return nil, errors.New("http: invalid character in file path")}dir := string(d)if dir == "" {dir = "."}fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))f, err := os.Open(fullName)if err != nil {return nil, mapDirOpenError(err, fullName)}return f, nil }HTTP消息
HTTP Request 和HTTP Response,他倆具有相同的結(jié)構(gòu),都有請求行,0個或者多個url,空行以及可選的消息體(body)
Request(請求)
在GO語言中Request是一個struct,代表了客戶端發(fā)送的HTTP請求消息(既可以代表客戶端的請求,也可以代表服務(wù)端的請求),可以通過Request的方法訪問請求中的Cookie、URL、User Agent等信息,源碼如下:
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 }其中幾個重要的字段
1.URL
Request的URL字段就代表了請求行(請求信息第一行)里面的部分內(nèi)容,URL字段是指向url.URL類型的一個指針,url.URL是一個struct源碼如下:
type URL struct {Scheme stringOpaque string User *Userinfo Host string Path string RawPath string ForceQuery bool RawQuery string Fragment string RawFragment string }URL的通用格式為:
scheme://[userinfo@]host/path[?query][#fragment]不以斜杠開頭的URL被解釋為:
scheme:opaque[?query][#fragment]URL Query
- RawQuery提供實際查詢的字符串
- 通過Request的Form字段
r.URL.Query()
該方法會提供查詢字符串對應(yīng)的map[string] [] string
URL Fragment
就是URL格式中的#后面的部分
當(dāng)請求從瀏覽器發(fā)出時,無法獲取到Fragment的值,因為在瀏覽器發(fā)送請求的時候會把Fragment去掉
部分客戶端工具發(fā)出的請求可以獲取到Fragment的值,例如HTTP客戶端包
2.Handler
請求和相應(yīng)的headers是通過Header類型來描述的,它是一個map類型,用來描述HTTP header里的Key-Value對。
Header map的key是string類型,value是[]string
設(shè)置key時會創(chuàng)建一個空的[]string作為value,value里面第一個元素就是新的header的值
如果是為指定的key添加一個新的header值的話,執(zhí)行append操作即可
3.Body
請求和相應(yīng)的bodies都是使用Body字段來表示的
Body是一個io.ReadCloser接口,一個Reader接口和一個Closer接口
Reader接口定義了一個Open方法,參數(shù)[]byte,返回byte的數(shù)量、可選的錯誤
Closer接口定義了一個Close方法:沒有參數(shù),返回可選的錯誤
讀取body的內(nèi)容,調(diào)用Body的Read方法
func getQuery(){http.HandleFunc("/query", func(writer http.ResponseWriter, request *http.Request) {//獲取URLurl := request.URL//調(diào)用query方法query := url.Query()//根據(jù)傳入的key值查詢相應(yīng)的數(shù)據(jù),返回全部值id := query["id"]//以日志的形式打印在控制臺log.Println(id)//根據(jù)傳入的key值返回第一個值name := query.Get("name")log.Println(name)}) }4.Form、PostForm、MultipartForm
通過表單發(fā)送post請求
<form action="/index" method="post">用戶名<input type="text" name="name" />密碼<input type="password" name="password" /><input type="submit" /> </form>action是發(fā)送請求對應(yīng)的服務(wù)器路徑,method是發(fā)送請求的方式,有post和get兩種,html表單里的數(shù)據(jù)會以name-value對的方式通過post請求發(fā)送出去。
name-value
通過psot發(fā)送的name-value數(shù)據(jù)對的格式通過表單的Content Type來指定,也就是表單里面的enctype屬性,在form表單中enctype的默認(rèn)屬性為application/x-www-form-urlencoded。
1.application/x-www-form-urlencoded
在這個屬性下,瀏覽器將會將表單數(shù)據(jù)編碼到查詢字符串里面,簡單的文本格式使用這種方式
2.multipart/form-data
在這種屬性下每一個name-value對都會被轉(zhuǎn)化為一個mime消息部分
每一個部分都有自己的Content Type和Content Disposition,在上傳文件的時候選用這個方式
3.text/plain
POST & GET
表單的method屬性可以設(shè)置post和get兩種屬性
1.GET
get請求沒有body,所有的數(shù)據(jù)都通過URL的name-value對來發(fā)送
2.POST
FORM字段
Resquest上的函數(shù)允許我們通過url或/和body中提取數(shù)據(jù),form里面的數(shù)據(jù)是key-value對
通常的做法是先調(diào)用ParseForm或ParseMultipartForm來解析Request,然后相應(yīng)的訪問Form、PostForm或MultipartForm字段
PostForm
PostForm只支持application/x-www-form-urlencoded,
func getForm(){http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {request.ParseForm()//輸出到頁面fmt.Fprintln(writer,request.Form)//以日志的形式打印到控制臺log.Println(request.Form)}) }前端代碼:
<form action="http://localhost:8080/process" method="post" >用戶名<input type="text" name="name" />密碼<input type="password" name="password" /><input type="submit" /> </form>MultipartForm
使用MultipartForm的時候需要先調(diào)用ParseMultipartForm,ParseMultipartForm會在必要的時候調(diào)用ParseForm,里面需要傳入一個參數(shù)(讀取的數(shù)據(jù)長度,單位為字節(jié)),MultipartForm只包含表單的key-value對,返回類型是一個struct而不是map,這個struct里面包含兩個map,一個是你表單里面的數(shù)據(jù),另一個是文件
func getMultipart(){http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {//參數(shù)為字節(jié),是上傳數(shù)據(jù)的長度request.ParseMultipartForm(1024)fmt.Fprintln(writer,request.MultipartForm)log.Println(request.MultipartForm)}) }FormValue&PostFormValue
FormValue方法會返回form字段指定key對應(yīng)的第一個value,無需調(diào)用ParseForm和ParseMultipartForm
PostFormValue方法只能讀取PostForm
這兩種該方法都會調(diào)用ParseMultipartForm
當(dāng)你表單的enctype設(shè)置為multipart/form-data的時候,上面兩種方法無法獲取到表單的數(shù)據(jù)
上傳文件
首先form里面的enctype類型要設(shè)置為multipart/form-data
在GO語言中處理上傳文件的時候:
1.調(diào)用ParseMultiparForm方法
2.從file字段獲得FileHeadler,調(diào)用Open方法來獲得文件
3.可以使用ioutil.ReadAll函數(shù)將文件內(nèi)容讀取到byte切片里
func getFile(){http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {request.ParseMultipartForm(1024)//因為file是一個map,也就是允許多個文件上傳,這里可以指定獲取那個文件,0代表第一個fileHeader := request.MultipartForm.File["uploaded"][0]file,err := fileHeader.Open()if err == nil {data,err := ioutil.ReadAll(file)if err != nil {fmt.Println(err)}fmt.Fprintln(writer,string(data))}}) }方法2
func getFile2(){http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {//返回第一個文件,當(dāng)只上傳一個文件的時候,這種方式更快file,_,err := request.FormFile("uploaded")if err == nil {data,err := ioutil.ReadAll(file)if err != nil {fmt.Println(err)}fmt.Fprintln(writer,string(data))}}) }MultipartReader()
源碼如下:
func (r *Request) MultipartReader() (*multipart.Reader, error) {if r.MultipartForm == multipartByReader {return nil, errors.New("http: MultipartReader called twice")}if r.MultipartForm != nil {return nil, errors.New("http: multipart handled by ParseMultipartForm")}r.MultipartForm = multipartByReader如果是multipart/form-data或multipart混合的POST請求,MultipartReader會返回一個MIME multipart reader,否則這返回一個error和nil
在使用中可以使用MultipartReader代替ParseMultipartForm來把請求的body作為stream進(jìn)行處理,它在處理的時候不是一次性處理整個表單數(shù)據(jù),而是檢查來自表單的值,然后每次處理一個
POST請求-JSON BODY
不是所有的post請求都來自form
在不同的客戶端框架下會以不同的方式對post請求編碼
例如jQuery通常使用application/x-www-form-urlencoded
Augular則是application/json,但是ParseForm方法無法處理application/json
ResponseWriter
從服務(wù)器向客戶端返回相應(yīng)需要使用ResponseWriter
ResponseWriter是一個接口,handler用它來返回相應(yīng),真正支撐ResponseWriter的幕后struct是一個非導(dǎo)出的http.response
寫入到ResponseWriter
ResponseWriter在底層實現(xiàn)的時候其實也是實現(xiàn)了一個指針
在ResponseWriter中,write方法接收一個byte切片作為參數(shù),然后把他寫入到HTTP相應(yīng)的Body里面。
如果Write方法被調(diào)用時,header里面沒有設(shè)定content type,那么數(shù)據(jù)的前512字節(jié)就會被用來監(jiān)測content type
func writeExample(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)) }WriteHeader
WriteHeader方法接收一個整數(shù)類型(HTTP狀態(tài)碼)作為參數(shù),并把它作為HTTP響應(yīng)的狀態(tài)碼返回,如果這個方法沒有被顯示的調(diào)用,那么在第一次調(diào)用Write方法前會隱式的調(diào)用
當(dāng)WriteHeader被調(diào)用完后,仍然可以寫入到ResponseWriter,但是不能再修改header
func writeHeader(w http.ResponseWriter,r *http.Request) {w.WriteHeader(501)fmt.Fprintln(w,"66666666666666") }Header
Header方法返回Headers的map。可以進(jìn)行修改,修改后的headers將會體現(xiàn)再返回給客戶端的HTTP響應(yīng)里
func headerExample(w http.ResponseWriter,r * http.Request) {//重定向訪問的請求w.Header().Set("location","http://www.baidu.com")//訪問請求的狀態(tài)碼w.WriteHeader(302) }傳入json數(shù)據(jù)
type Post struct {User stringThreads []string }func jsonExample(w http.ResponseWriter , r *http.Request) {w.Header().Set("Content-Type","application/json")post := &Post{User: "張三",Threads: []string{"666","777","888"},}json,_:= json2.Marshal(post)w.Write(json) }內(nèi)置的Response
1.NotFound函數(shù),包裝一個404狀態(tài)碼和一個額外的信息
2.ServeFile函數(shù),從文件系統(tǒng)提供文件,返回給請求者
3.ServeContent函數(shù),它可以把實現(xiàn)了io.ReadSeeker接口的任何東西里面的內(nèi)容返回給請求者,同時它還可以處理Range請求(范圍請求),如果只請求了資源的一部分內(nèi)容,那么ServeContent就可以如此響應(yīng)。而ServeFile或io.Copy則不行
4.Redirect函數(shù),告訴客戶端重定向到另一個URL
tp://www.baidu.com")
//訪問請求的狀態(tài)碼
w.WriteHeader(302)
}
內(nèi)置的Response
1.NotFound函數(shù),包裝一個404狀態(tài)碼和一個額外的信息
2.ServeFile函數(shù),從文件系統(tǒng)提供文件,返回給請求者
3.ServeContent函數(shù),它可以把實現(xiàn)了io.ReadSeeker接口的任何東西里面的內(nèi)容返回給請求者,同時它還可以處理Range請求(范圍請求),如果只請求了資源的一部分內(nèi)容,那么ServeContent就可以如此響應(yīng)。而ServeFile或io.Copy則不行
4.Redirect函數(shù),告訴客戶端重定向到另一個URL
總結(jié)
以上是生活随笔為你收集整理的GO WBE学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL优化面试题及答案
- 下一篇: html5 中的 wbe storage