Http 服务器

用 Go实现一个 http server 非常容易,Go 语言标准库 net/http 自带了一系列结构和方法来帮助开发者简化 HTTP 服务开发的相关流程。因此,我们不需要依赖任何第三方组件就能构建并启动一个高并发的 HTTP 服务器。

1. 简单的 HTTP 服务器

1.1 http 服务端

// http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error

用于启动HTTP服务器,监听addr,并使用handler来处理请求。返回启动错误。其中:

addr,TCP address,形式为 IP:port,IP省略表示监听全部网络接口 handler,经常的被设置为 nil,表示使用DefaultServeMux(默认服务复用器)来处理请求。 DefaultServe Mux要使用以下两个函数来添加请求处理器

func Handle(pattern string, handler Handler) func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

func Http() {

// 一: 设置不同路由对应的不同处理器

// 第一种方式: HandleFunc

http.HandleFunc("/ping", HandlerPing)

// 第二种方式: Handle

// 需要结构体,以及实现方法ServerHTTP

Info := InfoHandle{Info: "Hello , Sakura"}

http.Handle("/info", Info)

// 二:启动监听

addr := ":8089"

log.Println("HTTP 服务器正在监听: ", addr)

if err := http.ListenAndServe(addr, nil); err != nil {

log.Fatalln(err)

}

}

func HandlerPing(resp http.ResponseWriter, req *http.Request) {

resp.Write([]byte("Pong"))

}

// Handle第二个参数需要结构体,并且实现ServerHTTP

type InfoHandle struct {

Info string

}

func (info InfoHandle) ServeHTTP(resp http.ResponseWriter, req *http.Request) {

resp.Write([]byte(info.Info))

}

其中:Handler 接口的定义为:

type Handler interface {

ServeHTTP(ResponseWriter, *Request)

}

//通过创建结构体 InfoHandler 实现 Handler接口,可以作为 http.Handle()的第二个参数来使用

1.2 http 客户端

步骤:

创建客户端发起请求处理服务器响应关闭连接

func Client() {

// 1.创建客户端

client := &http.Client{}

// 2.创建请求

req, err := http.NewRequest("GET", "http://localhost:8080/sakura", nil)

if err != nil {

panic(err)

return

}

// 3.发送请求

resp, err := client.Do(req)

if err != nil {

panic(err)

}

defer resp.Body.Close()

// 4.处理服务器响应resp

all, err := io.ReadAll(resp.Body)

fmt.Println(string(all))

}

因为 Get 和 Post 比较常用,所以 net/http 包里本身封装了 Get 和 Post 请求,不过其他的请求比如说 Delete,Put 就没有了

func main() {

// 1.Get 请求

http.Get("http://localhost:8080/sakura")

buffer := bytes.NewBuffer([]byte{})

// 2.Post 请求

resp, err := http.Post("http://localhost:8080/service","application/json",buffer)

if err != nil {

return

}

fmt.Println(resp)

}

2. 复杂的 HTTP 服务器

定制性的 HTTP 服务器,通过 Server 类型进行设置。其定义如下:

// net/http

type Server struct {

// TCP Address

Addr string

Handler Handler // handler to invoke, http.DefaultServeMux if nil

// LSConfig optionally provides a TLS configuration for use

// by ServeTLS and ListenAndServeTLS

TLSConfig *tls.Config // 配置 HTTPS 相关 , 证书...

// 读请求超时时间

ReadTimeout time.Duration

// 读请求头超时时间

ReadHeaderTimeout time.Duration

// 写响应超时时间

WriteTimeout time.Duration

// 空闲超时时间

IdleTimeout time.Duration

// Header最大长度

MaxHeaderBytes int

// 其他字段略

}

该类型的 func (srv *Server) ListenAndServe() error 函数用于监听和服务

func CustomerHTTPServer() {

// 一: 定义Server类型数据,指定配置

addr := ":8089"

Handle := CustomHandle{Info: "Custom Server "}

server := http.Server{

Addr: addr,

Handler: Handle,

ReadTimeout: 3 * time.Second,

ReadHeaderTimeout: 3 * time.Second,

WriteTimeout: 3 * time.Second,

MaxHeaderBytes: 1 << 10, // 2 *10

}

// 二:启动网络监听

fmt.Println("启动自定义HTTP服务器,监听端口为: ", addr)

err := server.ListenAndServe()

if err != nil {

log.Fatalln(err)

}

}

type CustomHandle struct {

Info string

}

func (info CustomHandle) ServeHTTP(writer http.ResponseWriter, request *http.Request) {

fmt.Fprintf(writer, info.Info)

}

3. 分析源码

3.1 服务端源码

func Server() {

// 注册路由

// 1.pattern: 路由规则

// 2.handler: 处理器函数(回调函数)

http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) {

writer.Write([]byte("hello,sakura"))

})

// 启动服务

// 1.addr: 监听地址 host:port

// 2.handler: 处理HTTP请求的处理器,默认为http.DefaultServeMux=>ServeMux

err := http.ListenAndServe(":8080", nil)

panic(err)

}

路由:底层使用切片 []slice 存储,按照从长到短存储,

匹配优先全字匹配,eg,/sakura 不会匹配 /sakura/book没有匹配到的路由使用根目录进行兼容,eg,/sakura/info 会匹配到 /

http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) {

writer.Write([]byte("hello,sakura"))

})

http.HandleFunc("/sakura/book", func(writer http.ResponseWriter, request *http.Request) {

writer.Write([]byte("hello,sakura"))

})

http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {

writer.Write([]byte("hello,sakura"))

})

创建路由

首先进入http.HandleFunc() ,一直追入源码可以发现,HandlerFunc 类型实现了 Handler 接口

// 这个类型实现了Handler接口

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).

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

f(w, r)

}

// Handler 接口

type Handler interface {

ServeHTTP(ResponseWriter, *Request)

}

也就是说, http.HandleFunc() 中的函数最终被封装为了一个 Handler 接口的实现函数

http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) {

writer.Write([]byte("hello,sakura"))

})

而http.ListenAndServe(“:8080”, nil) 中的 Hander 类型,HandlerFunc 实现的接口类型

func ListenAndServe(addr string, handler Handler) error {

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

return server.ListenAndServe()

}

// ↓

// Handler 接口

type Handler interface {

ServeHTTP(ResponseWriter, *Request)

}

总结: ListenAndServe() 的第二个参数,可以自行定义类型实现接口,默认使用 http.DefaultServeMux HandleFunc() 中的第二个参数,可以直接指定函数,然后会将该函数封装为一个 Handler 接口的实现函数

HanleFunc() 的底层处理,需要自己实现的 Handler 和 直接传入函数封装为 HandleFunc 的 Handler

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

DefaultServeMux.HandleFunc(pattern, handler)

}

// ↓

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {

if handler == nil {

panic("http: nil handler")

}

mux.Handle(pattern, HandlerFunc(handler))

}

// ↓

// DefaultServeMux 在源码上面定义了,就是ServeMux

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

// 多路复用器

type ServeMux struct { // HTTP 请求多路复用器:路由器

mu sync.RWMutex // 读写锁:可共享读,不可共享写;写时不读,读时不写(排它锁)

m map[string]muxEntry // key: pattern; value: {Handler, pattern}

es []muxEntry // entries切片,按URL从长到短排序,方便匹配到最佳路由

hosts bool // pattern中是否有主机名

}

这个 handler 是以参数的形式传过去的

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

DefaultServeMux.HandleFunc(pattern, handler)

}

而 DefaultServeMux.HandleFunc() 中的 handle,由于 HanlerFunc 是一个定义的函数类型

HanlerFunc(handler) : 相当于将 handler 封装为 HanlerFunc,使得自己传入的函数,也实现了 Handler 这个接口

