Go HTTP

由于Web服务是HTTP协议的一个服务,Golang提供完善的net/http包,通过net/http包可以很方便地搭建一个可以运行的Web服务,同时net/http包能够简单地对Web的路由、静态资源、模板、Cookie等数据进行设置和操作。

net/http

Golang标准库内置net/http包涵盖了HTTP客户端和服务端的具体实现,使用net/http包可方便地编写HTTP客户端和服务端的程序。

Golang为了实现高并发和高性能,使用goroutine来处理连接的读写事件,以保证每个请求独立且互不阻塞以高效地响应网络事件。

c,err := srv.newConn(rw)
if err!=nil {
  continue
}
go c.serve()

Golang在等待客户端请求对连接处理时,客户端每次请求都会创建一个Conn连接对象,这个Conn连接对象中保存了本次请求的信息,然后再传递到对应的处理器,处理器可以方便地读取到相应地HTTP头信息,如此这般保证了每次请求的独立性。

服务端

基于HTTP构建的服务标准模型包括客户端和服务端,HTTP请求从客户端发出,服务端接收到请求后进行处理,然后将响应返回给客户端。HTTP服务器核心工作是如何接收来自客户端的请求,并向客户端返回响应。

HTTP服务器处理流程

当HTTP服务器接收到客户端请求时,首先会进入路由模块,路由又称为服务复用器(Multiplexer),路由的工作在于请求找到对应的处理器(Handler),处理器对接收到的请求进行对应处理后,构建响应并返回给客户端。

client -> Request -> Multiplexer(router)->handler ->Response -> client

运行流程

运行流程
  1. 创建Listen Socket监听指定端口,等待客户端请求到来。
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

首先初始化Server对象,然后调用其ListenAndServe()方法。

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

调用Server实例的ListenAndServe()方法会调用底层的net.Listen("tcp", addr)方法,即基于TCP协议创建Listen Socket,通过传入的主机地址和端口号,在指定端口上监听客户端请求。

  1. Listen Socket接受客户端请求并建立连接以获取Client Socket,通过Client Socket与客户端通信。

创建Listen Socket成功后会调用Server实例的Serve(net.Listener)方法,该方法用于接受并处理客户端请求。

Serve(net.Listener)方法内部会开启一个for死循环,在循环体内通过net.Listener(即Listen Socket)实例的Accept方法来接受客户端请求,接收到请求后根据请求会创建net.Conn连接实例(即Client Socket)。为了处理并发请求,会单独为每个连接实例开启一个goroutine去服务,请求的具体逻辑处理都会在serve()方法内完成。

  1. 处理客户端请求并返回响应

客户端请求的处理集中在conn连接实例的serve()方法内,serve()方法主要实现将HTTP请求分配给指定的处理器函数来进行处理。

首先从Client Socket中读取HTTP请求的协议头,判断请求方法若是POST则需读取客户端提交的数据,然后交给对应的Handler来处理请求,Handler处理完毕后准备后客户端所需数据,再通过Client Socket写给客户端。

连接实例通过readRequest()方法解析请求,然后再通过serverHandler{c.server}.ServeHTTP(w, w.req)中的ServeHTTP()方法获取请求对应的处理器。

创建服务

创建HTTP服务需经过两个阶段,首先需注册路由即提供URL模式和Handler处理函数的映射,然后是实例化Server对象并开启对客户端的监听。

例如:使用net/http包搭建Web服务

mux:= http.NewServeMux()
mux.Handle("/", http.RedirectHandler("http://www.baidu.com", 307))

server := &http.Server{Addr: ":3000", Handler: mux}
server.ListenAndServe()
  1. 注册路由,即注册一个到ServeMux的处理器函数。

注册路由即提供URL模式和Handler处理函数的映射

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(time.Now().Format(time.RFC3339)))
})
http.ListenAndServe(":3000", nil)

net/http包提供了注册路由的API,http.HandleFunc方法默认会采用DefaultServeMux作为服务复用器,DefaultServeMux实际是ServeMux的一个实例。

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

net/http包也提供了NewServeMux()方法来创建一个自定义的ServeMux实例,默认则创建一个DefaultServeMux

mux := http.NewServeMux()
mux.Handle("/", http.RedirectHandler("http://www.baidu.com", 307))

http.ListenAndServe(":3000", mux)
  1. 监听启动,设置监听的TCP地址并启动服务

