go提供了一系列用于创建web服务器的标准,而非常简单。只需要调用net/http包中的ListenAndServe函数并传入网络地址和负责处理的处理器就ok了。net/http库实现了整套的http服务中的客户端、服务端接口,可以基于此轻松的发起HTTP请求或者对外提供HTTP服务。
注1:如果网络地址为空,则默认使用hhtp的端口80进行网络连接。如果处理器参数为nil,则使用默认的多路复用转发器DefaultServeMux(Default默认 Serve服务器 Mux多路复用器)。
多路复用器接收到用户的请求之后根据请求的URL来判断使用哪个处理器处理请求,然后就会重定向到对应的处理器处理请求。
http.HandleFunc是Multiplexer根据URL将请求路由给指定的Handler。Handler用于处理请求并给予响应。更严格地说,用来读取请求体、并将请求对应的响应字段(respones header)写入ResponseWriter中,然后返回:
什么是Handler。它是一个接口,定义在net/http/server.go中:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
也就是说,实现了ServerHTTP方法的都是Handler。注意ServerHTTP方法的参数:http.ResponesWriter接口和Request指针。
在Handler的注释中,给出了几点主要的说明:
Handler用于响应一个HTTP request 接口方法ServerHTTP应该用来将response header和需要响应的数据写入到ResponseWriter中,然后返回。
ResponseWriter接口的作用是用于构造HTTP response,并将响应header和响应数据通过网络链接发送给客户端。
// 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.
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
这个接口有3个方法:
type Header map[string][]string
Go有一个函数HandleFunc(),它表示使用第二个参数的函数作为handler,处理匹配到的url路径请求。HandleFunc 的第一个参数指的是请求路径,第二个参数是一个函数类型,表示这个请求需要处理的事情。没有处理复杂的逻辑,而是直接给DefaultServeMux处理,如源码:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.request 和 http.ResponseWriter 两个对象交互。
源码,相当于一个适配器:
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
创建处理器方式1:
1.type HandlerFunc func(ResponseWriter,*Request)
package main
import (
"fmt"
"net/http"
)
//创建处理函数
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world") //向文件w写入Hello world
}
func main() {
//通过URL找到处理器 第一个参数就是URL
http.HandleFunc("/", indexHandler)
//web监听端口,在localhost:端口监听9090,使用DefaluteServeMux
http.ListenAndServe(":9090", nil)
}
启动服务:go run web.go
请求服务:
创建处理器方式1:创建自己的Handler
package main
import (
"fmt"
"net/http"
)
//声明一个结构体
type IndexHandler struct{}
//给IndexHandler绑定Handler接口ServeHTTP(....)方法
func (this *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "我是MyHandler")
}
func main() {
//创建一个IndexHandler实例
myHandler := IndexHandler{}
//处理请求
http.Handle("/", &myHandler)
http.ListenAndServe(":9090", nil)
}
GO提供了NewServerMux的方法,让我们自己创建一个多路复用器:
func NewServeMux() *ServeMux
package main
import (
"fmt"
"net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello golang")
}
func main() {
myMux := http.NewServeMux()
myMux.HandleFunc("/", sayHello)
http.ListenAndServe(":9090", myMux)
}
GO提供Server结构体,我们可以创建一个Server的实例,去配置Server中的一些配置。
type Server struct {
Addr string // 定义服务监听的地址端口,如果为空,则默认监听80端口
Handler Handler // 请求被处理的业务方,默认 http.DefaultServeMux
TLSConfig *tls.Config // 可选的TLS配置,对外提供https服务
ReadTimeout time.Duration // 读取客户端请求的超时时间,包含读取请求体
ReadHeaderTimeout time.Duration // 读取请求头的超时时间,如果为空,则使用 ReadTimeout, 如果两者都没有,则没有超时时间
WriteTimeout time.Duration // 服务响应的超时时间
IdleTimeout time.Duration // 长链接空闲的超时时间
MaxHeaderBytes int // 客户端请求头的最大大小,默认为1MB
ConnState func(net.Conn, ConnState) // 指定可选的回调方法,当客户端连接状态发生改变时
ErrorLog *log.Logger // 连接错误、handlers异常或者文件系统异常时使用,默认使用标准库的logger接口
onShutdown []func() // 服务停止时触发的方法调用
}
基于以上server结构,Golang标准库提供了如下几个服务接口
func (srv *Server) Close() error // 立即关闭所有的活跃监听以及所有的连接,包括新建的连接、活跃的或者空闲的连接
func (srv *Server) ListenAndServe() error // 启动服务监听tcp连接以及将请求转发到handler中
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error // 支持https服务
func (srv *Server) Shutdown(ctx context.Context) error // 实现优雅关闭连接
基于以上server结构创建web服务:
package main
import (
"fmt"
"net/http"
)
type MyHandler2 struct{}
func (this *MyHandler2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "小王子")
}
func main() {
myHandler := MyHandler2{}
//注:这里没有HanlderFunc函数可以调用了,要在定义server时指定
server := http.Server{
Addr: ":9090",
Handler: &myHandler,
}
server.ListenAndServe()
}
Web服务的起源也来自于一点,即Server结构体
srv := http.Server{
Addr: ":9090",
在此处初始化server服务,除显示指定监听端口外,还有一个重要的默认handler参数,即 http.DefaultServeMux ,提供web服务的路由解析功能。即
在此处初始化server服务,除显示指定监听端口外,还有一个重要的默认handler参数,即 http.DefaultServeMux ,提供web服务的路由解析功能。即
http.HandleFunc("/", helloWorldHandler)
以上代码其实等同于如下:
handler := http.DefaultServeMux
handler.HandleFunc("/", helloWorldHandler)
srv := http.Server{
Addr: ":9090",
Handler: handler,
}
http的mux维护了一个路由解析的map表,服务在启动时,所有的请求路由会被解析到这个map表中,其结构体为:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
其中muxEntry 中存储着路由的路径以及对应的处理方法handler。基于此Mux接口,还可以实现更加复杂的路由协议。
er := srv.ListenAndServe()
通过以上简单的一句代码,就实现了服务的监听以及服务,那么他是如何做到的呢?带着疑问,又一次进入如海般的代码中寻找代码。
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx)
}
}
可以看出,Golang接收web是基于TCP协议之上,然后就是调用Serve方法,处理连接请求,Serve方法会启动goroutine进行异步处理,这也是高并发的基本所在。
serve方法中以无限循环方式(for)接收客户端请求并进行处理,主要逻辑如下:
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
if d := c.server.ReadTimeout; d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
}
if d := c.server.WriteTimeout; d != 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.Handshake(); err != nil {
// If the handshake failed due to the client not speaking
// TLS, assume they're speaking plaintext HTTP and write a
// 400 response on the TLS conn's underlying net.Conn.
if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
re.Conn.Close()
return
}
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
...
serverHandler{c.server}.ServeHTTP(w, w.req)
其中ServeHTTP的实现:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
在这里终于发现我们定义的路由处理的handler方法。
整体流程如图:
通过以上源码的观看,基本了解了golang处理一次web请求的大体过程。
Request 就是封装好的客户端请求,包括 URL,method,header 等等所有信息,以及一些方便使用的方法:
Handler 需要知道关于请求的任何信息,都要从这个对象中获取,一般不会直接修改这个对象
type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.).For client requests an empty string means GET.
Method string
// URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
URL *url.URL
// The protocol version for incoming requests.
// Client requests always use HTTP/1.1.
Proto string // "HTTP/1.1"
ProtoMajor int // 1
ProtoMinor int // 1
// A header maps request lines to their values.
// If the header says
//
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// Connection: keep-alive
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Connection": {"keep-alive"},
// }
Header Header
// Body is the request's body.
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
...
RemoteAddr string
...
}
ResponseWriter 是一个接口,定义了三个方法:
Header():返回一个 Header 对象,可以通过它的 Set() 方法设置头部,注意最终返回的头部信息可能和你写进去的不完全相同,因为后续处理还可能修改头部的值(比如设置 Content-Length、Content-type 等操作) Write(): 写 response 的主体部分,比如 html 或者 json 的内容就是放到这里的 WriteHeader():设置 status code,如果没有调用这个函数,默认设置为 http.StatusOK, 就是 211 状态码
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
websocket由http升级而来,首先发送附带Upgrade请求头的Http请求,所以我们需要在处理Http请求时拦截请求并判断其是否为websocket升级请求,如果是则调用gorilla/websocket
库相应函数处理升级请求。
Golang官方标准库实现的websocket在功能上有些欠缺,本次介绍的gorilla/websocket库,是Gorilla出品的速度快、质量高,并且被广泛使用的websocket库,很好的弥补了标准库功能上的欠缺。另外Gorilla Web toolkit包含多个实用的HTTP应用相关的工具库,感兴趣可以到官网主页https://www.gorillatoolkit.org自取。
gorilla/websocket库是 RFC 6455 定义的websocket协议的一种实现,在数据收发方面,提供Data Messages、Control Messages两类message粒度的读写API;性能方面,提供Buffers和Compression的相关配置选项;安全方面,可通过CheckOrigin来控制是否支持跨域。
go get github.com/gorilla/websocket
package main
import (
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 4196,
WriteBufferSize: 1124,
CheckOrigin: func(r *http.Request) bool {
//if r.Method != "GET" {
// fmt.Println("method is not GET")
// return false
//}
//if r.URL.Path != "/ws" {
// fmt.Println("path error")
// return false
//}
return true
},
}
// ServerHTTP 用于升级协议
func ServerHTTP(w http.ResponseWriter, r *http.Request) {
// 收到http请求之后升级协议
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Error during connection upgrade:", err)
return
}
defer conn.Close()
for {
// 服务端读取客户端请求
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("Error during message reading:", err)
break
}
log.Printf("Received:%s", message)
// 开启关闭连接监听
conn.SetCloseHandler(func(code int, text string) error {
fmt.Println(code, text) // 断开连接时将打印code和text
return nil
})
//服务端给客户端返回请求
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println("Error during message writing:", err)
return
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Index Page")
}
func main() {
http.HandleFunc("/socket", ServerHTTP)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe("localhost:8181", nil))
}
Upgrader
发送附带Upgrade请求头的Http请求,把 http 请求升级为长连接的 WebSocket
,结构如下:
type Upgrader struct {
// 升级 websocket 握手完成的超时时间
HandshakeTimeout time.Duration
// io 操作的缓存大小,如果不指定就会自动分配。
ReadBufferSize, WriteBufferSize int
// 写数据操作的缓存池,如果没有设置值,write buffers 将会分配到链接生命周期里。
WriteBufferPool BufferPool
//按顺序指定服务支持的协议,如值存在,则服务会从第一个开始匹配客户端的协议。
Subprotocols []string
// http 的错误响应函数,如果没有设置 Error 则,会生成 http.Error 的错误响应。
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
// 如果请求Origin标头可以接受,CheckOrigin将返回true。 如果CheckOrigin为nil,则使用安全默认值:如果Origin请求头存在且原始主机不等于请求主机头,则返回false。
// 请求检查函数,用于统一的链接检查,以防止跨站点请求伪造。如果不检查,就设置一个返回值为true的函数
CheckOrigin func(r *http.Request) bool
// EnableCompression 指定服务器是否应尝试协商每个邮件压缩(RFC 7692)。 将此值设置为true并不能保证将支持压缩。 目前仅支持“无上下文接管”模式
EnableCompression bool
}
创建Upgrader实例用于升级请求例子:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1124, //指定读缓存大小
WriteBufferSize: 1124, //指定写缓存大小
CheckOrigin: checkOrigin,
}
// 检测请求来源
func checkOrigin(r *http.Request) bool {
if r.Method != "GET" {
fmt.Println("method is not GET")
return false
}
if r.URL.Path != "/ws" {
fmt.Println("path error")
return false
}
return true
}
其中CheckOringin
是一个函数,该函数用于拦截或放行跨域请求。函数返回值为bool
类型,即true
放行,false
拦截。如果请求不是跨域请求可以不赋值。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)
升级为websocket连接并获得一个conn实例,之后的发送接收操作皆有conn,其类型为websocket.Conn。
WriteMessage
首先向客户端发送消息使用WriteMessage(messageType int, data []byte)
,参数1为消息类型,参数2消息内容:
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//判断请求是否为websocket升级请求。
if websocket.IsWebSocketUpgrade(r) {
conn, err := upgrader.Upgrade(w, r, w.Header())
conn.WriteMessage(websocket.TextMessage, []byte("hgs"))
} else {
//处理普通请求
c := newContext(w, r)
e.router.handle(c)
}
}
或者使用func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error)方法获得writer,再调用func (w *messageWriter) Write(p []byte) (int, error)将数据写入。
ReadMessage()
接受客户端消息使用ReadMessage()
该操作会阻塞线程所以建议运行在其他协程上。该函数有三个返回值分别是,接收消息类型、接收消息内容、发生的错误当然正常执行时错误为 nil。一旦连接关闭返回值类型为-1可用来终止读操作。
示例:
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//判断请求是否为websocket升级请求。
if websocket.IsWebSocketUpgrade(r) {
conn, err := upgrader.Upgrade(w, r, w.Header())
conn.WriteMessage(websocket.TextMessage, []byte("wxm.alming"))
go func() {
for {
t, c, _ := conn.ReadMessage()
fmt.Println(t, string(c))
if t == -1 {
return
}
}
}()
} else {
//处理普通请求
c := newContext(w, r)
e.router.handle(c)
}
}
同时可以为连接设置关闭连接监听,函数为SetCloseHandler(h func(code int, text string) error)
函数接收一个函数为参数,参数为nil时有一个默认实现,其源码为:
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
if h == nil {
h = func(code int, text string) error {
message := FormatCloseMessage(code, "")
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
return nil
}
}
c.handleClose = h
}
可以看到作为参数的函数的参数为int和string类型正好和前端的close(long string)对应即前端调用close(long string)关闭连接后两个参数会被发送给后端并最终被func(code int, text string) error
所使用。
//Http入口
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//判断请求是否为websocket升级请求。
if websocket.IsWebSocketUpgrade(r) {
// 收到 http 请求后升级协议
conn, err := upgrader.Upgrade(w, r, w.Header())
conn.SetCloseHandler(func(code int, text string) error {
fmt.Println(code, text)
return nil
})
// 向客户端发送消息使用 WriteMessage(messageType int, data []byte),参数1为消息类型,参数2消息内容
conn.WriteMessage(websocket.TextMessage, []byte("升级成功"))
// 接受客户端消息使用 ReadMessage(),该操作阻塞线程所以建议运行在其他协程上。
//返回值(接收消息类型、接收消息内容、发生的错误)当然正常执行时错误为 nil。一旦连接关闭返回值类型为-1可用来终止读操作。
go func() {
for {
t, c, _ := conn.ReadMessage()
fmt.Println(t, string(c))
if t == -1 {
return
}
}
}()
} else {
//处理普通请求
c := newContext(w, r)
e.router.handle(c)
}
}
则断开连接时将打印code和text
注意:要想使断连处理生效必须要有ReadMessage()
操作否则不会触发断连处理操作。
大家看到这么多的开发框架,可能一时间都不知道该怎么选择了。小慕这里主要推荐Beego、Gin、Iris这三款框架~
框架特性:
Beego是一个RESTful风格的框架,具备MVC模型,在快速开发项目的同时支持代码的热编译、自动化测试以及自动化打包部署等功能,让开发更快更简单。
Beego不仅仅支持智能路由,同时还解决了Mux框架不支持路由参数的痛点。另外Beego还可以进行QPS、内存消耗、CPU使用、goroutine运行状况的智能监控,让开发人员以及运维人员能够更方便的掌握线上项目的运行情况。
Beego内置了Session、缓存操作、日志记录、配置解析、性能监控、上下文操作、ORM、请求模拟八大模块,让开发更加方便。
由于Beego采用的是Go语言内置的HTTP包来处理网络请求,所以Beego能够完整的发挥出Go语言支持并发的特性,目前也有不少高并发的产品是使用Beego框架进行开发的。
推荐理由:
Beego是一款由Go语言开发专家“Asta谢”开发的一款简单易用的企业级Go应用开发框架,具备全中文的官方网站和教程,不同于其他框架语言Beego不仅仅提供图文教程还提供了视频教程,这一点对国内的开发人员来讲是非常友好的。
框架特性:
Gin是根据Go语言第一个Web开发框架Martini的思想进行设计的,其使用httprouter模块将速度提高了近40倍。
Gin框架内置了非常多的中间件,比如Logger、Gzip、Authorization等,同时如果你想扩展中间件,Gin也是非常方便的。
Gin内置了数据验证器,同时也支持用户自定义数据验证器。
Gin可以收集程序运行中的错误信息,捕获到由panic引起的程序崩溃,从而保证我们的Web应用一直处于正常运行状态。
Gin支持标准的表单数据格式、JSON数据格式、XML数据格式以及YAML数据格式。
Gin默认支持两种将请求数据绑定到类型上的模型绑定方式,分别是Must Bind方式和Should Bind方式。
推荐理由:
一个完全由Go语言编写而成的Http Web框架,提供了一个速度更快的、性能更高的Go语言Web开发框架。
框架特性:
关于这一点有种说法是:Iris是最快的Go Web开发框架。关于它的具体速度,我们可以到官网看看基准测试结果或者自己下载框架代码运行基准测试看看结果。
Iris被称作Go语言版本的Javascript/Node.js框架,其拥有非常简单的API,让开发者更容易上手。
Iris内置很多中间件,同时也支持自定义中间件,如日志、权限管理、跨域请求等。
Iris框架可以针对任何的HTTP请求自定义相关处理函数。
Iris支持markdown、xml、Json、Jsonp等多种数据格式进行视图渲染。
使用Iris框架开发时,源码一旦发生改变就会自动编译加载新的内容。
推荐理由:
如果你正在选择一款性能不错、轻量级且简单易上手的Go语言Web开发框架,Iris是一个不错的选择。
最后具体选择哪一款框架,可以自行测试学习一下。
框架是我们开发的工具,只有工具顺手了,才能真正提高自己的开发效率。