簡介Hijack
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對應(yīng)的TCP連接取出西饵,連接在Hijack()
之后漂问,HTTP的相關(guān)操作就會受到影響幅聘,調(diào)用方需要負責去關(guān)閉連接∈璩妫看一個簡單的例子。
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
方法有什么區(qū)別呢卧秘?很簡單呢袱,同樣是http請求,返回的結(jié)果一個遵循h(huán)ttp協(xié)議翅敌,一個不遵循羞福。
? ~ 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是相同的,但是完全沒有遵循h(huán)ttp協(xié)議遭顶。(廢話张峰,別人都說了hijack之后返回了body然后直接關(guān)閉了,哪來的headers = = )
但我們還是要看看為啥..
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路由的核心方法喘批。調(diào)用ServeHTTP
(也就是上邊的handle方法)方法,如果被hijack
了就直接return了铣揉,而一般的http請求會經(jīng)過后邊的finishRequest
方法谤祖,加入headers等并關(guān)閉連接。
打開方式
上邊我們說了Hijack
方法老速,一般在在創(chuàng)建連接階段使用HTTP連接,后續(xù)自己完全處理connection凸主。符合這樣的使用場景的并不多橘券,基于HTTP協(xié)議的rpc算一個,從HTTP升級到WebSocket也算一個卿吐。
RPC中的應(yīng)用
go中自帶的rpc可以直接復(fù)用http server處理請求的那一套流程去創(chuàng)建連接旁舰,連接創(chuàng)建完畢后再使用Hijack
方法拿到連接。
// 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)
}
客戶端通過向服務(wù)端發(fā)送method為connect的請求創(chuàng)建連接嗡官,創(chuàng)建成功后即可開始rpc調(diào)用箭窜。
websocket中的應(yīng)用
// 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在創(chuàng)建連接的階段與http使用相同的協(xié)議,而在后邊的數(shù)據(jù)傳輸?shù)倪^程中使用了他自己的協(xié)議衍腥,符合了Hijack
的用途磺樱。通過serveWebSocket
方法將HTTP協(xié)議升級到Websocket協(xié)議。