搭建一个简单的web服务
package main
import (
"fmt"
"net/http"
"log"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析参数,默认是不会解析的
r.ParseForm()
//打印参数
fmt.Println(r.Form)
//打印出url
fmt.Println("path:", r.URL.Path)
//打印出scheme
fmt.Println("scheme:", r.URL.Scheme)
//打印出所有的参数
for k, v := range r.Form {
fmt.Println("key:", k, ",value:", v)
}
//输出到客户端
fmt.Fprintf(w, "hello,go!")
}
func main() {
//设置访问的路由
http.HandleFunc("/hello", sayHello)
//设置监听的端口
err := http.ListenAndServe(":9090", nil)
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}
运行命令
go run serve.go
在页面上访问
http://localhost:9090/hello
显示结果
hello,go!
后台控制台结果
map[a:[1 3] b:[2]]
path: /hello
scheme:
key: a ,value: [1 3]
key: b ,value: [2]
Go http包详解
go的http有两个核心功能:Conn、ServeMux
Conn的goroutine
与我们一般编写的http服务器不同,Go为了实现高并发和高性能,使用了goroutines来处理Conn的读写事件,这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证
Go在等待客户端请求里面是这样写的:
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handle,该handle中便可以读取到响应的header信息,这样保证了每个请求的独立性。
ServeMux的自定义
前面介绍conn.serve的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。serveMux的结构如下
type ServeMux struct {
// 锁,由于请求设计到并发处理,因此这里需要一个锁机制
mu sync.RWMutex
// 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
m map[string]muxEntry
// 是否存在任意的规则中带有host信息
hosts bool // whether any patterns contain hostnames
}
下面看看muxEntry定义
type muxEntry struct {
// 这个路由表达式对应的那个handle
h Handler
// 匹配字符串
pattern string
}
接着看一下Handle的定义
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}
Handle是一个接口,但是上文中的例子中sayHell函数并没有实现ServeHTTP这个方法,为什么能添加呢?原来在http包里面还定义了一个类型HandleFunc,我们定义sayHell函数,通过调用HandleFunc(f)强制把sayHello转换成了HandleFunc类型,这样f就拥有了ServeHTTP方法
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
路由器里面存储好了响应的路由规则之后,具体的请求又是怎么分发的呢?看下面的默认路由器实现的ServeHTTP方法
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
如果是*则关闭连接;否则调用mux.Hander®返回对应路由器的handle,并执行hander.ServeHTTP(w,r)方法
那么mux.Handler®是怎么处理的呢?
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
// If r.URL.Path is /tree and its handler is not registered,
// the /tree -> /tree/ redirect applies to CONNECT requests
// but the path canonicalization does not.
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
原来它是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到后返回存储的handler,调用这个handler的ServerHTTP接口就可以执行到响应的函数了
通过上面介绍,我们了解到整个路由过程,Go其实支持外部实现的路由器,ListenAndServe 的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们在自己实现的路由器的ServeHTTP里面实现自定义路由功能
package main
import (
"fmt"
"net/http"
"log"
)
type MyMux struct{
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/hello" {
sayHello(w,r)
return
}
log.Fatal("not found")
http.NotFound(w, r)
return
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析参数,默认是不会解析的
r.ParseForm()
//打印参数
fmt.Println(r.Form)
//打印出url
fmt.Println("path:", r.URL.Path)
//打印出scheme
fmt.Println("scheme:", r.URL.Scheme)
//打印出所有的参数
for k, v := range r.Form {
fmt.Println("key:", k, ",value:", v)
}
//输出到客户端
fmt.Fprintf(w, "hello,go!")
}
func main() {
//设置访问的路由
// http.HandleFunc("/hello", sayHello)
//设置监听的端口
err := http.ListenAndServe(":9090", &MyMux{})
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}
Go代码的执行流程
通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程
首先调用http.HandleFunc
调用DefaultServeMux的HandleFunc
调用DefaultServeMux的Handle
往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则其次调用http.ListenAndServe(":9090", nil)
实例化Server
调用Server的ListenAndServe()
调用net.Listen(“tcp”, addr)监听端口
启动一个for循环,在循环体中Accept请求
对每个请求实例化一个Conn,并开启一个goroutine为这个请求进行服务 go c.serve()
读取每个请求的内容w, err := c.readRequest()
判断handler是否为空,如果没有设置handler,handler就设置为DefaultServeMux
调用handler的ServeHttp
根据request选择handler,并且进入到这个handler的ServeHTTP中 mux.handler®.ServeHTTP(w,r)选择handler:
判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
如果有路由满足,则调用这个路由的ServeHttp方法
如果没有路由满足,则调用NotFoundHandleer的ServeHttp方法