package main
import (
"fmt"
"net/http"
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
func main() {
http.HandleFunc("/", myfunc)
http.ListenAndServe(":8080", nil)
编译运行程序,然后打开浏览器访问 http://localhost:8080/ , 我们可以看到网页输出"hi" ! 就这么简单,我们实现了一个HTTPserver!
下面我们通过分析net/http的源代码,来深入理解这个包的实现原理。在net/http源代码中,我们可以深深体会到Go语言的结构体(以及自定义类型)、接口、方法简单组合的设计哲学。这个包最主要的文件有4个,分别是:
client.go
server.go
request.go
response.go
这四个文件也分别代表了HTTP中最重要的4个部分,http Request 请求、 http Response 响应、http Client客户端和http Server 服务端,所以我们先从这四个方面来了解net/http包:
1.1. 36.1 Request
http Request请求是由客户端发出的消息, 用来使服务器执行动作.发出的消息包括起始行, Headers, Body。
在net/http包中,request.go文件定义了结构:
type Request struct
http Request请求是http Client客户端向http Server服务端发出的消息,或者是http Server服务端收到的一个请求,但是http Server服务端和http Client客户端使用Request时语义区别很大。我们一般使用 http.NewRequest来构造一个http Request请求,可能包括http Headers信息,cookies信息等,然后发给服务端:
func NewRequest(method, urlStr string, body io.Reader) (*Request, error)
func ReadRequest(b *bufio.Reader) (req *Request, err error)
func (r *Request) AddCookie(c *Cookie)
func (r *Request) Cookie(name string) (*Cookie, error)
func (r *Request) Cookies() []*Cookie
func (r *Request) SetBasicAuth(username, password string)
func (r *Request) UserAgent() string
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
func (r *Request) FormValue(key string) string
func (r *Request) MultipartReader() (*multipart.Reader, error)
func (r *Request) ParseForm() error
func (r *Request) ParseMultipartForm(maxMemory int64) error
func (r *Request) PostFormValue(key string) string
func (r *Request) ProtoAtLeast(major,minor int) bool
func (r *Request) Referer() string
func (r *Request) Write(w io.Writer) error
func (r *Request) WriteProxy(w io.Writer) error
1.2. 36.2 Response
http Response响应是由http Server服务端发出的消息,用来响应http Client端发出的http Request请求。发出的消息包括起始行, Headers, Body。
type Response struct
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func (r *Response) Cookies() []*Cookie
func (r *Response) Location() (*url.URL,error)
func (r *Response) ProtoAtLeast(major, minor int) bool
func (r *Response) Write(w io.Writer) error
1.3. 36.3 client
http Client客户端主要用来发送http Request请求给http Server服务端,比如以Do方法,Get方法以及Post或PostForm方法发送http Request请求。
type Client struct
func Get(url string) (resp *Response, err error)
func Head(url string) (resp *Response, err error)
func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)
func (c *Client) Get(url string) (resp *Response, err error)
func (c *Client) Head(url string) (resp *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)
http.NewRequest可以灵活的对http Request进行配置,然后再使用http.Client的Do方法发送这个http Request请求。注意:如果使用Post或者PostForm方法,是不能使用http.NewRequest配置请求的,只有Do方法可以定制http.NewRequest。
利用http.Client以及http.NewRequest就可以完整模拟一个http Request请求,包括自定义的http Request请求的头部信息。有了前面介绍的 http Request 请求、http Response 响应、http Client 客户端 三个部分,我们已经可以模拟各种http Request 请求的发送,接收http Response 响应了。
下面我们来模拟http Request请求,请求中附带有cookie信息,通过http.Client的Do方法发送这个请求。
先配置http.NewRequest,然后我们通过http.Client的Do方法来发送任何http Request请求。示例如下:
模拟任何http Request请求:
package main
import (
"compress/gzip"
"fmt"
"io/ioutil"
"net/http"
"strconv"
func main() {
client := &http.Client{}
request, err := http.NewRequest("GET", "http://www.baidu.com", nil)
if err != nil {
fmt.Println(err)
cookie := &http.Cookie{Name: "userId", Value: strconv.Itoa(12345)}
request.AddCookie(cookie)
request.Header.Set("Accept", "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
request.Header.Set("Accept-Charset", "GBK, utf-8;q=0.7, *;q=0.3")
request.Header.Set("Accept-Encoding", "gzip, deflate, sdch")
request.Header.Set("Accept-Language", "zh-CN, zh;q=0.8")
request.Header.Set("Cache-Control", "max-age=0")
request.Header.Set("Connection", "keep-alive")
response, err := client.Do(request)
if err != nil {
fmt.Println(err)
return
defer response.Body.Close()
fmt.Println(response.StatusCode)
if response.StatusCode == 200 {
body, err := gzip.NewReader(response.Body)
if
err != nil {
fmt.Println(err)
defer body.Close()
r, err := ioutil.ReadAll(body)
if err != nil {
fmt.Println(err)
fmt.Println(string(r))
使用http.Get 发送http Get请求非常简单,在一般简单不需要对http.Request配置的场景下我们可以使用,只需要提供URL即可。
发送一个http Get请求:
package main
import (
"fmt"
"io/ioutil"
"net/http"
func main() {
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
return c.Do(req)
response, err := http.Get("http://www.baidu.com")
if err != nil {
fmt.Println(err)
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(body))
使用http.Post 发送http Post请求也非常简单,在一般简单不需要对http.Request配置的场景下我们可以使用。
发送一个http.Post请求:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
func main() {
resp, err := http.Post("http://localhost:8080/login.do",
"application/x-www-form-urlencoded", strings.NewReader("mobile=xxxxxxxxxx&isRemberPwd=1"))
if err != nil {
fmt.Println(err)
return
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
fmt.Println(string(body))
使用http.PostForm 发送http Request请求也非常简单,而且可以附带参数的键值对作为请求的body传递到服务端。
发送一个http.PostForm请求:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
func main() {
postParam := url.Values{
"mobile": {"xxxxxx"},
"isRemberPwd": {"1"},
resp, err := http.PostForm("http://localhost:8080/login.do", postParam)
if err != nil {
fmt.Println(err)
return
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
fmt.Println(string(body))
上面列举了四种http Client客户端发送http Request请求的方式,其中只有Do方法最灵活。
http.Client与http.NewRequest结合可以模拟任何http Request请求,方法是Do。像Get方法,Post方法和PostForm方法,http.NewRequest都是定制好的,所以使用方便但灵活性不够。不过好在有Do方法,我们可以更灵活来配置http.NewRequest。
func NewRequest(method, url string, body io.Reader) (*Request, error)
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
......
func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body)
......
1.4. 36.4 http Server 服务端
http Server服务端用来接收并响应http Client端发出的http Request请求,是net/http包中非常重要和关键的一个功能。我们在Go语言中简单就能搭建HTTP服务器,就是因为它的存在。
在server.go文件中还定义了一个非常重要的接口:Handler,另外还有一个结构体response,这和http.Response结构体只有首字母大小写不一致,不过这个response 也是响应,只不过是专门用在服务端,和http.Response结构体是完全两回事。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
type Server struct
func (srv *Server) ListenAndServe() error
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error
func (srv *Server) Serve(l net.Listener) error
func (s *Server) SetKeepAlivesEnabled(v bool)
type ServeMux
func NewServeMux() *ServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
Handler接口应该算是server.go中最关键的接口了,如果我们仔细看这个文件的源代码,将会发现很多结构体实现了这个接口的ServeHTTP方法。
注意这个接口的注释:Handler响应HTTP请求。没错,最终我们的http服务是通过实现ServeHTTP(ResponseWriter, *Request)来达到服务端接收客户端请求并响应。
理解 HTTP 构建的网络应用只要关注两个端---客户端(Clinet)和服务端(Server),两个端的交互来自 Clinet 的 Request,以及Server端的Response。HTTP服务器,主要在于如何接受 Clinet端的 Request,Server端向Client端返回Response。
那这个过程是什么样的呢?要讲清楚这个过程,还需要回到开始的HTTP服务器程序。这里以前面我们了解到的http Request、http Response、http Client作为基础,并重点分析server.go源代码才能讲清楚:
func main() {
http.HandleFunc("/", myfunc)
http.ListenAndServe(":8080", nil)
以上两行代码,就成功启动了一个HTTP服务器。我们通过net/http 包源代码分析发现,调用Http.HandleFunc,按顺序做了几件事:
1.Http.HandleFunc调用了DefaultServeMux的HandleFunc
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
2.DefaultServeMux.HandleFunc调用了DefaultServeMux的Handle,DefaultServeMux是一个ServeMux 指针变量。而ServeMux 是Go语言中的Multiplexer(多路复用器),通过Handle匹配pattern 和我们定义的handler(其实就是http.HandlerFunc函数类型变量)。
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
上面的方法命名Handle,HandleFunc和HandlerFunc,Handler(接口),他们很相似,容易混淆。记住Handle和HandleFunc和pattern 匹配有关,也即往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则。
接着我们看看myfunc的声明和定义:
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
而type HandlerFunc func(ResponseWriter, *Request) 是一个函数类型,而我们定义的myfunc的函数签名刚好符合这个函数类型。
所以http.HandleFunc("/", myfunc),实际上是mux.Handle("/", HandlerFunc(myfunc))。
HandlerFunc(myfunc) 让myfunc成为了HandlerFunc类型,我们称myfunc为handler。而HandlerFunc类型是具有ServeHTTP方法的,而有了ServeHTTP方法也就是实现了Handler接口。
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
现在ServeMux和Handler都和我们的myfunc联系上了,myfunc是一个Handler接口变量也是HandlerFunc类型变量,接下来和结构体server有关了。
从http.ListenAndServe的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
而我们HTTP服务器中第二行代码:
http.ListenAndServe(":8080", nil)
创建了一个server对象,并调用server对象的ListenAndServe方法,这里没有直接传递Handler,而是默认使用DefautServeMux作为multiplexer,myfunc是存在于handler和路由规则中的。
Server的ListenAndServe方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。
for {
rw, e := l.Accept()
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
go c.serve(ctx)
监听开启之后,一旦客户端请求过来,Go就开启一个协程go c.serve(ctx)处理请求,主要逻辑都在serve方法之中。
func (c *conn) serve(ctx context.Context),这个方法很长,里面主要的一句:serverHandler{c.server}.ServeHTTP(w, w.req)。其中w由w, err := c.readRequest(ctx)得到,因为有传递context。
还是来看源代码:
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)
从http.ListenAndServe(":8080", nil)开始,handler是nil,所以最后实际ServeHTTP方法是DefaultServeMux.ServeHTTP(rw, req)。
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)
通过func (mux ServeMux) Handler(r Request) (h Handler, pattern string),我们得到Handler h,然后执行h.ServeHTTP(w, r)方法,也就是执行我们的myfunc函数(别忘了myfunc是HandlerFunc类型,而他的ServeHTTP(w, r)方法这里其实就是自己调用自己),把response写到http.ResponseWriter对象返回给客户端,fmt.Fprintf(w, "hi"),我们在客户端会接收到hi 。至此整个HTTP服务执行完成。
总结下,HTTP服务整个过程大概是这样:
Request -> ServeMux(Multiplexer) -> handler-> Response
我们再看下面代码:
http.ListenAndServe(":8080", nil)
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
上面代码实际上就是server.ListenAndServe()执行的实际效果,只不过简单声明了一个结构体Server{Addr: addr, Handler: handler}实例。如果我们声明一个Server实例,完全可以达到深度自定义 http.Server的目的:
package main
import (
"fmt"
"net/http"
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
func main() {
server := http.Server{
Addr: ":8080",
ReadTimeout: 0,
WriteTimeout: 0,
http.HandleFunc("/", myfunc)
server.ListenAndServe()
这样服务也能跑起来,而且我们完全可以根据情况来自定义我们的Server!
还可以指定Servemux的用法:
GOPATH\src\go42\chapter-15\15.3\7\main.go
package main
import (
"fmt"
"net/http"
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", myfunc)
http.ListenAndServe(":8080", mux)
如果既指定Servemux又自定义 http.Server,因为Server中有字段Handler,所以我们可以直接把Servemux变量作为Server.Handler:
package main
import (
"fmt"
"net/http"
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
func main() {
server := http.Server{
Addr: ":8080",
ReadTimeout: 0,
WriteTimeout: 0,
mux := http.NewServeMux()
server.Handler = mux
mux.HandleFunc("/", myfunc)
server.ListenAndServe()
在前面pprof 包的内容中我们也用了本章开头这段代码,当我们访问http://localhost:8080/debug/pprof/ 时可以看到对应的性能分析报告。
因为我们这样导入 _"net/http/pprof" 包时,在文件 pprof.go 文件中init 函数已经定义好了handler:
func init() {
http.HandleFunc("/debug/pprof/", Index)
http.HandleFunc("/debug/pprof/cmdline", Cmdline)
http.HandleFunc("/debug/pprof/profile", Profile)
http.HandleFunc("/debug/pprof/symbol", Symbol)
http.HandleFunc("/debug/pprof/trace", Trace)
所以,我们就可以通过浏览器访问上面地址来看到报告。现在再来看这些代码,我们就明白怎么回事了!
1.5. 36.5 自定义处理器(Custom Handlers)
自定义的Handler:
标准库http提供了Handler接口,用于开发者实现自己的handler。只要实现接口的ServeHTTP方法即可。
package main
import (
"log"
"net/http"
"time"
type timeHandler struct {
format string
func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(th.format)
w.Write([]byte("The time is: " + tm))
func main() {
mux := http.NewServeMux()
th := &timeHandler{format: time.RFC1123}
mux.Handle("/time", th)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
我们知道,NewServeMux可以创建一个ServeMux实例,ServeMux同时也实现了ServeHTTP方法,因此代码中的mux也是一种handler。把它当成参数传给http.ListenAndServe方法,后者会把mux传给Server实例。因为指定了handler,因此整个http服务就不再是DefaultServeMux,而是mux,无论是在注册路由还是提供请求服务的时候。
任何有 func(http.ResponseWriter,*http.Request) 签名的函数都能转化为一个 HandlerFunc 类型。这很有用,因为 HandlerFunc 对象内置了 ServeHTTP 方法,后者可以聪明又方便的调用我们最初提供的函数内容。
1.6. 36.6 将函数作为处理器
package main
import (
"log"
"net/http"
"time"
func timeHandler(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(time.RFC1123)
w.Write([]byte("The time is: " + tm))
func main() {
mux := http.NewServeMux()
th := http.HandlerFunc(timeHandler)
mux.Handle("/time", th)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
创建新的server:
func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<doctype html>
<title>Hello World</title>
</head>
Welcome
</body>
</html>`
fmt.Fprintln(w, html)
func main(){
http.HandleFunc("/", index)
server := &http.Server{
Addr: ":8000",
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
server.ListenAndServe()
1.7. 36.7 中间件Middleware
所谓中间件,就是连接上下级不同功能的函数或者软件,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。前文的HandleFunc就能把签名为 func(w http.ResponseWriter, r *http.Reqeust)的函数包裹成handler。这个函数也算是中间件。
Go的http中间件很简单,只要实现一个函数签名为func(http.Handler) http.Handler的函数即可。http.Handler是一个接口,接口方法我们熟悉的为serveHTTP。返回也是一个handler。因为Go中的函数也可以当成变量传递或者或者返回,因此也可以在中间件函数中传递定义好的函数,只要这个函数是一个handler即可,即实现或者被handlerFunc包裹成为handler处理器。
func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<doctype html>
<title>Hello World</title>
</head>
Welcome
</body>
</html>`
fmt.Fprintln(w, html)
func middlewareHandler(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
next.ServeHTTP(w, r)
func loggingHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
func main() {
http.Handle("/", loggingHandler(http.HandlerFunc(index)))
http.ListenAndServe(":8000", nil)
1.8. 36.8 静态站点
下面代码通过指定目录,作为静态站点:
package main
import (
"net/http"
func main() {
http.Handle("/", http.FileServer(http.Dir("D:/html/static/")))
http.ListenAndServe(":8080", nil)
本书《Go语言四十二章经》内容在github上同步地址:https://github.com/ffhelicopter/Go42
本书《Go语言四十二章经》内容在简书同步地址: https://www.jianshu.com/nb/29056963
虽然本书中例子都经过实际运行,但难免出现错误和不足之处,烦请您指出;如有建议也欢迎交流。
联系邮箱:[email protected]
2019-04-30 19:18:04