监听启动实际上是实例化一个Server对象,并开启对客户端的监听。

func http.ListenAndServe(addr string, handler Handler) error

net/http提供的http.ListenAndServe(addr string, handler Handler)用于在指定的TCP网络地址进行监听,然后调用服务端处理程序来处理传入的请求。

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    n, err := rw.Write([]byte(rq.RemoteAddr))
    if err != nil || n <= 0 {
        panic(err)
    }
})

err := http.ListenAndServe(":3000", nil)
if err != nil {
    panic(err)
}

处理器默认为nil表示服务端会调用包变量http.DefaultServeMux作为默认处理器,服务端编写的业务逻辑处理程序http.Handler()http.HandleFunc()会默认注入http.DefaultServeMux中。

若不想采用默认的的http.DefaultServeMux可使用net/http包中提供的NewServeMux()创建自定义的ServeMux

func NewServeMux() *ServeMux

http.ListenAndSerTLS()方法用于处理HTTPS请求

func http.ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

注册路由

理解Golang中的HTTP服务最重要的是理解Multiplexer多路转接器和Handler处理器,Multiplexer基于ServeMux结构同时实现了Handler接口。

  • ServeMux本质上是一个HTTP请求路由器,又称为多路转接器(Multiplexor),它会将接收到的请求与一组预先定义的URL路径列表做对比,然后匹配路径时调用关联的处理器(Handler)。
  • Handler处理器负责输出HTTP响应的头和正文,任何满足http.Handler接口的对象都可以作为一个处理器。

多路转接器

HTTP请求的多路转接器(即路由)会负责将每个请求的URL与注册模式列表进行匹配,并调用和URL最佳匹配模式的处理器。多路转换器内部使用一个map映射来保存所有处理器。

type ServeMux struct {
    mu    sync.RWMutex//读写互斥锁,并发请求需锁机制
    m     map[string]muxEntry//路由规则,一个路由表达式对应一个复用器实体
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // 是否在任意规则中携带主机信息
}

虽然ServeMux也实现了ServerHTTP方法算得上是一个处理器,但ServeMuxServeHTTP方法并非用来处理请求和响应,而是用来查找注册路由对应的处理器。

DefaultServeMux是默认的ServeMux,随着net/http包初始化而被自动初始化。

快捷函数

net/http包提供了一组快捷函数http.Handlehttp.HandleFunc来配置DefaultServeMux,快捷函数会将处理器注册到DefaultServeMux。当ListenAndServe在没有提供其他处理器的情况下,即handlernil时内部会使用DefaultServeMux

默认多路转接器

package main

import (
    "net/http"
)

func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(rq.RemoteAddr))
}

func main() {
    http.Handle("/", http.HandlerFunc(defaultHandler))
    http.ListenAndServe(":3000", nil)
}

任何具有func(http.ResponseWriter, *http.Request)签名的函数都能转换成为一个http.HandlerFunc类型的对象,因为HandlerFunc对象内置了ServeHTTP()方法。

自定义快捷函数

实际上将函数转换为HandlerFunc后注册到ServeMux是很普遍的用法,当显式地使用ServeMux时,Golang提供了更为方便地ServeMux.HandleFunc函数。

package main

import (
    "net/http"
)

func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(rq.RemoteAddr))
}

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":3000", nil)
}

此时若需要从main()函数中传递参数到处理器,应该如何实现呢?一种优雅的方式是将处理器放入闭包中,将参数传入。

package main

import (
    "net/http"
    "time"
)

func defaultHandler(format string) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, rq *http.Request) {
        rw.Write([]byte(time.Now().Format(format)))
    })
}

func main() {
    handler := defaultHandler(time.RFC3339)
    http.Handle("/", handler)
    http.ListenAndServe(":3000", nil)
}

这里defaultHandler()函数除了将函数封装成为Handler外还会返回一个处理器。

也可在返回时使用一个到http.HandlerFunc类型的隐式转换

package main

import (
    "net/http"
    "time"
)

func defaultHandler(format string) http.HandlerFunc {
    return func(rw http.ResponseWriter, rq *http.Request) {
        rw.Write([]byte(time.Now().Format(format)))
    }
}

func main() {
    handler := defaultHandler(time.RFC3339)
    http.Handle("/", handler)
    http.ListenAndServe(":3000", nil)
}

处理器

Golang中没有继承、多态,可通过接口来实现。而接口则是定义声明的函数签名,任何结构体只要实现与接口函数签名相同的方法,即等同于实现了对应的接口。

Golang的net/http包实现的HTTP服务都是基于http.Handler接口进行处理的

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

任何结构体只要实现了ServeHTTP方法即可称之为处理器对象,http.ServeMux多路转接器会使用处理器对象并调用其ServeHTTP方法来处理请求并返回响应。

处理器函数的实现实际上调用默认ServeMuxHandleFunc()方法

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

若使用http.Handle()方法则第二个参数需实现Handler接口,实现Handler接口需实现其ServeHTTP()方法。换句话说,只要具有如下签名的ServeHTTP方法即可作为处理器。

ServeHTTP(http.ResponseWriter, *http.Request)
ServeHTTP
  • handler函数表示具有func(ResponseWriter, *Request)签名的函数
  • handler处理器函数表示经过http.HandlerFunc结构包装的handler函数,http.HandlerFunc结构实现了ServeHTTP接口,因此调用handler处理器的ServeHTTP()方法时,也就是在调用handler函数本身。
  • handler对象表示实现了http.Handler接口中ServerHTTP()方法的结构实例

自定义处理器

package main

import (
    "net/http"
    "time"
)

type TestHandler struct {
    format string
}

func (t *TestHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(time.Now().Format(t.format)))
}

func main() {
    mux := http.NewServeMux()

    th := &TestHandler{format: time.RFC3339}
    mux.Handle("/", th)

    http.ListenAndServe(":3000", mux)
}

Golang中net/http包中自带处理程序包括FileServerNotFoundHandlerRedirectHandler等。

适配器

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc适配器实现了ServeHTTP接口,因此它也是一个处理器。

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

HandlerFunc适配器的作用是将自定义的函数转换为Handler处理器类型,当调用HandlerFunc(f)后会强制将f函数类型转换为HandlerFunc类型,这样f函数就具有了ServeHTTP方法,同时也就转换成为了一个处理器。

请求

Web服务最基本的工作是接受请求返回响应,net/http包封装了http.Request结构体,用于获取一次HTTP请求的所有信息。

type Request struct {
    Method string//请求方法
    URL *url.URL//请求地址
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    Header Header//请求头
    Body io.ReadCloser//请求体
    GetBody func() (io.ReadCloser, error)//获取请求体
    ContentLength int64//内容长度
    TransferEncoding []string//传输编码
    Close bool//连接是否关闭
    Host string//服务器主机地址
    Form url.Values//GET表单
    PostForm url.Values//POST表单
    MultipartForm *multipart.Form//上传表单
    Trailer Header
    RemoteAddr string//远程客户端地址
    RequestURI string//请求URI
    TLS *tls.ConnectionState//HTTPS
    Cancel <-chan struct{}
    Response *Response//响应
    ctx context.Context//上下文对象
}
http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    //rw.Write([]byte(rq.RemoteAddr))
    fmt.Printf("protocol: %v\n", rq.Proto)
    fmt.Printf("method: %v\n", rq.Method)
    fmt.Printf("content length: %v\n", rq.ContentLength)
    fmt.Printf("url: %v\n", rq.URL)
    fmt.Printf("uri: %v\n", rq.RequestURI)
    fmt.Printf("remoteAddr: %v\n", rq.RemoteAddr)
    fmt.Printf("host: %v\n", rq.Host)
})
http.ListenAndServe(":3000", nil)

响应

net/http包中提供了访问Web服务的函数,比如http.Get()http.Post()http.Head()等,用于读取请求数据。服务端发送的响应报文会被保存在http.Response结构体中,响应包体会被存放在ResponseBody字段中。程序使用完响应必须关闭回复主体。

服务端

package main

import (
    "fmt"
    "net/http"
)

func server() {
    http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
        fmt.Printf("client %v %v %v\n", rq.RemoteAddr, rq.Method, rq.URL)
        rw.Write([]byte(rq.RemoteAddr))
    })
    http.ListenAndServe(":3000", nil)
}

func main() {
    server()
}

GET

客户端发送不带参数的GET请求

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func client() {
    url := "http://127.0.0.1:3000?id=1"
    fmt.Printf("request: GET %v\n", url)

    rp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    defer rp.Body.Close()

    fmt.Printf("response: status=%v, code=%v\n", rp.Status, rp.StatusCode)

    body, err := ioutil.ReadAll(rp.Body)
    if err != nil {
        panic(err)
    }
    fmt.Printf("response: body=%v\n", string(body))
}

func main() {
    client()
}
运行测试

客户端发送带参数的GET请求

//请求参数
params := url.Values{}
params.Set("id", "1")
params.Set("pid", "0")
//设置URL
rawURL := "http://127.0.0.1:3000"
reqURL, err := url.ParseRequestURI(rawURL)
if err != nil {
    panic(err)
}
//整合参数
reqURL.RawQuery = params.Encode()
fmt.Printf("request: GET %v\n", reqURL.String())
//发送请求
rp, err := http.Get(reqURL.String())
if err != nil {
    panic(err)
}
//延迟关闭响应包体
defer rp.Body.Close()
//解析响应
fmt.Printf("response: status=%v, code=%v\n", rp.Status, rp.StatusCode)
body, err := ioutil.ReadAll(rp.Body)//一次性读取响应包体内容
if err != nil {
    panic(err)
}
fmt.Printf("response: body=%v\n", string(body))
request: GET http://127.0.0.1:3000?id=1&pid=0
response: status=200 OK, code=200
response: body=127.0.0.1:3000: id=1, pid=0

服务端接收GET请求并解析参数

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    //延迟关闭请求包体
    defer rq.Body.Close()
    fmt.Printf("client %v %v %v\n", rq.RemoteAddr, rq.Method, rq.URL)
    //获取GET请求参数
    val := rq.URL.Query()
    id := val.Get("id")
    pid := val.Get("pid")
    //返回响应
    msg := fmt.Sprintf("%v: id=%v, pid=%v", rq.Host, id, pid)
    rw.Write([]byte(msg))
})
http.ListenAndServe(":3000", nil)

POST

application/x-www-form-urlencoded

客户端发送POST请求

url := "http://127.0.0.1:3000"
//内容类型
contentType := "application/x-www-form-urlencoded"
//对应内容类型的数据样式
id := 10
pid := 1
data := fmt.Sprintf("id=%v&pid=%v", id, pid)
body := strings.NewReader(data)
//发送请求
rp, err := http.Post(url, contentType, body)
if err != nil {
    panic(err)
}
defer rp.Body.Close() //延迟关闭响应包体
//解析响应包体
arr, err := ioutil.ReadAll(rp.Body) //一次性读取完毕
if err != nil {
    panic(err)
}
msg := string(arr)
fmt.Println(msg)

服务端解析POST参数

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    defer rq.Body.Close()
    //解析表单
    rq.ParseForm()
    //获取表单字段
    id := rq.PostForm.Get("id")
    pid := rq.PostForm.Get("pid")
    fmt.Printf("%v %v %v id=%v pid=%v\n", rq.RemoteAddr, rq.Method, rq.Proto, id, pid)
    //返回响应
    msg := fmt.Sprintf("%v %v %v", rq.Host, rq.Method, rq.Proto)
    rw.Write([]byte(msg))
})

application/json

客户端发送POST JSON数据

url := "http://127.0.0.1:3000"
//内容类型
contentType := "application/json"
//对应内容类型的数据样式
id := 10
pid := 1
data := fmt.Sprintf(`{"id":%v, "pid":%v}`, id, pid)
body := strings.NewReader(data)
//发送请求
rp, err := http.Post(url, contentType, body)
if err != nil {
    panic(err)
}
defer rp.Body.Close() //延迟关闭响应包体
//解析响应包体
arr, err := ioutil.ReadAll(rp.Body) //一次性读取完毕
if err != nil {
    panic(err)
}
msg := string(arr)
fmt.Println(msg)

服务端解析POST JSON

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    defer rq.Body.Close()
    //读取数据
    arr, err := ioutil.ReadAll(rq.Body)
    if err != nil {
        panic(err)
    }
    str := string(arr)
    //获取表单字段
    fmt.Printf("%v %v %v %v\n", rq.RemoteAddr, rq.Method, rq.Proto, str)
    //返回响应
    msg := fmt.Sprintf(`{"code":%v, "message":%v}`, 1, "success")
    rw.Write([]byte(msg))
})
POST JSON

推荐阅读更多精彩内容