理解 HTTP 构建的网络应用只要关注两个端—客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。
接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer — DefautServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet
因此,理解go中的http服务,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux结构,同时也实现了Handler接口。
对于handler的其实没有合适的中文词语,只可意会,不可言传的感觉。为了更好的说明问题,本文约定了如下规则:
hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数
handler处理器(函数): 经过HandlerFunc结构包装的handler函数,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。
handler对象:实现了Handler接口ServeHTTP方法的结构。
handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了ServeHTTP方法。很多情况下它们的功能类似,下文就使用统称为handler。这算是Golang通过接口实现的类动态类型吧。
Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
从handler的结构来看,需要实现servehttp的方法
servemux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
从这个里面可以看出,mux最重要的部分是muxentry的map结构
其中key代表的是url的一些模式,value是muxentry结构
当然,所谓的ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler,后面再做解释。
server
除了ServeMux和Handler,还有一个结构Server需要了解。从http.ListenAndServe的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
而server的结构如下
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
可以看到server存储了服务器处理请求常见的字段,其中handler字段保留了handler接口。如果server没有handler对象,就会使用defautmux来做mux。
创建HTTP服务
创建http服务首先需要注册路由(提供url以及handler的映射)
然后实例化一个对象,开启对客户端的监听
对于net/http的开启http服务,可以有两种写法
注册路由:
Http.handlerfunc("",indexhandler)
开启监听:
Http.listenandserver(addr,handler)
注册路由
Http.handlerfunc选取了defautservemux作为mux
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
什么是defautservemux?
defaultservemux是servemux的一个实例。也提供了使用new的方法创建一个servemux。
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
Golang标准库http包提供了基础的http服务,这个服务又基于Handler接口和ServeMux结构的做Mutilpexer。实际上,go的作者设计Handler这样的接口,不仅提供了默认的ServeMux对象,开发者也可以自定义ServeMux对象。
本质上ServeMux只是一个路由管理器,而它本身也实现了Handler接口的ServeHTTP方法。因此围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件。
自定义HANDLER
就是用户可以自己定义一个handler,只要能够实现servehttp的方法就行
type textHandler struct {
responseText string
}
func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, th.responseText)
}
type indexHandler struct {}
func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “text/html”)
html := `<doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
<a href="/welcome">Welcome</a> | <a href="/message">Message</a>
</p>
</body>
` fmt.Fprintln(w, html) }
func main() {
mux := http.NewServeMux()
mux.Handle("/", &indexHandler{})
thWelcome := &textHandler{"TextHandler !"}
mux.Handle("/text",thWelcome)
http.ListenAndServe(":8000", mux)
}
上面自定义了两个handler结构,都实现了ServeHTTP方法。我们知道,NewServeMux可以创建一个ServeMux实例,ServeMux同时也实现了ServeHTTP方法,因此代码中的mux也是一种handler。把它当成参数传给http.ListenAndServe方法,后者会把mux传给Server实例。因为指定了handler,因此整个http服务就不再是DefaultServeMux,而是mux,无论是在注册路由还是提供请求服务的时候。
有一点值得注意,这里并没有使用HandleFunc注册路由,而是直接使用了mux注册路由。当没有指定mux的时候,系统需要创建一个默认的defaultServeMux,此时我们已经有了mux,因此不再需要http.HandleFucn方法了,直接使用mux的Handle方法注册即可。
此外,Handle第二个参数是一个handler(处理器),并不是HandleFunc的一个handler函数,其原因也是因为mux.Handle本质上就需要绑定url的pattern模式和handler(处理器)即可。既然indexHandler是handle(处理器),当然就能作为参数,一切请求的处理过程,都交给器实现的接口方法ServeHTTP就行了。
创建handler处理器
直接创立handler函数,然后调用go的方法把函数转换成handler
func text(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w, “hello world”)
}
func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “text/html”)
html := `<doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
<a href="/text">Welcome</a> | <a href="/message">Message</a>
</p>
</body>
` fmt.Fprintln(w, html) }
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(index))
mux.HandleFunc("/text", text)
http.ListenAndServe(“localhost:8081”, mux)
}
通过这个就可以实现一个http服务
自定义server
如果说自定义的server没有提供mux,那么将会使用defaultservemux
如下图
func main(){
http.HandleFunc("/", index)
server := &http.Server{
Addr: ":8000",
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
}
server.ListenAndServe()
}
创建server的时候需要给出addr,readtime,writetime
然后进行监听活动
如果自定义了mux
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", index)
server := &http.Server{
Addr: ":8000",
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
Handler: mux,
}
server.ListenAndServe()
}
对于HTTP,是一个以请求和响应为应答模式的传输协议
请求的结构
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
…
ctx context.Context
}
URL
go里面提供了一个URL结构来映射到HTTP的URL
type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawQuery string
Fragment string
}
URL的格式比较明确,更好的名词应该是URI—>统一资源定位
URL中比较重要的是查询字符串query,通常作为get请求的参数
query是一些用&符号分割的键值对
但是url的编码是ASSIC吗,需要query进行urlencode。
go中可以通过request.URI.Rawquery来读取query
func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln(“URL”, r.URL, “HOST”, r.Host, “Method”, r.Method, “RequestURL”, r.RequestURI, “RawQuery”, r.URL.RawQuery)
fmt.Fprintln(w, info)
}
通过这个就可以读出request的信息
hander
hander是http的重要组成部分,request中有hander结构,hander本身是一个map结构,将http协议的header的key-value映射成一个图
Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two
Header = map[string][]string{
"Accept-Encoding": {"gzip, deflate"},
"Accept-Language": {"en-us"},
"Foo": {"Bar", "two"},
}
Golng 提供了不少打印函数,基本上分为三类三种。即 Print Println 和Printf。
Print比较简单,打印输出到标准输出流,Println则也一样不同在于多打印一个换行符。至于Printf则是打印格式化字符串,三个方法都返回打印的bytes数。Sprint,Sprinln和Sprintf则返回打印的字符串,不会输出到标准流中。Fprint,Fprintf和Fprinln则把输出的结果打印输出到io.Writer接口中,http中则是http.ReponseWriter这个对象中,返回打印的bytes数。
BODY
http的数据通信主要通过body进行传输
go把body封装成request的body,这是一个readcloser接口
接口方法reader也是一个接口,后者具有一个read(p []byte)的方法,于是body可以通过读取byte数组来获得请求的数据
func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln(r.Header.Get("Content-Type"))
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintln(w, info, string(body))
}
response
请求和相应的报文是类似的,相关的处理和结构也是相似的。go构造响应的结构是responsewriter接口
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
可以看到同样具有一个header方法返回一个hander的map结构
writeheader会返回响应的状态码
write返回的是客户端的数据
func indexHandler(w http.ResponseWriter, r *http.Request) {
str := `
同样可以写入状态码,,,,注意 不能往head的数据里面写东西,不然还会显示write的东西
w.writerheader(404)
重定向
通过head.set重新定位
w.Header().Set(“Location”, “https://google.com”)
w.WriteHeader(302)
与请求的Header结构一样,w.Header也有几个方法用来设置headers
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value)
}
func (h MIMEHeader) Add(key, value string) {
key = CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}
func (h MIMEHeader) Set(key, value string) {
h[CanonicalMIMEHeaderKey(key)] = []string{value}
}
Set和Add方法都可以设置headers,对于已经存在的key,Add会追加一个值value的数组中,,set则是直接替换value的值。即 append和赋值的差别。