build-web-application-with-golang 學習教程javascript
這幾周學習以上教程,僅記錄一些重點難點部分。java
Go是一門相似C的編譯型語言,可是它的編譯速度很是快。這門語言的關鍵字總共也就二十五個:mysql
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
Go程序是經過package
來組織的,package <pkgName>
這一行告訴咱們當前文件屬於哪一個包,而包名main
則告訴咱們它是一個可獨立運行的包,它在編譯後會產生可執行文件。除了main
包以外,其它的包最後都會生成*.a
文件(也就是包文件)並放置在$GOPATH/pkg/$GOOS_$GOARCH
中(以Mac爲例就是$GOPATH/pkg/darwin_amd64
)。git
每個可獨立運行的Go程序,一定包含一個
package main
,在這個main
包中一定包含一個入口函數main
,而這個函數既沒有參數
,也沒有返回值。github
包的概念和Python中的package相似,它們都有一些特別的好處:模塊化(可以把你的程序分紅多個模塊)和可重用性(每一個模塊都能被其它應用程序反覆使用)。golang
簡單的說,interface是一組method簽名的組合,咱們經過interface來定義對象的一組行爲。web
package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } //Human實現SayHi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human實現Sing方法 func (h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } //Employee重載Human的SayHi方法 func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee實現 // 由於這三個類型都實現了這兩個方法 type Men interface { SayHi() Sing(lyrics string) } func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定義Men類型的變量i var i Men //i能存儲Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存儲Employee i = tom fmt.Println("This is tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定義了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //這三個都是不一樣類型的元素,可是他們實現了interface同一個接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } }
interface就是一組抽象方法的集合,它必須由其餘非interface類型實現,而不能自我實現。sql
空interface(interface{})不包含任何的method,正由於如此,全部的類型都實現了空interface。空interface對於描述起不到任何的做用(由於它不包含任何的method),可是空interface在咱們須要存儲任意類型的數值的時候至關有用,由於它能夠存儲任意類型的數值。它有點相似於C語言的void*類型。shell
// 定義a爲空接口 var a interface{} var i int = 5 s := "Hello world" // a能夠存儲任意類型的數值 a = i a = s
goroutine是Go並行設計的核心。goroutine說到底其實就是協程,可是它比線程更小,十幾個goroutine可能體如今底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),固然會根據相應的數據伸縮。也正由於如此,可同時運行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。數據庫
goroutine是經過Go的runtime管理的一個線程管理器。goroutine經過go關鍵字實現了,其實就是一個普通的函數。
package main import ( "fmt" "runtime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } } func main() { go say("world") //開一個新的Goroutines執行 say("hello") //當前Goroutines執行 } // 以上程序執行後將輸出: // hello // world // hello // world // hello // world // hello // world // hello
goroutine運行在相同的地址空間,所以訪問共享內存必須作好同步。那麼goroutine之間如何進行數據的通訊呢,Go提供了一個很好的通訊機制channel。channel能夠與Unix shell 中的雙向管道作類比:能夠經過它發送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也須要定義發送到channel的值的類型。注意,必須使用make 建立channel:
package main import "fmt" func sum(a []int, c chan int) { total := 0 for _, v := range a { total += v } c <- total // send total to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) }
Web是基於http協議的一個服務,Go語言裏面提供了一個完善的net/http包,經過http包能夠很方便的就搭建起來一個能夠運行的Web服務。同時使用這個包能很簡單地對Web的路由,靜態文件,模版,cookie等數據進行設置和操做。
package main import ( "fmt" "net/http" "strings" "log" ) func sayhelloName(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析參數,默認是不會解析的 fmt.Println(r.Form) //這些信息是輸出到服務器端的打印信息 fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) for k, v := range r.Form { fmt.Println("key:", k) fmt.Println("val:", strings.Join(v, "")) } fmt.Fprintf(w, "Hello astaxie!") //這個寫入到w的是輸出到客戶端的 } func main() { http.HandleFunc("/", sayhelloName) //設置訪問的路由 err := http.ListenAndServe(":9090", nil) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } }
看到上面這個代碼,要編寫一個Web服務器很簡單,只要調用http包的兩個函數就能夠了(相似於Python的tornado)。咱們build以後,而後執行web.exe,這個時候其實已經在9090端口監聽http連接請求了。
在瀏覽器輸入http://localhost:9090
能夠看到瀏覽器頁面輸出了Hello astaxie!
能夠換一個地址試試:http://localhost:9090/?url_long=111&url_long=222
看看瀏覽器輸出的是什麼,服務器輸出的是什麼?
http包執行流程
建立Listen Socket, 監聽指定的端口, 等待客戶端請求到來。
Listen Socket接受客戶端的請求, 獲得Client Socket, 接下來經過Client Socket與客戶端通訊。
處理客戶端的請求, 首先從Client Socket讀取HTTP請求的協議頭, 若是是POST方法, 還可能要讀取客戶端提交的數據, 而後交給相應的handler處理請求, handler處理完畢準備好客戶端須要的數據, 經過Client Socket寫給客戶端。
這整個的過程裏面咱們只要瞭解清楚下面三個問題,也就知道Go是如何讓Web運行起來了
前面小節的代碼裏面咱們能夠看到,Go是經過一個函數ListenAndServe
來處理這些事情的,這個底層其實這樣處理的:初始化一個server對象,而後調用了net.Listen("tcp", addr)
,也就是底層用TCP協議搭建了一個服務,而後監控咱們設置的端口。
下面代碼來自Go的http包的源碼,經過下面的代碼咱們能夠看到整個的http處理過程:
func (srv *Server) Serve(l net.Listener) error { defer l.Close() var tempDelay time.Duration // how long to sleep on accept failure for { rw, e := l.Accept() if e != nil { if ne, ok := e.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c, err := srv.newConn(rw) if err != nil { continue } go c.serve() } }
監控以後如何接收客戶端的請求呢?上面代碼執行監控端口以後,調用了srv.Serve(net.Listener)
函數,這個函數就是處理接收客戶端的請求信息。這個函數裏面起了一個for{}
,首先經過Listener接收請求,其次建立一個Conn,最後單獨開了一個goroutine,把這個請求的數據當作參數扔給這個conn去服務:go c.serve()
。這個就是高併發體現了,用戶的每一次請求都是在一個新的goroutine去服務,相互不影響。
那麼如何具體分配到相應的函數來處理請求呢?conn首先會解析request:c.readRequest()
,而後獲取相應的handler:handler := c.server.Handler
,也就是咱們剛纔在調用函數ListenAndServe
時候的第二個參數,咱們前面例子傳遞的是nil,也就是爲空,那麼默認獲取handler = DefaultServeMux
,那麼這個變量用來作什麼的呢?對,這個變量就是一個路由器,它用來匹配url跳轉到其相應的handle函數,那麼這個咱們有設置過嗎?有,咱們調用的代碼裏面第一句不是調用了http.HandleFunc("/", sayhelloName)
嘛。這個做用就是註冊了請求/
的路由規則,當請求uri爲"/",路由就會轉到函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這個方法內部其實就是調用sayhelloName自己,最後經過寫入response的信息反饋到客戶端。
詳細的整個流程以下圖所示:
經過對http包的分析以後,如今讓咱們來梳理一下整個的代碼執行過程。
首先調用Http.HandleFunc
按順序作了幾件事:
1 調用了DefaultServeMux的HandleFunc
2 調用了DefaultServeMux的Handle
3 往DefaultServeMux的map[string]muxEntry中增長對應的handler和路由規則
其次調用http.ListenAndServe(":9090", nil)
按順序作了幾件事情:
1 實例化Server
2 調用Server的ListenAndServe()
3 調用net.Listen("tcp", addr)監聽端口
4 啓動一個for循環,在循環體中Accept請求
5 對每一個請求實例化一個Conn,而且開啓一個goroutine爲這個請求進行服務go c.serve()
6 讀取每一個請求的內容w, err := c.readRequest()
7 判斷handler是否爲空,若是沒有設置handler(這個例子就沒有設置handler),handler就設置爲DefaultServeMux
8 調用handler的ServeHttp
9 在這個例子中,下面就進入到DefaultServeMux.ServeHttp
10 根據request選擇handler,而且進入到這個handler的ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 選擇handler:
A 判斷是否有路由能知足這個request(循環遍歷ServeMux的muxEntry)
B 若是有路由知足,調用這個路由handler的ServeHTTP
C 若是沒有路由知足,調用NotFoundHandler的ServeHTTP
表單是咱們日常編寫Web應用經常使用的工具,經過表單咱們能夠方便的讓客戶端和服務器進行數據的交互。
表單是一個包含表單元素的區域。表單元素是容許用戶在表單中(好比:文本域、下拉列表、單選框、複選框等等)輸入信息的元素。表單使用表單標籤(