【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

原文地址:https://www.cnblogs.com/everydawn/p/14049411.html