由于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服务器接收到客户端请求时,首先会进入路由模块,路由又称为服务复用器(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)
-
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))
})
有疑问加站长微信联系(非本文作者)