// HandleFunc registers the handler function for the given pattern.

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {

if handler == nil {

panic("http: nil handler")

}

mux.Handle(pattern, HandlerFunc(handler))

}

mux.Handle 路由绑定

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

mux.mu.Lock() // 加锁

defer mux.mu.Unlock()

// pattern 的路径

if pattern == "" {

panic("http: invalid pattern")

}

// handler 为nil

if handler == nil {

panic("http: nil handler")

}

// 路由重复

if _, exist := mux.m[pattern]; exist {

panic("http: multiple registrations for " + pattern)

}

if mux.m == nil {

mux.m = make(map[string]muxEntry) // 初始化map

}

e := muxEntry{h: handler, pattern: pattern} // 完成路由与处理器的映射

mux.m[pattern] = e

if pattern[len(pattern)-1] == '/' { // 最后一个字符如果是'/'的话,

mux.es = appendSorted(mux.es, e) //将新路由放到正确的位置,从长到短

}

if pattern[0] != '/' { //如果路由不是以/开头,例如127.0.0.1:8080/sakura

mux.hosts = true //将 mux.hosts置为true

}

}

创建服务器

// ListenAndServe always returns a non-nil error.

func ListenAndServe(addr string, handler Handler) error {

// 在这里创建服务器

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

return server.ListenAndServe()

}

通过源码可以看到 server 的全部属性

关系 :

一个 server 可以接受多个请求每个客户端又可以和 server 建立多个连接,并且每个连接又可以发送很多次请求

所以关系都是一对多的

Addr 服务监听的地址 “IP + Port” ,默认是 80 Handler 路由处理器,没有指定的话,默认走 http.DefaultServeMux TLSconfig TLS的相关配置,如果要进行 https 的请求的时候使用 ReadTimeoout 读超时,客户端向服务端的管道,请求 WriteTimeout 写超时,服务端行客户单的管道,响应

监听端口

func ListenAndServe(addr string, handler Handler) error {

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

// 服务器创建完成之后,启动监听

return server.ListenAndServe()

}

// 启动 TCP 监听

func (srv *Server) ListenAndServe() error {

if srv.shuttingDown() {

return ErrServerClosed

}

addr := srv.Addr

if addr == "" {

addr = ":http"

}

// 创建TCp监听器

ln, err := net.Listen("tcp", addr)

if err != nil {

return err

}

// 提供服务

return srv.Serve(ln)

}

核心方法 Serve()

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

if fn := testHookServerServe; fn != nil {

fn(srv, l) // call hook with unwrapped listener

}

origListener := l

// 对传递过来的监听器进行封装

l = &onceCloseListener{Listener: l}

defer l.Close()

// 都是细节判断....

ctx := context.WithValue(baseCtx, ServerContextKey, srv)

// 核心

// 一个监听可以创建多个连接

for {

// 等待客户端建立连接,如果没有连接,会被阻塞

rw, err := l.Accept()

// 监听的过程中出现错误之后,进行处理,尝试重新连接..

if err != nil {

// ..............

}

connCtx := ctx

if cc := srv.ConnContext; cc != nil {

connCtx = cc(connCtx, rw)

if connCtx == nil {

panic("ConnContext returned nil")

}

}

tempDelay = 0

// 如果有客户端发送了请求,将Accetp()中得到的连接进行一个封装

c := srv.newConn(rw)

c.setState(c.rwc, StateNew, runHooks) // before Serve can return

// 连接成功,新建协程提供服务

go c.serve(connCtx)

}

}

启动协程提供服务的时机是,TCP 连接之后,TLS 连接(握手)之前。在协程里面进行 TLS 握手

最核心方法 serve()

方法前半段: 处理关闭连接和 TLS 握手

// Serve a new connection.

func (c *conn) serve(ctx context.Context) {

if ra := c.rwc.RemoteAddr(); ra != nil {

c.remoteAddr = ra.String()

}

ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())

var inFlightResponse *response

defer func() { // 关闭连接之后的首尾工作,异常处理,日志,连接状态,钩子函数

// ...

}()

if tlsConn, ok := c.rwc.(*tls.Conn); ok {

// tls 握手(非常复杂)

}

//..

}

方法后半段:真正处理连接的请求

一个监听器可以创建多个连接,一个连接可以创建多个请求

// HTTP/1.x from here on.

ctx, cancelCtx := context.WithCancel(ctx)

c.cancelCtx = cancelCtx

defer cancelCtx()

c.r = &connReader{conn: c}

c.bufr = newBufioReader(c.r)

c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

// 一个连接对应多个请求,所以这里使用for

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, runHooks)

}

// 请求错误处理

if err != nil {

const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

switch {

// 431 客户端文件太大

case err == errTooLarge:

const publicErr = "431 Request Header Fields Too Large"

fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)

c.closeWriteAndWait()

return

// 无法识别客户端的编码

case isUnsupportedTEError(err):

code := StatusNotImplemented

// We purposefully aren't echoing back the transfer-encoding's value,

// so as to mitigate the risk of cross side scripting by an attacker.

fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)

return

case isCommonNetReadError(err):

return // don't reply

// 默认错误,会根据状态码响应对应的错误信息

// 例如404 not found | 400 bad request

default:

if v, ok := err.(statusError); ok {

//...

}

}

// Expect 100 Continue support

// ...

c.curReq.Store(w) // 将请求与响应实例 w绑定,回写客户端

// ....

inFlightResponse = w

serverHandler{c.server}.ServeHTTP(w, w.req) // 调用handler处理请求

inFlightResponse = nil

w.cancelCtx()

if c.hijacked() {

return

}

w.finishRequest() // 请求完成,刷新response缓存

c.rwc.SetWriteDeadline(time.Time{})

if !w.shouldReuseConnection() { //尝试复用TCP连接

if w.requestBodyLimitHit || w.closedRequestBodyEarly() {

c.closeWriteAndWait()

}

return

}

c.setState(c.rwc, StateIdle, runHooks) // 将连接置为空闲状态

c.curReq.Store(nil) // 响应实例置为空,便于下一次返回响应

// http/1.1 持久连接,客户端可以继续发送下个报文

if !w.conn.server.doKeepAlives() {

// We're in shutdown mode. We might've replied

// to the user without "Connection: close" and

// they might think they can send another

// request, but such is life with HTTP/1.1.

return

}

if d := c.server.idleTimeout(); d != 0 {

c.rwc.SetReadDeadline(time.Now().Add(d))

} else {

c.rwc.SetReadDeadline(time.Time{})

}

// 读取请求缓存,看看那是否有数据

if _, err := c.bufr.Peek(4); err != nil {

return

}

// 设置截止时间,不截止,进入下次循环

c.rwc.SetReadDeadline(time.Time{})

}

}

3.2 客户端源码

func Client() {

// 1.创建客户端

client := &http.Client{}

// 2.创建请求

req, err := http.NewRequest("GET", "http://localhost:8080/sakura", nil)

if err != nil {

panic(err)

return

}

// 3.发送请求

resp, err := client.Do(req)

if err != nil {

panic(err)

}

defer resp.Body.Close()

// 4.处理服务器响应resp

all, err := io.ReadAll(resp.Body)

fmt.Println(string(all))

}

首先查看 client 结构体

type Client struct {

// 客户端发送请求,服务端响应,称为一个往返

// 执行http请求的机制

// 请求到响应一整个流程的主函数 -> RoundTrip

// 如果为nil,则使用 DefaultTransport

Transport RoundTripper

// 检查是否需要重定向

CheckRedirect func(req *Request, via []*Request) error

// Cookie包

Jar CookieJar

// 超时时间

Timeout time.Duration

}

type RoundTripper interface {

// 执行请求和响应的这个流程

RoundTrip(*Request) (*Response, error)

}

Transport

DefaluTransport

client 相当于浏览器, Transport 就相当于连接池

// 默认Transport

var DefaultTransport RoundTripper = &Transport{

// 从上下文中找到代理环境

Proxy: ProxyFromEnvironment,

// 拨号

DialContext: defaultTransportDialContext(&net.Dialer{

Timeout: 30 * time.Second, // 超时时间

KeepAlive: 30 * time.Second, // 保活时间

}),

ForceAttemptHTTP2: true, // 尝试连接http2

MaxIdleConns: 100, // 最大空闲连接

IdleConnTimeout: 90 * time.Second, // 空闲超时时间

TLSHandshakeTimeout: 10 * time.Second, // TLS 超时时间

ExpectContinueTimeout: 1 * time.Second, // 100状态码超时时间

}

// 真正的Transport

type Transport struct {

idleMu sync.Mutex

closeIdle bool // 用户请求关闭所有的闲置连接

idleConn map[connectMethodKey][]*persistConn // 每个host对应的闲置连接列表

idleConnWait map[connectMethodKey]wantConnQueue // 每个host对应的等待闲置连接列表,在其它request将连接放回连接池前先看一下这个队列是否为空,不为空则直接将连接交由其中一个等待对象

idleLRU connLRU // 用来清理过期的连接

reqMu sync.Mutex

reqCanceler map[*Request]func(error)

connsPerHostMu sync.Mutex

connsPerHost map[connectMethodKey]int // 每个host对应的等待连接个数

在当前主机/Client/Transport/连接池实体 中,等待获取连接的队列集合

map:[key:请求方法, value:使用此方法发送请求的等待队列]

connsPerHostWait map[connectMethodKey]wantConnQueue // 每个host对应的等待连接列表

// 用于指定创建未加密的TCP连接的dial功能,如果该函数为空,则使用net包下的dial函数

DialContext func(ctx context.Context, network, addr string) (net.Conn, error)

Dial func(network, addr string) (net.Conn, error)

// 以下两个函数处理https的请求

DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)

DialTLS func(network, addr string) (net.Conn, error)

DisableKeepAlives bool // 是否复用连接

DisableCompression bool // 是否压缩

MaxIdleConns int // 总的最大闲置连接的个数

MaxIdleConnsPerHost int // 每个host最大闲置连接的个数

MaxConnsPerHost int // 每个host的最大连接个数,如果已经达到该数字,dial连接会被block住

IdleConnTimeout time.Duration // 闲置连接的最大等待时间,一旦超过该时间,连接会被关闭

ResponseHeaderTimeout time.Duration // 读超时,从写完请求到接受到返回头的总时间

ExpectContinueTimeout time.Duration // Expect:100-continue两个请求间的超时时间

MaxResponseHeaderBytes int64 // 返回中header的限制

WriteBufferSize int // write buffer的使用量

ReadBufferSize int // read buffer的使用量

}

自己配置 Transport 中的字段,并且完成 Client 的初始化

func main() {

// 创建连接池

transport := &http.Transport{

DialContext: (&net.Dialer{

Timeout: 30 * time.Second, //连接超时

KeepAlive: 30 * time.Second, //探活时间

}).DialContext,

ForceAttemptHTTP2: true,

MaxIdleConns: 100, //最大空闲连接

IdleConnTimeout: 90 * time.Second, //空闲超时时间

TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间

ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间

}

// 创建客户端

client := &http.Client{

Timeout: time.Second * 30,

Transport: transport,

}

// 请求数据

resp, err := client.Get("http://127.0.0.1:9527/hello")

defer resp.Body.Close()

if err != nil {

panic(err)

}

// 读取内容

bds, err := ioutil.ReadAll(resp.Body)

if err != nil {

panic(err)

}

fmt.Println(string(bds))

}

文章来源

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。