golang基础-http server


本文摘自网络,作者,侵删。

golang实现一个简单的http server

如果搜索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等。 均提供了比较方便,简单的编程架子。并且拥有良好的程序结构。 后续会针对其中一些进行单独介绍。




本文来自:51CTO博客

感谢作者:mob604756e80bb7

查看原文:golang基础-http server

相关阅读 >>

Golang 泛型

Golang判断字符是否存在字符串中

Golang怎么判断slice是否为空

Golang可以写web吗?

Golang 通用链接池

Go compiler intrinsics(文末赠书)

Golang使用protobuf中的oneof

Go:测量函数执行时间的方法

Go是动态语言还是静态语言

Go语言函数值传递值

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...