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应用经常使用的工具,经过表单咱们能够方便的让客户端和服务器进行数据的交互。
表单是一个包含表单元素的区域。表单元素是容许用户在表单中(好比:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(