Skip to content

Instantly share code, notes, and snippets.

@rpl
Created October 11, 2011 17:55
Show Gist options
  • Save rpl/1278843 to your computer and use it in GitHub Desktop.
Save rpl/1278843 to your computer and use it in GitHub Desktop.
Go Websocket implementation, hybi and Firefox
_obj
*~
bin
*.6
Currently Go websocket package works on Chromium and Google Chrome,
but doesn't work on any Firefox versions, so we've started to look out
to better understand the problem and find a solution or a workaround.
Follows some internal notes on the debugging session and a proposed patch.
TESTING BEHAVIOR
----------------
We've added some logging to the websocket package
(tmp-websocket-morelogging-go9990.patch) and coded a minimal websocket
example (hello-websocket-server.go) to detect where the current
implementation fails on firefox.
- build and launch hello-websocket
- Go (6g version weekly.2011-10-06 9990)
- open a browser on localhost:8081/public
- look at the produced logs (on the browser page and server output on
the console)
# BEFORE APPLYING THE PATCH (go version 9990):
- Chromium (13.0.782.218 (Developer Build 98754 Linux) Ubuntu 11.04)
- hybi FAIL (error: "mismatch challenge/response")
- hixie76 FALLBACK & WORKS
- Google Chrome (14.0.835.202)
- hybi WORKS
- Firefox nightly (10.0a1), Firefox aurora (9.0a2), Firefox beta (8.0b2),
Firefox stable (7.0.1), Firefox 6.0.2
- hybi FAIL (error: "not websocket protocol")
- hixie76 FALLBACK & FAIL (error: "not websocket protocol")
- hixie75 FALLBACK & FAIL (error: "not websocket protocol")
# AFTER APPLYING THE PATCH (websocket-fix-hiby-go9990.patch):
- Firefox 6.0.2
- BADWEBSOCKETVERSION missing or bad WebSocket Version
- Firefox >= 7.0.1
- hybi WORKS
PROBLEM ANALISYS
----------------
Quoting from the ietf hybi draft, page 29 n. 6:
The request MUST contain a "Connection" header whose value MUST
include the "Upgrade" token.
(http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10)
Current behavior (as in "MUST be equal")
pkg/websocket/hiby.go (function ReadHandshake):
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
...
}
Fixed behavior (as in "MUST include"):
pkg/websocket/hiby.go (function ReadHandshake):
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
...
}
package main
import (
"http"
"websocket"
"io"
"fmt"
)
var WebsocketHandler = websocket.Handler(WebsocketServer)
func WebsocketServer(ws *websocket.Conn) {
io.Copy(ws, ws) // Echo
}
func main() {
// Static handler for public resources
http.Handle("/public/", http.FileServer(http.Dir("./")))
http.Handle("/websocket", WebsocketHandler)
fmt.Println("Open your browser on http://127.0.0.1:8081/public")
if err := http.ListenAndServe("127.0.0.1:8081", nil); err != nil {
panic(err)
}
}
<div id="results">
</div>
<script>
var el = document.getElementById("results");
if(typeof MozWebSocket !== "undefined")
WebSocket = MozWebSocket;
var socket = new WebSocket("ws://"+window.location.host+"/websocket");
socket.onopen = function () {
el.innerHTML += "OPENED<br/>";
socket.send("testmessage");
}
socket.onmessage = function(msg) { el.innerHTML += "RECEIVED DATA "+msg.data+"<br/>"; }
socket.onclose = function() { el.innerHTML += "CLOSED<br/>"; }
</script>
include $(GOROOT)/src/Make.inc
TARG=bin/hello-websocket-server
GOFILES=hello-websocket-server.go
include $(GOROOT)/src/Make.cmd
diff -r 9c743824e7d6 src/pkg/websocket/hybi.go
--- a/src/pkg/websocket/hybi.go Tue Oct 11 11:11:47 2011 +1100
+++ b/src/pkg/websocket/hybi.go Tue Oct 11 19:54:15 2011 +0200
@@ -21,6 +21,7 @@
"os"
"strings"
"url"
+ "log"
)
const (
@@ -477,6 +478,8 @@
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
+ log.Print("DEBUG ", strings.ToLower(req.Header.Get("Upgrade")), " - ", strings.ToLower(req.Header.Get("Connection")))
+
return http.StatusBadRequest, ErrNotWebSocket
}
diff -r 9c743824e7d6 src/pkg/websocket/server.go
--- a/src/pkg/websocket/server.go Tue Oct 11 11:11:47 2011 +1100
+++ b/src/pkg/websocket/server.go Tue Oct 11 19:54:15 2011 +0200
@@ -10,6 +10,7 @@
"http"
"io"
"os"
+ "log"
)
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request) (conn *Conn, err os.Error) {
@@ -17,6 +18,7 @@
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
code, err := hs.ReadHandshake(buf.Reader, req)
if err == ErrBadWebSocketVersion {
+ log.Print("BADWEBSOCKETVERSION ",err)
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
buf.WriteString("\r\n")
@@ -24,14 +26,17 @@
return
}
if err != nil {
+ log.Print("TRY hixie76 ",err)
hs = &hixie76ServerHandshaker{Config: config}
code, err = hs.ReadHandshake(buf.Reader, req)
}
if err != nil {
+ log.Print("TRY hixie75 ",err)
hs = &hixie75ServerHandshaker{Config: config}
code, err = hs.ReadHandshake(buf.Reader, req)
}
if err != nil {
+ log.Print("FAIL WEBSOCKET AND RETURN ",err)
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
buf.WriteString("\r\n")
buf.WriteString(err.String())
diff -r 9c743824e7d6 src/pkg/websocket/hybi.go
--- a/src/pkg/websocket/hybi.go Tue Oct 11 11:11:47 2011 +1100
+++ b/src/pkg/websocket/hybi.go Wed Oct 12 12:25:49 2011 +0200
@@ -476,7 +476,7 @@
// HTTP version can be safely ignored.
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
- strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
+ !strings.Contains(strings.ToLower(req.Header.Get("Connection")),"upgrade") {
return http.StatusBadRequest, ErrNotWebSocket
}
diff -r 9c743824e7d6 src/pkg/websocket/hybi_test.go
--- a/src/pkg/websocket/hybi_test.go Tue Oct 11 11:11:47 2011 +1100
+++ b/src/pkg/websocket/hybi_test.go Wed Oct 12 12:25:49 2011 +0200
@@ -30,6 +30,55 @@
}
}
+// Test the hybiServerHandshaker supports firefox implementation and
+// checks Connection header include (but it's not necessary equal to)
+// "upgrade"
+func TestHybiFirefoxHandshake(t *testing.T) {
+ config := new(Config)
+ handshaker := &hybiServerHandshaker{Config: config}
+ br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: keep-alive, upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 13
+
+`))
+ req, err := http.ReadRequest(br)
+ if err != nil {
+ t.Fatal("request", err)
+ }
+ code, err := handshaker.ReadHandshake(br, req)
+ if err != nil {
+ t.Errorf("handshake failed: %v", err)
+ }
+ if code != http.StatusSwitchingProtocols {
+ t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+ }
+ b := bytes.NewBuffer([]byte{})
+ bw := bufio.NewWriter(b)
+
+ config.Protocol = []string{"chat"}
+
+ err = handshaker.AcceptHandshake(bw)
+ if err != nil {
+ t.Errorf("handshake response failed: %v", err)
+ }
+ expectedResponse := strings.Join([]string{
+ "HTTP/1.1 101 Switching Protocols",
+ "Upgrade: websocket",
+ "Connection: Upgrade",
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+ "Sec-WebSocket-Protocol: chat",
+ "", ""}, "\r\n")
+
+ if b.String() != expectedResponse {
+ t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+ }
+}
+
func TestHybiClientHandshake(t *testing.T) {
b := bytes.NewBuffer([]byte{})
bw := bufio.NewWriter(b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment