httputil.ReverseProxy + websockets

4,238 views
Skip to first unread message

brad.ry...@gmail.com

unread,
Apr 24, 2012, 6:38:18 PM4/24/12
to golan...@googlegroups.com
I running a node.js / socket.io (websocket) application on localhost (http://scrumblr.ca/) ... it works great. Now i'm trying to put a proxy in front of it using httputil.ReverseProxy

I can see the request for the connection upgrade:

&{GET /socket.io/1/websocket/16993572521850517416 HTTP/1.1 1 1 map[Connection:[Upgrade] Origin:[http://localhost:8080] Sec-Websocket-Extensions:[x-webkit-deflate-frame] Upgrade:[websocket] Sec-Websocket-Key:[MIwYnTOdMSQedUHvF0ku+A==] Cookie:[scrumscrum-cookie=V3vN6Yly8Y49adk0dEyqps0p.ZSBUt90x3%2B86NnJub7FIR97oOw5mJeo14isMKRUqVXc; scrumscrum-username=unknown] Sec-Websocket-Version:[13]] 0xf8400f3e00 0 [] false localhost:8080 map[] <nil> map[] 127.0.0.1:60580 /socket.io/1/websocket/16993572521850517416 <nil>}

I see an error message written to the console:

2012/04/24 15:34:03 Unsolicited response received on idle HTTP channel starting with "\x81"; err=<nil>

Then it looks like the browser starts polling, trying to reconnect, but i'm not 100% sure since i'm still digging through scrublr's client side code 

I'm wondering ... has anyone tried using the ReverseProxy utility when applications behind the proxy use websockets?

Kyle Lemons

unread,
Apr 24, 2012, 7:49:24 PM4/24/12
to brad.ry...@gmail.com, golan...@googlegroups.com
We have an application that uses websocket, but I got halfway through implementing the upgrade and got distracted by higher-priority things.  The easiest way, I believe, is to have a higher-priority entry in your mux to handle the websocket connection separately, and then you hijack the connection and io.Copy back and forth.  Since I never finished, though, I can't say if this will actually work :).

Brad Fitzpatrick

unread,
Apr 24, 2012, 8:59:34 PM4/24/12
to golang-nuts, brad.ry...@gmail.com
That works.  I did exactly that yesterday for the proxy I run in front of a dozen websites, including websomtep.danga.com (which is WebSockets).

The code is:

func websocketProxy(target string) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                d, err := net.Dial("tcp", target)
                if err != nil {
                        http.Error(w, "Error contacting backend server.", 500)
                        log.Printf("Error dialing websocket backend %s: %v", target, err)
                        return
                }
                hj, ok := w.(http.Hijacker)
                if !ok {
                        http.Error(w, "Not a hijacker?", 500)
                        return
                }
                nc, _, err := hj.Hijack()
                if err != nil {
                        log.Printf("Hijack error: %v", err)
                        return
                }
                defer nc.Close()
                defer d.Close()

                err = r.Write(d)
                if err != nil {
                        log.Printf("Error copying request to target: %v", err)
                        return
                }

                errc := make(chan error, 2)
                cp := func(dst io.Writer, src io.Reader) {
                        _, err := io.Copy(dst, src)
                        errc <- err
                }
                go cp(d, nc)
                go cp(nc, d)
                <-errc
        })

brad.ry...@gmail.com

unread,
Apr 25, 2012, 2:25:32 AM4/25/12
to golan...@googlegroups.com, brad.ry...@gmail.com
that definitely did the trick, thanks for providing the code!

Fatih Arslan

unread,
Jun 23, 2013, 12:58:51 PM6/23/13
to golan...@googlegroups.com, brad.ry...@gmail.com
Hi,

I've also implemented proxy trough hijacking. I've used the exact webSocket http.Handler you provided in your example. However I'm serving my proxy trough path "/" thus I have to distinguish between HTTP requests and incoming websocket connection in order to call the webSocket http.Handler. My ServeHTTP is like:

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if isWebsocket(r) {
p := websocketProxy("backendTargetURL") //example URL
p.ServeHTTP(w, r)
return
}

if h := s.handler(r); h != nil {
h.ServeHTTP(w, r)
return
}
http.Error(w, "Not found.", http.StatusNotFound)
}


To check for Websocket, I'm using the function:


func isWebsocket(req *http.Request) bool {
conn_hdr := ""
conn_hdrs := req.Header["Connection"]
if len(conn_hdrs) > 0 {
conn_hdr = conn_hdrs[0]
}

upgrade_websocket := false
if strings.ToLower(conn_hdr) == "upgrade" {
upgrade_hdrs := req.Header["Upgrade"]
if len(upgrade_hdrs) > 0 {
upgrade_websocket = (strings.ToLower(upgrade_hdrs[0]) == "websocket")
}
}

return upgrade_websocket
}

If this returns true, I'm calling my webSocket http.ProxyHandler. Is this a correct approach? Or is it better when I listen to a path like:

http.Handle("/ws", websocketProxy)

And route all websocket connections to this handler and call this websocket handler directly? (without using the isWebsocket function above)

Which way is better and is the implementation correct?

Thanks in advance
Regards
Reply all
Reply to author
Forward
0 new messages