51

golang系列教程(八)-http server

 4 years ago
source link: https://www.tuicool.com/articles/InqeEba
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如果搜索golang http server,会发现网上有很多不同的写法,本节将介绍多种写法,并把他们的关系捋清楚。

写法1

直接传入函数

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
	http.HandleFunc("/say_hello", SayHello)
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
复制代码

写法2

定义结构体,需要实现ServerHTTP方法。

type ValueHandler struct {}
func (p *ValueHandler) ServerHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("value-pretty"))
}
func main() {
	http.Handle("/get_value", &ValueHandler)
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
复制代码

1,2两种写法极其相似,区别在于写法2需要一个结构体,并且必须实现ServerHTTP这个接口。1其实是对2的简化,相当于在net/http帮大家定义了一个实现了ServerHTTP的函数定义。

  • http.HandleFunc 注册了路由关系
  • http.ListenAndServe表示启动了一个服务。

写法3

显示使用Mux,Mux即multiplexor,保存了路由和方法的映射。可以记录多个url和handler的对应关系。

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/say_hello", SayHello)
    err := http.ListenAndServe(":12345", mux)
    if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
复制代码

这里可能会问,http.HanlderFunc没有使用mux,也可以注册多路映射。实际是底层有DefaultMux,在http Server中若没有传递,即用默认的。以下是http.HanlderFunc定义:

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

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
复制代码

写法4

使用server.ListenAndServe

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/say_hello", SayHello)

	server := http.Server{
		Addr:        ":12345",
		Handler:     serveMux,
		ReadTimeout: 5 * time.Second,
	}
    err := server.ListenAndServe()
    if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
复制代码

和写法3的区别,在于监听函数http.ListenAndServe,替换为了server.ListenAndServe 。其实Server结构体是必然存在的,http.ListenAndServe只是做了封装,实际还是生成了一个Server字段,默认填了addr, handler两个参数。以下是http.ListenAndServe定义

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

复制代码

显示指定http.Server,可以灵活选择更多配置。比如超时时间。

小结

四种写法本质上是一样的,建议使用最完整的方法4。 因为其能提供更完整,灵活的配置,同时也并不复杂。

流程分析

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。

HandleFunc 流程

如果是Http.HandleFunc,首先会调用调用了DefaultServeMux的HandleFunc。

进入实际流程:

  • 调用了DefaultServeMux的Handle
  • 往路由映射表map[string]muxEntry,添加对应的handler和路由规则
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e

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
}
复制代码

ListenAndServe流程

若http.ListenAndServe(":9090", nil),首先实例化Server

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

type Server struct {
    Addr    string  // TCP address to listen on, ":http" if empty
    Handler Handler // handler to invoke, http.DefaultServeMux if nil

    TLSConfig *tls.Config
    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger

    disableKeepAlives int32     // accessed atomically.
    inShutdown        int32     
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     

    mu         sync.Mutex
    listeners  map[*net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}
复制代码

Server结构比较复杂,关注ReadTimeout,WriteTimeout,Handler几个常用字段即可。

实际流程:

  • 调用Server的ListenAndServe()
  • 调用net.Listen("tcp", addr)监听端口,拿到Listen的实例
  • srv.Serve 传入上面的Listen示例
  • Accept请求,每收到一个请求,开启一个协程处理
  • 协程中会根据路由和handler的映射,选择handler来处理请求

处理函数如何拿到参数

the http.Request 包含了请求的所有信息,我们可以从中拿出请求的参数:

  • 针对Get 请求: vars := r.URL.Query(); vars["username"][0]
  • 针对Post 表单请求,r.ParseForm(); r.Form("username")
  • 针对Post json请求: 解析r.Body
// deal get
func SayHello(w http.ResponseWriter, r *http.Request) {
    vars := r.URL.Query()
    username := vars["username"][0]
	io.WriteString(w, "hello world " + username)
}

// 处理表单
func SayHello(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    username, err :=  request.Form["username"]
	io.WriteString(w, "hello world " + username)
}

//deal json
type User struct {
    UserName string
}
func SayHello(w http.ResponseWriter, r *http.Request) {
        err := json.NewDecoder(r.Body).Decode(&u)
        if err != nil {
            http.Error(w, err.Error(), 400)
            return
        }
        io.WriteString(w, "hello world " + u.Username)
}
复制代码

中间件抽象

如果需要计算接口处理时间,那么我们可以这么写

func SayHello(w http.ResponseWriter, r *http.Request) {
        timeStart := time.Now()
        err := json.NewDecoder(r.Body).Decode(&u)
        if err != nil {
            http.Error(w, err.Error(), 400)
            return
        }
        io.WriteString(w, "hello world " + u.Username)
        timeElapsed := time.Since(timeStart)
        fmt.Println(timeElapsed)
}

复制代码

存在一个问题,就是如果有二十个接口,那同样的代码得写20次,当有类似需求增加,或者修改时。需要改几十处。这些同样的代码,我们要想办法抽象起来。

func timeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
        timeStart := time.Now()

        // next handler
        next.ServeHTTP(wr, r)

        timeElapsed := time.Since(timeStart)
        fmt.Println(timeElapsed)
    })
}

复制代码

如此就可以通过中间件的思想,抽象出公用代码。而且这种中间件思想,还可以嵌套使用。拥有很强的扩展性。

更优雅的写法

// 示例来自《go语言高级编程》
r = NewRouter()
r.Use(timeMiddleware)
r.Use(ratelimitMiddleware)
r.Add("/", SayHello)
复制代码
type middleware func(http.Handler) http.Handler

type Router struct {
    middlewareChain [] middleware
    mux map[string] http.Handler
}

func NewRouter() *Router{
    return &Router{}
}

func (r *Router) Use(m middleware) {
    r.middlewareChain = append(r.middlewareChain, m)
}

func (r *Router) Add(route string, h http.Handler) {
    var mergedHandler = h

    for i := len(r.middlewareChain) - 1; i >= 0; i-- {
        mergedHandler = r.middlewareChain[i](mergedHandler)
    }

    r.mux[route] = mergedHandler
}
复制代码

web框架

在实际开发中,基本上都会用到一些完善的框架,比如gin, beego, echo等。 均提供了比较方便,简单的编程架子。并且拥有良好的程序结构。 后续会针对其中一些进行单独介绍。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK