作者:JunChow520
出处:https://www.jianshu.com/p/844ba023eac7
由于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
运行流程
运行流程
- 创建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,通过传入的主机地址和端口号,在指定端口上监听客户端请求。
- 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() 方法内完成。
- 处理客户端请求并返回响应
客户端请求的处理集中在 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()
- 注册路由,即注册一个到 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)
- 监听启动,设置监听的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 方法算得上是一个处理器,但 ServeMux 的 ServeHTTP 方法并非用来处理请求和响应,而是用来查找注册路由对应的处理器。
DefaultServeMux 是默认的 ServeMux ,随着 net/http 包初始化而被自动初始化。
快捷函数
net/http 包提供了一组快捷函数 http.Handle 和 http.HandleFunc 来配置 DefaultServeMux ,快捷函数会将处理器注册到 DefaultServeMux 。当 ListenAndServe 在没有提供其他处理器的情况下,即 handler 为 nil 时内部会使用 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 方法来处理请求并返回响应。
处理器函数的实现实际上调用默认 ServeMux 的 HandleFunc() 方法
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 包中自带处理程序包括 FileServer 、 NotFoundHandler 、 RedirectHandler 等。
适配器
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 结构体中,响应包体会被存放在 Response 的 Body 字段中。程序使用完响应必须关闭回复主体。
服务端
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
作者:JunChow520
出处:https://www.jianshu.com/p/844ba023eac7