zoukankan      html  css  js  c++  java
  • 【go语言学习】网络编程之HTTP

    一、go中HTTP服务处理流程

    超文本传输协议(HTTP,Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。

    HTTP 协议从诞生到现在,发展从1.0,1.1到2.0也不断在进步。除去细节,理解 HTTP 构建的网络应用只要关注两个端——客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 client 的 request,并向client返回response。接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。

    Go中既可以使用内置的 multiplexer - DefaultServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。

    二、构建一个简单的http服务

    代码示例

    package main
    
    import "net/http"
    
    func main() {
    	// 1.设置路由
    	// 访问"/",调用indexHandleFunc函数处理
    	http.HandleFunc("/", indexHandleFunc)
    	// 访问"/home",调用homeHandleFunc函数处理
    	http.HandleFunc("/home", homeHandleFunc)
    	// 2.开启监听
    	http.ListenAndServe(":8080", nil)
    }
    
    func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("index"))
    }
    func homeHandleFunc(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("home"))
    }
    

    运行程序

    访问http://localhost:8080/,页面上显示index
    访问http://localhost:8080/home/,页面上显示home

    三、深入net/http包理解go语言http

    1、http.Server

    HTTP 服务器在 Go 语言中是由 http.Server 结构体对象实现的。参考 http.ListenAndServe() 的实现:

    // src/net/http/server.go
    
    // ListenAndServe always returns a non-nil error.
    func ListenAndServe(addr string, handler Handler) error {
      server := &Server{Addr: addr, Handler: handler}
      return server.ListenAndServe()
    }
    

    可见过程是先实例化 Server 对象,再完成 ListenAndServe 。其中 Serve 对象就是表示 HTTP 服务器的对象。其结构如下 :

    // src/net/http/server.go
    
    type Server struct {
    	Addr         string        // TCP 监听地址, 留空为:":http"
    	Handler      Handler       // 调用的 handler(路由处理器), 设为 nil 表示 http.DefaultServeMux
    	ReadTimeout  time.Duration // 请求超时时长
    	WriteTimeout time.Duration // 响应超时时长
    	...
    }
    

    实例化了 Server 对象后,调用其 func (srv *Server) ListenAndServe() error 方法。该方法会监听 srv.Addr 指定的 TCP 地址,并通过 func (srv *Server) Serve(l net.Listener) error 方法接收浏览器端连接请求。Serve 方法会接收监听器 l 收到的每一个连接,并为每一个连接创建一个新的服务协程goroutine。

    该 go 协程会读取请求,然后调用 srv.Handler 处理并响应。srv.Handler 通常会是 nil,这意味着需要调用 http.DefaultServeMux 来处理请求,这个 DefaultServeMux 是默认的路由,我们使用 http.HandleFunc 就是在 http.DefaultServeMux 上注册方法。

    看一下详细过程(不完全摘录go源码):

    step1:

    初始化一个Server结构体实例后, 执行Server.ListenAndServer函数(其中主要调用net.Listen 和 Server.Serve函数)

    func (srv *Server) ListenAndServe() error {
    	// 调用net.Listen方法启用监听
    	ln, err := net.Listen("tcp", addr)
    	// 调用Server对象的Serve方法,将监听对象作为参数传入
    	return srv.Serve(ln)
    }
    

    step2:

    接着进入Server.Serve部分的代码,看看详细的处理过程,可以看到主要是在主goroutine中用for循环阻塞,不断通过Accept函数读取连接请求,然后调用Server.newConn()函数,创建一个连接实例c,然后每个连接实例启动一个新的goroutine去执行处理,从这里也能看出, 如果并发请求很高的时候,会创建出海量的goroutine来并发处理请求。

    这一步,调用newConn函数,会创建一个conn结构体的连接实例,其中的server成员变量是Server实例,即每个connection实例都保留了指向server的信息。

    func (srv *Server) Serve(l net.Listener) error {
    	for {
    		// 循环读取连接请求
    		rw, err := l.Accept()
    		// 创建一个新的连接实例 c := &conn{server: srv, rwc: rw}
    		c := srv.newConn(rw)
    		// 启动一个新的goroutine去执行处理
    		go c.serve(connCtx)
    	}
    }
    

    step3

    接着进入每个connection处理的goroutine,看具体做了什么工作:先调用connectionreadRequest方法构造一个response对象, 然后执行serverHandler{c.server}.ServeHTTP(w, w.req)进行实际的请求处理。

    func (c *conn) serve(ctx context.Context) {
        for{
    		...
    		// 调用connection的readRequest函数构造一个response对象
            w, err := c.readRequest(ctx)
            ...
            req := w.req
    		...
    		// serverHandler{c.server}.ServeHTTP(w, w.req)进行实际的请求处理
            serverHandler{c.server}.ServeHTTP(w, w.req)
        }
    }
    

    step4

    serverHandler是一个结构体,有一个成员属性srv *Server,结合step 3可以看出,serverHandler{c.server}.ServeHTTP(w, w.req)就是实例化了一个serverHandler结构体,然后执行其成员方法 ServeHTTP,在ServeHTTP成员方法的定义中,可以看到,主要是调用了初始化server时的Handler成员方法,执行handler.ServeHTTP(rw, req)进行调用。

    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)
    }
    
    2、http.Handler
    // Handler 接口
    type Handler interface {
    	ServeHTTP(ResponseWriter, *Request)
    

    任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为Handler对象。

    代码示例:

    package main
    
    import (
    	"net/http"
    )
    
    type a struct{}
    
    func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("hello world"))
    }
    
    func main() {
    	// 从go的源码中可以发现函数的第二个参数是一个Handler对象,a实现了Handler的方法
    	// a就可以看做是一个Handler对象传入。
    	http.ListenAndServe(":8080", &a{})
    }
    

    运行程序

    访问http://localhost:8080/,页面上显示hello world

    3、http.HandleFunc

    http.HandleFunc注册路由,从源码中可以看到,http.HandleFunc的功能是向http包的DefaultServeMux加入新的处理路由。

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

    DefaultServeMux 是一个 ServeMux 结构体的实例。

    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    
    var defaultServeMux ServeMux
    
    5、http.ServeMux

    ServeMux是一个结构体,其中有个m属性是一个map类型,存放了不同 URL pattern 的处理Handler。同时ServeMux 也实现了 ServeHTTP 函数,是 http.Handler 接口的实现。

    type ServeMux struct {
    	mu    sync.RWMutex
    	m     map[string]muxEntry
    	hosts bool 
    }
    
    type muxEntry struct {
    	h       Handler
    	pattern string
    }
    
    // ServeMux实现了Handler接口
    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)
    }
    

    DefaultServeMux 调用 ServeMuxHandleFunc 方法

    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    	if handler == nil {
    		panic("http: nil handler")
    	}
    	mux.Handle(pattern, HandlerFunc(handler))
    }
    

    接着调用 ServeMuxHandle 方法向 ServeMux 结构体的 m 字段写入信息 map[pattern] = muxEntry{h: handler, pattern: pattern}

    func (mux *ServeMux) Handle(pattern string, handler Handler) {}
    

    四、go语言http的其他实现方式

    1、自定义server

    创建一个Server对象,可以设置server的其他参数。

    package main
    
    import (
    	"net/http"
    	"time"
    )
    
    type a struct{}
    
    func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("hello world"))
    }
    
    func main() {
    	// 创建一个新的Server对象
    	s := http.Server{
    		// 监听端口
    		Addr: "localhost:8080",
    		// 处理路由
    		Handler: &a{},
    		// 设置超时
    		ReadTimeout:  5 * time.Second,
    		WriteTimeout: 10 * time.Second,
    	}
    	s.ListenAndServe()
    }
    
    2、使用自定义的serveMux
    package main
    
    import (
    	"net/http"
    )
    
    type a struct{}
    
    func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("hello world"))
    }
    
    func index(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("index"))
    }
    func page(w http.ResponseWriter, r *http.Request) {
    	w.Write([]byte("page"))
    }
    
    func main() {
    	mux := http.NewServeMux()
    	// 通过Handle将a这个实现Handler接口的结构体,注册到map表中
    	mux.Handle("/", &a{})
    	//通过HandleFunc方法去向mux中handler中注册处理函数
    	//其底层仍然是通过mux.Handle方法实现的
    	mux.HandleFunc("/index", index)
    	// 直接调用Handle方法去注册处理函数,
    	// 这里的http.HandleFunc 是一个函数类型,这里将page转换为一个Handler接口的实现
    	mux.Handle("/page", http.HandlerFunc(page))
    	mux.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
    		w.Write([]byte("home"))
    	})
    
    	http.ListenAndServe("localhost:8080", mux)
    }
    

    五、http的执行过程

    1、传入自己实现Handler接口的对象

    http.ListenAndServe("localhost:8080", &a{}) 第二个参数传入的是自己实现Handler接口的对象,http的server会调用这个对象的 ServeHTTP(w, r) 成员方法,实现 requestresponse

    示例代码:

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    type a struct{}
    
    func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	fmt.Println(r.URL.Path)
    	switch r.URL.Path {
    	case "/":
    		w.Write([]byte("<h1>hello world</h1>"))
    	case "/index":
    		w.Write([]byte("<h1>index</h1>"))
    	case "/home":
    		w.Write([]byte(`<div style="color:red;font-size: 20px;">This is home page</div>`))
    	default:
    		w.Write([]byte(`<h1 style="color:red">404 NOT FOUND</h1>`))
    	}
    }
    
    func main() {
    	http.ListenAndServe("localhost:8080", &a{})
    }
    
    2、传入自定义的ServeMux或默认的DefaultServeMux

    http.ListenAndServe("localhost:8080", mux) 第二个参数传入的是自定义的ServeMux或默认的DefaultServeMux(就是传入 nil ),http的server会调用ServeMux的 ServeHTTP(w, r) 成员方法,实现 requestresponse ,详细步骤如下(不完全摘录go源码):

    step1:
    调用 ServeHTTP(w, r){} 实现 requestresponse ,先调用 mux.Handler(r) 方法返回一个Handler对象,调用其 ServeHTTP(w, r) 方法

    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    	h, _ := mux.Handler(r)
    	h.ServeHTTP(w, r)
    }
    

    step2:在调用 mux.Handler(r *Request) 方法时,是接着调用
    mux.handler(host, r.URL.Path) 方法

    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    	return mux.handler(host, r.URL.Path)
    }
    

    step3:在调用 mux.handler(host, path string) 方法,是接着调用mux.match(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
    }
    

    step4: 在调用 mux.match(path) 方法时,返回 mux.m[path].h 一个Handler对象,其实就是注册的路由器处理函数,并且和 r.URL.Path 相匹配,返回的 mux.m[path].pattern 就是路由地址 r.URL.Path

    func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    	v, ok := mux.m[path]
    	if ok {
    		return v.h, v.pattern
    	}
    	return nil, ""
    }
    

    step5:回到step1调用 mux.m[path].hServeHTTP(w, r) 方法。

    参考文章
    https://my.oschina.net/u/943306/blog/151293
    https://studygolang.com/articles/16179

  • 相关阅读:
    Remove Duplicates from Sorted Array
    Longest Valid Parentheses
    Valid Parentheses
    Java [Leetcode 112]Path Sum
    Java [Leetcode 119]Pascal's Triangle II
    Java [Leetcode 102]Binary Tree Level Order Traversal
    Java [Leetcode 172]Factorial Trailing Zeroes
    奇异值分解(转载)
    Java [Leetcode 118]Pascal's Triangle
    Java [Leetcode 66]Plus One
  • 原文地址:https://www.cnblogs.com/everydawn/p/14049411.html
Copyright © 2011-2022 走看看