>

Go 的 http 包提供了一个 Hijacker 接口类型,如果我们不想使用 Go 的 http 连接默认行为,我们通过使用这个接口,“劫获” http 连接,并且把它转换成我们自己的管理方式。
一旦我们“劫获”了原来的http连接,我们可以自己操控这个连接,也需要自己来处理这个连接的关闭。通常,我们还可以把劫获的 http 转换成 websocket 或者 rpc 等方式,

下面看一个简单的例子来比较默认的 http 行为和使用 Hijacker “劫取”之后的行为:

(1) Default http

Request Handler function

1
2
3
func PrintSomething(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world!")
}

Response:

1
2
3
4
5
6
HTTP/1.1 200 OK
Date: Wed, 26 Nov 2014 03:37:57 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

Hello world!

(2) After Hijacked

1
2
3
4
5
6
func hijack(w http.ResponseWriter, r *http.Request) {
hj, _ := w.(http.Hijacker)
_, buf, _ := hj.Hijack();
buf.WriteString("Hello world!")
buf.Flush()
}

The message “Hello world!” will be write to the output directly without header part, like below.

Response:

1
Hello world!

Notice that both of http.ResponseWriter and http.Hijacker are interfaces, this means w implements both interfaces.

Hijacker prototype

1
2
3
4
5
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.
Hijack() (net.Conn, *bufio.ReadWriter, error)
}

Let’s see an example to see how we can hijack a normal http request and convert it to RPC protocol.

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
// ServeHTTP hijack the HTTP connection and switch to RPC.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Before hijacke any connection, make sure the pd is initialized.
if s.isClosed() {
return
}

hj, ok := w.(http.Hijacker)
if !ok {
log.Errorf("server doesn't support hijacking: conn %v", w)
return
}

conn, bufrw, err := hj.Hijack()
if err != nil {
log.Error(err)
return
}

err = conn.SetDeadline(time.Time{})
if err != nil {
log.Error(err)
conn.Close()
return
}

c, err := newConn(s, conn, bufrw)
if err != nil {
log.Error(err)
conn.Close()
return
}

s.wg.Add(1)
go c.run()
}

Notice that by executing hj.Hijack() we get the conn and buffer reader & writer out of the http connection, we generate a new conn object, which is different from original one, from them by
doing newConn().

newConne is look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func newConn(s *Server, netConn net.Conn, bufrw *bufio.ReadWriter) (*conn, error) {
s.connsLock.Lock()
defer s.connsLock.Unlock()

c := &conn{
s: s,
rb: bufrw.Reader,
wb: bufrw.Writer,
conn: netConn,
}

s.conns[c] = struct{}{}

return c, nil
}

After that we simply run this newly create conn.

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
func (c *conn) run() {
for {
msg := &pb.Message{}
msgID, err := util.ReadMessage(c.rb, msg)
if err != nil {
log.Errorf("read request message err %v", err)
return
}

if msg.GetMsgType() != pb.MessageType_Req {
log.Errorf("invalid request message %v", msg)
return
}

request := msg.GetReq()

var response *pb.Response
response, err = c.handleRequest(request)
if err != nil {
log.Errorf("handle request %s err %v", request, errors.ErrorStack(err))
response = newError(err)
}

if response == nil {
log.Warn("empty response")
continue
}

updateResponse(request, response)

msg = &msgpb.Message{
MsgType: pb.MessageType_Resp,
Resp: response,
}

util.WriteMessage(c.wb, msgID, msg)
c.wb.Flush()
}
}

In the above code example (for simplicity, I removed some error checking blocks), a proto buffer response struct is constructed to for the client request, and therefore
the original http transport is switched to RPC communication.


extention

http.Hijacker is also a good learning example on how to use interface in Go.

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

import "fmt"

type ResponseWriter interface {
Write() (int, error)
}

type Hijacker interface {
Hijack() error
}

type wter struct{}

func (writer wter) Write() (int, error) {
return 0, nil
}

func (writer wter) Hijack() error {
fmt.Println("I am in Hijack()")
return nil
}

func testHj(w ResponseWriter) {
hj, ok := w.(Hijacker)
if ok {
fmt.Println("wt is Hijacker!")
} else {
fmt.Println("wt is not Hijacker!")
}
hj.Hijack()
}

func main() {
wt := wter{}
testHj(wt)
}

In the above example, I emulate two interfaces like the ResponseWriter and Hijacker provided by the Go standard library net/http, then I define a wter struct to implement both of the interfaces, and pass it in as an argument in testHj function, the type assertion suceeds as expected.

Note: if we use type assertion on a declared type object, we have to store it in a interface variable,
otherwise we will receive errors saying “invalid type assertion: (non-interface type xxx on left)”

1
2
3
4
5
6
7
8
9
10
func main() {
var w interface{} = wter{} // if we use w := wter{}, we will encounter compile error described as above
hj, ok := w.(Hijacker)
if ok {
fmt.Println("wt is Hijacker!")
} else {
fmt.Println("wt is not Hijacker!")
}
hj.Hijack()
}



Reference
http://stackoverflow.com/questions/27075478/when-to-use-hijack-in-golang