golang 构建HTTP服务

栏目: Go · 发布时间: 5年前

内容简介:一个go最简单的Http服务器程序除去细节,理解HTTP构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自client的request,以及server端的response。所谓的http服务器,主要在于如何接受client的request,并向client返回response。接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer--DefaultServeMux,也可以

一个 go 最简单的Http服务器程序

package main

import (
    "fmt"
    "net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe("127.0.0.0:8000", nil)
}

HTTP

除去细节,理解HTTP构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自client的request,以及server端的response。所谓的http服务器,主要在于如何接受client的request,并向client返回response。

接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer--DefaultServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(hander),后者将对request进行处理,同时构建response。

简单总结就是这个流程:

golang 构建HTTP服务

因此,理解go中的http服务,,最重要的就是要理解Multiplexer和hander,Golang中的Multiplexer基于ServerMux结构,同时也实现了Handler接口。

·hander函数:具有func(w http.ResponseWriter, r *http.Requests)签名的函数

·handler处理器(函数):经过HanderFunc结构包装的handler函数,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。

·handler对象:实现了Hander接口ServeHTTP方法的结构。

golang 构建HTTP服务

Golang的http处理流程可以用下面一张图表示,后面内容是针对图进行说明:

golang 构建HTTP服务

Handler

Golang没有继承,类多态的方法可以通过接口实现。所谓接口则是定义声明了函数签名,任何结构只要实现了与接口函数签名相同的方法,就等同于实现了接口。go的http服务都是基于handler进行处理的。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。

ServeMux

了解了Handler之后,再看ServeMux。ServeMux源码很简单:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool 
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux结构中最重要的字段为m,这是一个map,key是一些url模式,value是一个muxEntry结构,后者里定义存储了具体的url模式和handler。

当然,所谓的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字段也保留Hander接口。如果Server接口没有提供Handler结构对象,那么会使用DefaultServeMux做Multiplexer。

创建HTTP服务

创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。

http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil)

或者:

server := &Server{Addr: addr, Handler: handler}

server.ListenAndServe()

http注册路由

net/http包暴露的注册路由的api很简单,http.HandleFunc选取了DefaultServeMux作为multiplexer:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

实际上,DefaultServeMux是ServeMux的一个实例。当然http包也提供了NewServeMux方法创建一个ServeMux实例,默认则创建一个DefaultServeMux:

// 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

注意,go创建实例的过程中,也可以使用指针方式,即

type Server struct{}

server := Server{}

和下面的一样都可以创建Server的实例

var DefaultServer Server

var server = &DefalutServer

因此DefaultServeMux的HandleFunc(pattern,handler)方法实际是定义在ServeMux下的:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

上述代码中,HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

一旦这样做了,就意味着我们的indexHandler函数也有了ServeHTTP方法。

此外,ServeMux的Handle方法,将会对pattern和handler函数做一个map映射:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    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)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }

    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {

        path := pattern
        if pattern[0] != '/' {
            path = pattern[strings.Index(pattern, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

由此可见,Handle函数的主要目的在于把handler和pattern模式绑定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息。Server的m字段就是map[string]muxEntry这样一个map。

此时,pattern和handler的路由注册完成。

开启监听

注册好路由之后,启动web服务还需要开启服务器监听。http的ListenAndServer方法中可以看到创建了一个Server对象,并调用了Server对象的同名方法:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

func (srv Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)})
}

Server的ListenAndServer方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入Serve方法:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    ...

    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        ...
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

处理请求

监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在server方法之中。

serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来获取连接数据并使用newConn方法创建连接对象。最后使用goroutein协程的方式处理连接请求。因此每一个连接都开启了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    defer func() {
        if err := recover(); err != nil {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, StateClosed)
        }
    }()

    ...

    for {
        w, err := c.readRequest(ctx)
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        ...
        
        }
        
        ...
    
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
    }
}

尽管serve很长,里面的结构和逻辑还是很清晰的,使用defer定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w,w.req)方法处理请求了。最后就是请求处理完毕的逻辑。serverHandler是一个重要的结构,它仅有一个字段,即Server结构,同时它也实现了Hander接口方法ServeHTTP,同时它也实现了Handler接口方法ServeHTTP,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。如果server对象没有制定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化Handler的ServeHTTP方法。

type serverHandler struct {
    srv *Server
}

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)
}

这里DefaultServeMux的ServeHTTP方法其实也是定义在ServeMux结构中的,相关代码如下:

func (mux *ServeMux) (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)
}

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
}

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
}

mux的ServeHTTP方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法。

mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。

在match方法中,mux的m字段是map[string]muxEntry,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的pattern模式与实际url匹配的handler函数并返回。

返回的结构一直传递到mux的ServeHTTP方法,接下来调用handler函数的ServeHTTP方法,即IndexHandler函数,然后把reponse写到http.RequestWirter对象返回给客户端。

上述函数运行结束即serverHandler{c.server}.ServeHTTP(w, w.req)运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。

至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。

参考:

https://www.yuque.com/docs/share/24a1bd1c-e32d-4268-9115-bf03f53677d3


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First HTML5 Programming

Head First HTML5 Programming

Eric Freeman、Elisabeth Robson / O'Reilly Media / 2011-10-18 / USD 49.99

What can HTML5 do for you? If you're a web developer looking to use this new version of HTML, you might be wondering how much has really changed. Head First HTML5 Programming introduces the key featur......一起来看看 《Head First HTML5 Programming》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具