本文简单的介绍golang http服务端路由的注册解析,简单剖析http server handler的代码。
HTTP server–简而言之就是一个支持http协议的服务,http是一个相对简单的请求—响应的协议,通常是运行在TCP连接之上, 通过客户端发送请求到服务端,获取服务端的响应。基本模型如下:
package main
import (
"fmt"
"net/http"
)
//③处理请求,返回结果
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
//①路由注册
http.HandleFunc("/", sayhelloName)
//②服务监听
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
如上example已经实现了一个简单的http 服务,其仅仅处理一个URL 为IP:8080/的请求, 其返还的信息是“hello world”。
以上代码中有两处至为关键,http服务怎么知道有sayhelloName函数被注册以及其为谁注册,另一处则是请求怎样找到其处理函数。
从上面的代码路由注册开http.HandleFunc始看:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
这里使用了http.HandleFunc是使用http包自带的DefaultServerMux来进行服务的路由注册与管理。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
//空路径判断
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
//空handler判断
if handler == nil {
panic("http: nil handler")
}
//注册冲突判断
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
//注册添加到map表
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
//是否是以URL hosts开头
if pattern[0] != '/' {
mux.hosts = true
}
// 如果URL以字符/结尾,则多注册注册一个redirectHandler,访问/tree时重定向到/tree/
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}
大部分的注册函数到此结束, 但是有些需要重定向的则需要继续看下面这部分:
func RedirectHandler(url string, code int) Handler {
return &redirectHandler{url, code}
}
type redirectHandler struct {
url string
code int
}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
Redirect(w, r, rh.url, rh.code)
}
以上代码主要是处理一些重定向的请求。
路由的解析过程是以上过程的反向寻找,它在http server运行的时候,等待接受到http请求一行解析 http request的头部信息,找到request的URL,根据该URL进行查询。这里先简单说这些,下面http服务运行中进行详细分析。
ServeMux
Http ServerMux 其实是一个http的多路转发器(路由器),负责接受http handler的注册和路由解析。在服务开始对外服务之前接受handler的注册, 将所有的路径与处理函数成对的存放在一个map表中, 当接收到URL请求的时候根据URL请求所带的路径到map表中进行查询,查到对应的handler函数对该请求进行处理或者说成是服务。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeHTTP
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc
type HandlerFunc func(ResponseWriter, *Request)
Server
http Server 是一个http服务对象, 是一个http服务的总体数据结构。
type Server struct {
Addr string // TCP address to listen on, “:http” if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
ReadTimeout time.Duration // maximum duration before timing out read of the request
WriteTimeout time.Duration // maximum duration before timing out write of the response
TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
// MaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN/ALPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
// If TLSNextProto is nil, HTTP/2 support is enabled automatically.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
}
服务创建的三种模式是说http服务三种代码风格,以及http ListenAndServer时候的对象配置模式。其实更准确的说法为路由服务注册的三种方式以及对应的服务创建模式。
模式一
模式一也是最简单的模式,就是类似于例子中那种写法,直接ListenAndServe(:port, nil), 这里写的nil实际上是使用了http包自带的http.DefaultServeMux来进行路由的注册于解析。
这种方法适合非常简单和没有太多路由请求的简单httpserver, 比如说这个http server仅仅支持单一的IP:Port/talk 这样的路由,或者简单的几个路由。对应稍微重一点服务来说都是不好用的。
package main
import (
"fmt"
"net/http"
)
//③处理请求,返回结果
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
//①路由注册
http.HandleFunc("/", sayhelloName)
//②服务监听
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
模式二
第二种模式就是自定义一个ServeMux, 然后自己在自定义的ServeMux注册路由于路由处理函数。最后ListenAndServe自定义的ServeMux。
package main
import (
"fmt"
"net/http"
)
//③处理请求,返回结果
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
mux := http.NewServeMux()
//①路由注册
mux.Handle("/", sayhelloName)
//mux.Handlefunc("/", sayhelloName)
//②服务监听
err := http.ListenAndServe(":8080", mux)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
模式三
第三种模式是自定义server, 然后使用自定义的server的ListenAndServe()。这种模式完全的掌握和管理服务端的事物,一般的服务都采用这种模式。这种写法也比较符合对象化的概念。
package main
import (
"fmt"
"net/http"
)
//③处理请求,返回结果
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
mux := http.NewServeMux()
//①路由注册
mux.Handle("/", sayhelloName)
//mux.Handlefunc("/", sayhelloName)
s := &http.Server{
Addr: ":8080:,
Handler: mux, //指定路由或处理器,不指定时为nil,表示使用默认的路由DefaultServeMux
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
MaxHeaderBytes: 1 << 20,
ConnState: //指定连接conn的状态改变时的处理函数
...
}
//②服务监听
err := s.ListenAndServe()
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
在前面我们分析了http的路由注册,在路由注册完成以准备开始运行服务并提供访问,通常的的起始点是ListenAndServer方法。
这里我们依然先使用例子中的默认方式来进行分析:
http.ListenAndServe(":12345", nil)-->
func ListenAndServe(addr string, handler Handler) error {
// 构造一个http的server,其实我们上面所说的第三种方式就是是自定义了一个server
server := &Server{Addr: addr, Handler: handler}
// server 进行监听和提供访问
return server.ListenAndServe()
}
上面的代码和注释中以及完成了服务的构造,以及监听并对外服务。
我们先来看一下服务的构造, 很简单:
server := &Server{Addr: addr, Handler: handler} 就像我们模式三种的那样给new 一个server,然后给server的addr 和handler进行赋值。
我们再来看一下Server的ListenAndServe():
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
//这里创建一个tcplisten, 监听tcp连接
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
func Listen(net, laddr string) (Listener, error) {
addrs, err := resolveAddrList(context.Background(), "listen", net, laddr, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err}
}
var l Listener
switch la := addrs.first(isIPv4).(type) {
case *TCPAddr:
//ListenTCP-->listenTCP-->fd=internetSocket return &TCPListener{fd}
l, err = ListenTCP(net, la)
case *UnixAddr:
l, err = ListenUnix(net, la)
default:
return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
}
if err != nil {
return nil, err // l is non-nil interface containing nil pointer
}
return l, nil
}
从代码来看先创建了一个tcp的监听,实际上是一个的TCP连接,然后将该TCP连接的fd转换成为TCPListener,再讲该TCPListener转换为tcpKeepAliveListener, 主要是tcpKeepAliveLitener的Accept方法带有定时发送心跳的设置,以实现连接保持。
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true) //设置发送心跳
tc.SetKeepAlivePeriod(3 * time.Minute) //发送心跳周期
return tc, nil
}
真正的开始服务现在才开始:
func (srv *Server)Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
// TODO: allow changing base context? can't imagine concrete
// use cases yet.
baseCtx := context.Background()
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
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
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
上面主要的部分是 for{}里面, Accept会获取到连接请求,然后调用srv.newConn()进行连接,设置连接状态,然后有独立的go 线程进行Serve该连接。这就是是http服务提供服务。接下来,c.serve()中通常的request请求会调用到 serverHandler{c.server}.ServeHTTP(w, w.req), 准备开始路由解析过程。
接着上面http服务serverHandler{c.server}.ServeHTTP(w, w.req):
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)
}
从这段代码上可以看出上面创建http.ListenAndServe(“:8080”, nil) 时填写的nil为啥能够正确提供服务了。继续分析代码最后一行handler实际上使用mux,因此继续如下:
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)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
最终调用到mux.handler函数:
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
这里开始调用match方法,从字面上来讲就行对比请求的URL,简单猜测就是查表,再继续往下看:
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
果然,一个key Value的表查询。这样该请求的handler方法就找到了,因此http请求的路由解析或者说根据请求查询处理函数其实就是一个mux的handler表查询。
查找到handler后执行h.serverHttp(w,r)方法,
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
将handler转换为ServeHTTP的借口。至此路由解析与http服务执行的分析接近结束,剩下的是自己的handler处理业务逻辑。
最常用的http方法用俗语表示为增删改查————>Post, Delete, Put, Get。处理这四种最常用的方法外http还有比较常用的Head,Patch,Trace,Options 方法,这里简单的说Head跟Get非常类似,Patch跟Put非常类似。
http 方法在request请求的Hearder里面有设置,在发送http请求的时候其仅仅只是http包的一个属性。在server接受到http request的时候其实是不太care这个属性的,方法一直被写到知道路由信息解析完成到达这层路由的处理函数中,在该函数在红检测http request的方法,在进行Dispatch, 这种处理方式相当于把 http request方法当做是http请求的一个路径,因此处理的时候可以当做二次路由进行处理。
比较好的服务端或者大型http服务的服务段在这种情况下通常是自定义server,在server中的handler定义一个对象,对象有Post、Put、Delete和Get方法, 把request的方法当做对象的方法进行调用。
http包自带有部分的错误处理或者说重定向函数:FileServer,NotFoundHandler、StripPrefix、TimeoutHandler,除了这些,http还自带RedirectHandler函数是用来专门提供重定向的:它返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到其他URL上。
TLS 是一种加密的协议, https服务即为http TLS加密的结构,在需要http加密的时候,其服务端和客户端都有相应的修改。
HTTP服务段在建立服务的时候使用如下模式:
http.ListenAndServeTLS(":8081", "server.crt",
"server.key", nil)
以上方法既可以给HTTP服务段进行加密,只是server.crt跟server.key 需要生成或购买。
HTTP客户端则如下:
tr := &http.Transport{
TLSClientConfig: &tls.Config{****}, //****这里是TLS的相关设置
}
client := &http.Client{Transport: tr}
根据详细的内容参见:https://studygolang.com/articles/2946
这里主要是关于golang语言自注册的http的handler在处理请求是遇到错误的话很多时候直接进行abort或者panic的处理。 这个跟golang语言的语法有关系,golang中的recover会补货http request handler里面遇到错误时给出的panic,从而关闭连接,整体进程不会受到影响。
一般来讲,http服务端的程序相对简单,代码模型如下:
第一种模型–使用DefaultClient:
resq, err = http.Get(URL)
resq, err = http.Post(URL, BodyType, BodyBuffer)
resq, err = http.PostForm(URL, url.Values{key:value,key:value...})
// 设置关闭response主体
defer resp.Body.Close()
if err!= nil {
// TODO 错误处理
}
if resp.StatusCode != ?? {
//这里主要是处理一些异常的状态值
}
//读取response的Body信息
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
//TODO 错误信息
}
这种格式写法简单,但是无法设置头部信息。
第二种模型:
//初始化一个Client
client := &http.Client{}
//创建request
req, err := http.NewRequest("GET", URL, nil)
//设置Header
req.Header.Set("string of Key", "string of Value")
// 发送请求并等待response
resp, err := client.Do(req)
// 设置关闭response主体
defer resp.Body.Close()
if err != nil {
//TODO 错误处理
}
//检查response 状态值并处理
if resp.StatusCode != ?? {
//这里主要是处理一些异常的状态值
}
//读取response的Body信息
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
//TODO 错误信息
}
这种写法自己定义client, 自创request,可以设置request头信息。
由此,如果遇到https这种请求,则只能使用第二种模式。如下:
tr := &http.Transport{
TLSClientConfig: &tls.Config{****}, //****这里是TLS的相关设置
}
client := &http.Client{Transport: tr}
2XX 通常表示请求成功完成。具体其信息,请参见http.status
200 200OK, 表示请求被正确处理
201 针对POST, 表示成功创建请求的对象
3XX 通常表示请求成功完成。具体其信息,请参见http.status
4XX 通常表示用户信息错误–客户端配置的信息出错。具体其信息,请参见http.status
400 表示bad request
401 表示用户认证错误
403 表示禁止访问
404 表示访问的资源未发现
5XX 通常表示请求成功完成。具体其信息,请参见http.status
500 内部错误
参考文献: