GO WBE学习笔记
GoWeb學(xué)習(xí)筆記
學(xué)習(xí)的資料來自楊旭老師在B站的視頻
文章目錄
- GoWeb學(xué)習(xí)筆記
- 創(chuàng)建第一個(gè)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ù)
- 整合實(shí)現(xiàn)
- 直接創(chuàng)建
- 通過外部函數(shù)和使用http.Server實(shí)現(xiàn)
- http.Handle源碼
- 使用http.Handle創(chuàng)建一個(gè)Hello World
- 注意
- Go語言的五個(gè)內(nèi)置Handler
- HTTP消息
- Request(請求)
- FORM字段
- 上傳文件
- POST請求-JSON BODY
- ResponseWriter
- 內(nèi)置的Response
- 內(nèi)置的Response
創(chuàng)建第一個(gè)Web程序(網(wǎng)頁輸出HelloWorld)
在go語言中創(chuàng)建一個(gè)網(wǎng)頁中輸出的HelloWorld,需要先創(chuàng)建一個(gè)訪問的函數(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一共有兩個(gè)參數(shù),第一個(gè)參數(shù)是訪問路徑,第二個(gè)參數(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ù)有兩種方式,一個(gè)是調(diào)用http.ListenAndServe方法,需要配置兩個(gè)參數(shù),另一個(gè)是調(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ù)用器) //需要傳入兩個(gè)參數(shù),分別是訪問的地址和使用的訪問函數(shù) //當(dāng)使用localhost的時(shí)候可以寫為http.ListenAndServe(":8080",nil) http.ListenAndServe("localhost:8080",nil)使用http.Server創(chuàng)建監(jiān)聽和服務(wù)
//等同于http.ListenAndServe,但是使用這種方式配置更加靈活,因?yàn)榭梢栽O(shè)置更多參數(shù)值server := http.Server{Addr: "localhost:8080",Handler: nil,}server.ListenAndServe()整合實(shí)現(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實(shí)現(xiàn)
//為了方便,寫了一個(gè)監(jiān)聽的函數(shù) func Listen(){server := http.Server{Addr: "localhost:8080",Handler: nil,}server.ListenAndServe() } //自定義的訪問函數(shù) func MyHandleFunc(w http.ResponseWriter,r *http.Request){//因?yàn)閃rite()函數(shù)的源碼為Write([]byte) (int, error),所以在輸出string類型的時(shí)候需要轉(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)建一個(gè)Hello World
有http.Handle的源碼可以看出傳入的第二個(gè)參數(shù)為Handler,源碼如下:
type Handler interface {ServeHTTP(ResponseWriter, *Request) }Handler是一個(gè)ServeHTTP類型的Type,所以傳入Handle的第二個(gè)參數(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ù)的時(shí)候是使用創(chuàng)建好的ServeHttp,我們也可以使用HandlerFunc來將創(chuàng)建的沒有繼承ServeHTTP的 路由轉(zhuǎn)化
//使用HandlerFunc將自定義的訪問控制轉(zhuǎn)換為一個(gè)Handler //HandlerFunc可以將某個(gè)具有適當(dāng)簽名的函數(shù)f適配成為一個(gè)Handler http.Handle("/my", http.HandlerFunc(MyHandleFunc))func MyHandleFunc(w http.ResponseWriter, r *http.Request) {w.Write([]byte("MyHandleFunc")) }Go語言的五個(gè)內(nèi)置Handler
1.NotFoundHandler
返回一個(gè)handler,每個(gè)請求的相應(yīng)都是404 page not found
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }2.RedirectHandler
返回一個(gè)handler,把每一個(gè)請求按照狀態(tài)碼跳轉(zhuǎn)到指定的URL
常見的:StatusMovedPermanently、StatusFound、StatusSeeOther
func RedirectHandler(url string, code int) Handler {return &redirectHandler{url, code} }3.StripPrefix
去前綴,返回一個(gè)handler,在指定的url中去掉前綴,然后調(diào)用另一個(gè)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
返回一個(gè)handler,在指定時(shí)間內(nèi)運(yùn)行傳入的handler
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {return &timeoutHandler{handler: h,body: msg,dt: dt,} }5.FileServer
返回一個(gè)handler,使用基于root的文件系統(tǒng)來相應(yīng)請求
func FileServer(root FileSystem) Handler {return &fileHandler{root} } type FileSystem interface {Open(name string) (File, error) }在使用時(shí)需要用到操作系統(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個(gè)或者多個(gè)url,空行以及可選的消息體(body)
Request(請求)
在GO語言中Request是一個(gè)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 }其中幾個(gè)重要的字段
1.URL
Request的URL字段就代表了請求行(請求信息第一行)里面的部分內(nèi)容,URL字段是指向url.URL類型的一個(gè)指針,url.URL是一個(gè)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提供實(shí)際查詢的字符串
- 通過Request的Form字段
r.URL.Query()
該方法會(huì)提供查詢字符串對應(yīng)的map[string] [] string
URL Fragment
就是URL格式中的#后面的部分
當(dāng)請求從瀏覽器發(fā)出時(shí),無法獲取到Fragment的值,因?yàn)樵跒g覽器發(fā)送請求的時(shí)候會(huì)把Fragment去掉
部分客戶端工具發(fā)出的請求可以獲取到Fragment的值,例如HTTP客戶端包
2.Handler
請求和相應(yīng)的headers是通過Header類型來描述的,它是一個(gè)map類型,用來描述HTTP header里的Key-Value對。
Header map的key是string類型,value是[]string
設(shè)置key時(shí)會(huì)創(chuàng)建一個(gè)空的[]string作為value,value里面第一個(gè)元素就是新的header的值
如果是為指定的key添加一個(gè)新的header值的話,執(zhí)行append操作即可
3.Body
請求和相應(yīng)的bodies都是使用Body字段來表示的
Body是一個(gè)io.ReadCloser接口,一個(gè)Reader接口和一個(gè)Closer接口
Reader接口定義了一個(gè)Open方法,參數(shù)[]byte,返回byte的數(shù)量、可選的錯(cuò)誤
Closer接口定義了一個(gè)Close方法:沒有參數(shù),返回可選的錯(cuò)誤
讀取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"]//以日志的形式打印在控制臺(tái)log.Println(id)//根據(jù)傳入的key值返回第一個(gè)值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ù)會(huì)以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
在這個(gè)屬性下,瀏覽器將會(huì)將表單數(shù)據(jù)編碼到查詢字符串里面,簡單的文本格式使用這種方式
2.multipart/form-data
在這種屬性下每一個(gè)name-value對都會(huì)被轉(zhuǎn)化為一個(gè)mime消息部分
每一個(gè)部分都有自己的Content Type和Content Disposition,在上傳文件的時(shí)候選用這個(gè)方式
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)//以日志的形式打印到控制臺(tái)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的時(shí)候需要先調(diào)用ParseMultipartForm,ParseMultipartForm會(huì)在必要的時(shí)候調(diào)用ParseForm,里面需要傳入一個(gè)參數(shù)(讀取的數(shù)據(jù)長度,單位為字節(jié)),MultipartForm只包含表單的key-value對,返回類型是一個(gè)struct而不是map,這個(gè)struct里面包含兩個(gè)map,一個(gè)是你表單里面的數(shù)據(jù),另一個(gè)是文件
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方法會(huì)返回form字段指定key對應(yīng)的第一個(gè)value,無需調(diào)用ParseForm和ParseMultipartForm
PostFormValue方法只能讀取PostForm
這兩種該方法都會(huì)調(diào)用ParseMultipartForm
當(dāng)你表單的enctype設(shè)置為multipart/form-data的時(shí)候,上面兩種方法無法獲取到表單的數(shù)據(jù)
上傳文件
首先form里面的enctype類型要設(shè)置為multipart/form-data
在GO語言中處理上傳文件的時(shí)候:
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)//因?yàn)閒ile是一個(gè)map,也就是允許多個(gè)文件上傳,這里可以指定獲取那個(gè)文件,0代表第一個(gè)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) {//返回第一個(gè)文件,當(dāng)只上傳一個(gè)文件的時(shí)候,這種方式更快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會(huì)返回一個(gè)MIME multipart reader,否則這返回一個(gè)error和nil
在使用中可以使用MultipartReader代替ParseMultipartForm來把請求的body作為stream進(jìn)行處理,它在處理的時(shí)候不是一次性處理整個(gè)表單數(shù)據(jù),而是檢查來自表單的值,然后每次處理一個(gè)
POST請求-JSON BODY
不是所有的post請求都來自form
在不同的客戶端框架下會(huì)以不同的方式對post請求編碼
例如jQuery通常使用application/x-www-form-urlencoded
Augular則是application/json,但是ParseForm方法無法處理application/json
ResponseWriter
從服務(wù)器向客戶端返回相應(yīng)需要使用ResponseWriter
ResponseWriter是一個(gè)接口,handler用它來返回相應(yīng),真正支撐ResponseWriter的幕后struct是一個(gè)非導(dǎo)出的http.response
寫入到ResponseWriter
ResponseWriter在底層實(shí)現(xiàn)的時(shí)候其實(shí)也是實(shí)現(xiàn)了一個(gè)指針
在ResponseWriter中,write方法接收一個(gè)byte切片作為參數(shù),然后把他寫入到HTTP相應(yīng)的Body里面。
如果Write方法被調(diào)用時(shí),header里面沒有設(shè)定content type,那么數(shù)據(jù)的前512字節(jié)就會(huì)被用來監(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方法接收一個(gè)整數(shù)類型(HTTP狀態(tài)碼)作為參數(shù),并把它作為HTTP響應(yīng)的狀態(tài)碼返回,如果這個(gè)方法沒有被顯示的調(diào)用,那么在第一次調(diào)用Write方法前會(huì)隱式的調(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將會(huì)體現(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ù),包裝一個(gè)404狀態(tài)碼和一個(gè)額外的信息
2.ServeFile函數(shù),從文件系統(tǒng)提供文件,返回給請求者
3.ServeContent函數(shù),它可以把實(shí)現(xiàn)了io.ReadSeeker接口的任何東西里面的內(nèi)容返回給請求者,同時(shí)它還可以處理Range請求(范圍請求),如果只請求了資源的一部分內(nèi)容,那么ServeContent就可以如此響應(yīng)。而ServeFile或io.Copy則不行
4.Redirect函數(shù),告訴客戶端重定向到另一個(gè)URL
tp://www.baidu.com")
//訪問請求的狀態(tài)碼
w.WriteHeader(302)
}
內(nèi)置的Response
1.NotFound函數(shù),包裝一個(gè)404狀態(tài)碼和一個(gè)額外的信息
2.ServeFile函數(shù),從文件系統(tǒng)提供文件,返回給請求者
3.ServeContent函數(shù),它可以把實(shí)現(xiàn)了io.ReadSeeker接口的任何東西里面的內(nèi)容返回給請求者,同時(shí)它還可以處理Range請求(范圍請求),如果只請求了資源的一部分內(nèi)容,那么ServeContent就可以如此響應(yīng)。而ServeFile或io.Copy則不行
4.Redirect函數(shù),告訴客戶端重定向到另一個(gè)URL
總結(jié)
以上是生活随笔為你收集整理的GO WBE学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL优化面试题及答案
- 下一篇: html5 中的 wbe storage