入门

简单get请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

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

func main() {
resp, err := http.Get("https://www.liwenzhou.com/")
if err != nil {
fmt.Printf("get failed, err:%v\n", err)
return
}
// 使用完response后必须关闭回复的主体。
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("read from resp.Body failed, err:%v\n", err)
return
}
fmt.Print(string(body))
}

带参数get请求

1
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
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)

func main() {
apiUrl := "http://127.0.0.1:9090/get"
// URL param
data := url.Values{}
data.Set("name", "小王子")
data.Set("age", "18")

u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Printf("parse url requestUrl failed, err:%v\n", err)
}
u.RawQuery = data.Encode() // URL encode
fmt.Println(u.String()) // http://127.0.0.1:9090/get?age=18&name=%E5%B0%8F%E7%8E%8B%E5%AD%90

resp, err := http.Get(u.String())
if err != nil {
fmt.Printf("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()

b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("get resp failed, err:%v\n", err)
return
}
fmt.Println(string(b))
}

对应的Server端HandlerFunc

1
2
3
4
5
6
7
8
func getHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data := r.URL.Query()
fmt.Println(data.Get("name"))
fmt.Println(data.Get("age"))
answer := `{"status": "ok"}`
w.Write([]byte(answer))
}

post请求

1
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
package main

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

func main() {
url := "http://127.0.0.1:9090/post"

// 表单form数据
// contentType := "application/x-www-form-urlencoded"
// data := "name=小王子&age=18"

// json数据
contentType := "application/json"
data := `{"name":"小王子","age":18}`

resp, err := http.Post(url, contentType, strings.NewReader(data))
if err != nil {
fmt.Printf("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()

b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("get resp failed, err:%v\n", err)
return
}
fmt.Println(string(b))
}

对应的Server端HandlerFunc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func postHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// 1. 请求类型是application/x-www-form-urlencoded时解析form数据
r.ParseForm()
fmt.Println(r.PostForm) // 打印form数据
fmt.Println(r.PostForm.Get("name"), r.PostForm.Get("age"))

// 2. 请求类型是application/json时从r.Body读取数据
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("read request.Body failed, err:%v\n", err)
return
}
fmt.Println(string(b))
answer := `{"status": "ok"}`
w.Write([]byte(answer))
}

postForm

1
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
package main

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

func main() {
postParam := url.Values{
"mobile": {"xxxxxx"},
"isRemberPwd": {"1"},
}

resp, err := http.PostForm("http://www.maimaiche.com/loginRegister/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))
}

Go Http客户端

1
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
package main

import (
"fmt"
"net/http"
"log"
"reflect"
"bytes"
)

func main() {
// 简单请求
resp, err := http.Get("http://www.baidu.com")
if err != nil {
// handle error
log.Println(err)
return
}

defer resp.Body.Close()

headers := resp.Header
for k, v := range headers {
fmt.Printf("k=%v, v=%v\n", k, v)
}

fmt.Printf("resp status %s,statusCode %d\n", resp.Status, resp.StatusCode)
fmt.Printf("resp Proto %s\n", resp.Proto)
fmt.Printf("resp content length %d\n", resp.ContentLength)
fmt.Printf("resp transfer encoding %v\n", resp.TransferEncoding)
fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)
fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReader

// 读取response
buf := bytes.NewBuffer(make([]byte, 0, 512))
length, _ := buf.ReadFrom(resp.Body)

fmt.Println(len(buf.Bytes()))
fmt.Println(length)
fmt.Println(string(buf.Bytes()))
}

设置头参数、cookie

1
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
package main

import (
"net/http"
"strings"
"fmt"
"io/ioutil"
"log"
"encoding/json"
)

func main() {
client := &http.Client{}

req, err := http.NewRequest("POST", "http://www.maimaiche.com/loginRegister/login.do",
strings.NewReader("mobile=xxxxxxxxx&isRemberPwd=1"))
if err != nil {
log.Println(err)
return
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")

resp, err := client.Do(req)

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}

fmt.Println(resp.Header.Get("Content-Type")) //application/json;charset=UTF-8

type Result struct {
Msg string
Status string
Obj string
}

// 读取json到Result对象
result := &Result{}
json.Unmarshal(body, result) //解析json字符串

if result.Status == "1" {
fmt.Println(result.Msg)
} else {
fmt.Println("login error")
}
fmt.Println(result)
}

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello\n")
}

func headers(w http.ResponseWriter, req *http.Request) {
for name, headers := range req.Header {
for _, h := range headers {
fmt.Fprintf(w, "%v: %v\n", name, h)
}
}
}

func main() {
http.HandleFunc("/hello", hello)
http.HandleFunc("/headers", headers)
http.ListenAndServe(":8090", nil)
}

HandlerFunc

HandlerFunc 实际上是以函数类型 func(ResponseWriter, *Request) 为底层类型,为 HandlerFunc 类型定义了方法 ServeHTTP。是的,Go 语言允许为(基于)函数的类型定义方法。Serve.Handle() 方法只接受类型为接口 Handler 的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

