# Go语言之http
http
包提供了http
客户端和服务端的实现,提供Get
,Head
,Post
和PostForm
函数发出http、https
的请求。程序在使用完回复后必须关闭回复的主体。net/http源码学习
# 1 HTTP Server
Go中,实现一个最简单的http server非常容易,代码如下:
package main
import (
"fmt"
"net/http"
)
//访问http://127.0.0.1:9099/hello的回调处理函数
func IndexHandlers(w http.ResponseWriter, r *http.Request){
//w.Write([]byte("hello world!"))
fmt.Fprintln(w, "hello, world")
}
func main (){
//HandleFunc注册一个处理器函数handler和对应的模式pattern。
http.HandleFunc("/hello", IndexHandlers)
//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
// handler参数一般会设为nil,此时会使用DefaultServeMux。
err := http.ListenAndServe("127.0.0.1:9099", nil)
//如果监听失败了,就打印报错信息
if err != nil {
fmt.Printf("listen error:[%v]", err.Error())
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2 http.Get和 http.Post
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
//模拟一个post提交请求
// (resp *Response): 网络服务返回的响应内容的结构化表示
// (err error): 创建和发送HTTP请求,以及接收和解析HTTP响应的过程中可能发生的错误
resp, err := http.Post("http://www.baidu.com", "application/x-www-form-urlencoded", strings.NewReader("id=1"))
if err != nil {
panic(err)
}
//关闭连接
defer resp.Body.Close()
//读取报文中所有内容
body, err := ioutil.ReadAll(resp.Body)
//输出内容
fmt.Println(string(body))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3 http.Client类型
http.Client是一个结构体,并且它包含的字段都是公开的。该类型是开箱即用的,因为它的所有字段,要么存在相应的缺省值,要么其零值直接就可以使用,并且代表着特定的含义。
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
2
3
4
5
6
# 3.1 Transport字段
主要看下Transport字段,该字段向网络服务发送HTTP请求,并从网络服务接收HTTP响应。该字段的方法RoundTrip应该实现单次HTTP事务(或者说基于HTTP协议的单次交互)需要的所有步骤。这个字段是一个接口:
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
2
3
并且该字段有一个由http.DefaultTransport
变量的缺省值:在初始化http.Client
类型的时候,如果没有显式的为该字段赋值,这个Client
字段就会直接使用DefaultTransport
。
func (c *Client) transport() RoundTripper {
if c.Transport != nil {
return c.Transport
}
return DefaultTransport
}
2
3
4
5
6
# 3.2 Timeout字段
该字段是单次HTTP事务的超时时间,它是time.Duration
类型。它的零值是可用的,用于表示没有设置超时时间。
# 3.3 http.Transport类型
http.Transport
类型是一个结构体,该类型包含的字段很多。这里通过http.Client
结构体中的Transport
字段的缺省值DefaultTransport
,来深入了解一下。DefaultTransport
是一个*http.Transport
的结构体,做了一些默认的设置:
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
2
3
4
5
6
7
8
9
10
11
12
这里Transport结构体的指针就是就是RoundTripper接口的默认实现:
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
return t.roundTrip(req)
}
2
3
这个类型是可以被复用的,并且也推荐被复用。同时它也是并发安全的。所以
http.Client
类型也是一样,推荐复用,并且并发安全。 看上面的默认设置,http.Transport
类型,内部的DialContext
字段会使用net.Dialer
类型的值,并且把Timeout
设置为30秒。仔细看,该值是一个方法,这里把Dialer
值的DialContext
方法赋值给了DefaultTransport
里的同名字段,并且已经设置好了调用该方法时的结构体。\
# 4 Server示例
package main
import (
"fmt"
"net/http"
"os"
"sync"
)
var wg sync.WaitGroup
// 一般没有这么用的,http.Server的Handler字段
// 要么是nil,就用包里的http.DefaultServeMux
// 要么用NewServeMux()来创建一个*http.ServeMux
// 我这里按照http.Handler接口的要求实现了一个,赋值给Handler字段
// 这个自定义的Handler不支持路由
func startServer1() {
defer wg.Done()
var httpServer http.Server
httpServer.Addr = "127.0.0.1:8001"
httpServer.Handler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
fmt.Println(*r)
fmt.Fprint(w, "Hello World")
},
)
fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8001")
if err := httpServer.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
fmt.Println("HTTP Server1 Closed.")
} else {
fmt.Fprintf(os.Stderr, "HTTP Server1 Error: %v\n", err)
}
}
}
// 这个最简单,都是调用http包里的函数。本质上还是要调用方法的,都会用默认的或是零值
func startServer2() {
defer wg.Done()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World\nThis is Server2")
})
fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8002")
// 第二个参数传nil,就是用包里的http.DefaultServeMux,或者也可以自己创建一个传给第二个参数
if err := http.ListenAndServe("127.0.0.1:8002", nil); err != nil {
if err == http.ErrServerClosed {
fmt.Println("HTTP Server2 Closed.")
} else {
fmt.Fprintf(os.Stderr, "HTTP Server2 Error: %v\n", err)
}
}
}
// 这个例子里用到了解析Get请求的参数,并且还设置了2个路由
func startServer3() {
defer wg.Done()
mux := http.NewServeMux()
mux.HandleFunc("/hi", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hi" {
// 这个分支应该是进不来的,因为要进入这个分支,路径应该必须是"/hi"
fmt.Println("Server3 hi 404")
http.NotFound(w, r)
return
}
name := r.FormValue("name")
if name == "" {
fmt.Fprint(w, "Hi!")
} else {
fmt.Fprintf(w, "Hi, %s!", name)
}
})
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World\nThis is Server3")
})
// 如果只是定义http.Server的下面2个字段,完全可以使用http.ListenAndServe函数来启动服务
// 这样的用法可以对http.Server里更多的字段进行自定义
httpServer := http.Server{
Addr: "127.0.0.1:8003",
Handler: mux,
}
fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8003/hi?name=Adam")
if err := httpServer.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
fmt.Println("HTTP Server3 Closed.")
} else {
fmt.Fprintf(os.Stderr, "HTTP Server3 Error: %v\n", err)
}
}
}
func main() {
wg.Add(1)
go startServer1()
wg.Add(1)
go startServer2()
wg.Add(1)
go startServer3()
wg.Wait()
}
--------------------------输出结果-------------------------------
启动服务,浏览器访问: http://127.0.0.1:8003/hi?name=Adam
启动服务,浏览器访问: http://127.0.0.1:8002
启动服务,浏览器访问: http://127.0.0.1:8001
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 5 优雅的停止HTTP服务
包里还提供了一个Shutdown方法,可以优雅的停止HTTP服务:
func (srv *Server) Shutdown(ctx context.Context) error {
// 内容省略
}
2
3
我们要做的就是在需要的时候,可以调用该
Shutdown
方法。 这里的问题是,调用了ListenAndServe
方法之后,就进入了无限循环的流程。 这里最好是用一个goroutine
来启动ListenAndServe
方法,在goroutine
外声明http.Server
。 然后在主线程里等待一个信号,比如是从通道接收值。这样就可以在主线程里调用这个Shutdown
方法执行了。