func (mux *ServeMux) Handle(pattern string, handler Handler) {
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
mux.m[pattern] = e
}

显然 HandlerFunc 实现了接口 HandlerHandlerFunc 类型只是为了方便注册函数类型的处理器。我们当然可以直接定义一个实现 Handler 接口的类型,然后注册该类型的实例:

1
2
3
4
5
6
7
type greeting string

func (g greeting) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, g)
}

http.Handle("/greeting", greeting("Welcome, dj"))

Web 服务器创建启动流程

img

img

Hijacker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Hijacker interface {
// Hijack lets the caller take over the connection.
// After a call to Hijack the HTTP server library
// will not do anything else with the connection.
//
// It becomes the caller's responsibility to manage
// and close the connection.
//
// The returned net.Conn may have read or write deadlines
// already set, depending on the configuration of the
// Server. It is the caller's responsibility to set
// or clear those deadlines as needed.
//
// The returned bufio.Reader may contain unprocessed buffered
// data from the client.
//
// After a call to Hijack, the original Request.Body must
// not be used.
Hijack() (net.Conn, *bufio.ReadWriter, error)
}

Hijack() 可以将 HTTP 对应的 TCP 连接取出,连接在 Hijack() 之后,HTTP 的相关操作就会受到影响,调用方需要负责去关闭连接。

也就是说,Hijack 接管了 HTTP 的 Socket 连接,之后 Golang 的内置 HTTP 库和 HTTPServer 库将不会管理这个 Socket 连接的生命周期,这个生命周期已经划给 Hijacker 了。

看一个简单的例子。

1
2
3
4
5
6
7
8
9
10
11
func handle1(w http.ResponseWriter, r *http.Request) {
hj, _ := w.(http.Hijacker)
conn, buf, _ := hj.Hijack()
defer conn.Close()
buf.WriteString("hello world")
buf.Flush()
}

func handle2(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
}

问题来了,上面两个 handle 方法有什么区别呢?很简单,同样是 http 请求,返回的结果一个遵循 http 协议,一个不遵循。

1
2
3
4
5
6
7
8
➜  ~ curl -i http://localhost:9090/handle1
hello world% ➜ ~ curl -i http://localhost:9090/handle2
HTTP/1.1 200 OK
Date: Thu, 14 Jun 2018 07:51:31 GMT
Content-Length: 11
Content-Type: text/plain; charset=utf-8

hello world%

分别是以上两者的返回,可以看到,hijack 之后的返回,虽然 body 是相同的,但是完全没有遵循 http 协议。(废话,别人都说了 hijack 之后返回了 body 然后直接关闭了,哪来的 headers)

但我们还是要看看为啥..

1
2
3
4
5
6
7
8
9
10
func (c *conn) serve(ctx context.Context) {
...
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
...
}

这是 net/http 包中的方法,也是 http 路由的核心方法。调用 ServeHTTP(也就是上边的 handle 方法)方法,如果被 hijack 了就直接 return 了,而一般的 http 请求会经过后边的 finishRequest 方法,加入 headers 等并关闭连接。

Hijack 方法,一般在在创建连接阶段使用 HTTP 连接,后续自己完全处理 connection。符合这样的使用场景的并不多,基于 HTTP 协议的 rpc 算一个,从 HTTP 升级到 WebSocket 也算一个。

go 中自带的 rpc 可以直接复用 http server 处理请求的那一套流程去创建连接,连接创建完毕后再使用 Hijack 方法拿到连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ServeHTTP implements an http.Handler that answers RPC requests.
func (server *server) servehttp(w http.responsewriter, req *http.request) {
if req.method != "connect" {
w.header().set("content-type", "text/plain; charset=utf-8")
w.writeheader(http.statusmethodnotallowed)
io.writestring(w, "405 must connect\n")
return
}
conn, _, err := w.(http.hijacker).hijack()
if err != nil {
log.print("rpc hijacking ", req.remoteaddr, ": ", err.error())
return
}
io.writestring(conn, "http/1.0 "+connected+"\n\n")
server.serveconn(conn)
}

客户端通过向服务端发送 method 为 connect 的请求创建连接,创建成功后即可开始 rpc 调用。

websocket 中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ServeHTTP implements the http.Handler interface for a WebSocket
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.serveWebSocket(w, req)
}

func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
rwc, buf, err := w.(http.Hijacker).Hijack()
if err != nil {
panic("Hijack failed: " + err.Error())
}
// The server should abort the WebSocket connection if it finds
// the client did not send a handshake that matches with protocol
// specification.
defer rwc.Close()
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
if err != nil {
return
}
if conn == nil {
panic("unexpected nil conn")
}
s.Handler(conn)
}

websocket 在创建连接的阶段与 http 使用相同的协议,而在后边的数据传输的过程中使用了他自己的协议,符合了 Hijack 的用途。通过 serveWebSocket 方法将 HTTP 协议升级到 Websocket 协议。