diff --git a/.travis.yml b/.travis.yml
index 00dd9c9..ea5faa0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,6 @@ os:
- linux
- osx
julia:
- - 0.5
- 0.6
sudo: false
notifications:
diff --git a/appveyor.yml b/appveyor.yml
index aa55302..75c64a0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,9 +1,7 @@
environment:
matrix:
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
- - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
- - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe"
# HttpCommon not building yet - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
# HttpCommon not building yet - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
branches:
diff --git a/examples/chat-client.html b/examples/chat-client.html
index 5ae1c61..f1847f8 100644
--- a/examples/chat-client.html
+++ b/examples/chat-client.html
@@ -65,6 +65,14 @@
Select a userinput
var $chatForm = document.querySelector("#say_message");
var $chatInput = $chatForm.querySelector("input[name=say]");
+
+ function uuid4() {
+ return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ )
+ }
+ var id = uuid4();
+
function addContent(html) {
var $content = document.querySelector("#content");
var div = document.createElement("div");
@@ -86,7 +94,11 @@ Select a userinput
if( !uname.replace(/\s/gi,'').length ) {
alert("Please select a valid userinput");
} else {
- connection.send('setusername:'+ uname);
+ var msg = {
+ "id": id,
+ "userName": uname
+ };
+ connection.send(JSON.stringify(msg));
$userName.innerHTML = uname;
$welcome.style.display = "none";
$chat.style.display = "block";
@@ -94,12 +106,16 @@ Select a userinput
}
function whenChatMessage() {
- var msg = $chatInput.value;
- if(!msg.replace(/\s/gi,'').length) {
+ var content = $chatInput.value;
+ if( !content.replace(/\s/gi,'').length) {
/* nothing to do */
} else {
- connection.send('say:'+ msg);
- addContent(`${you}: ${msg}
`);
+ var msg = {
+ "id": id,
+ "say": content
+ };
+ connection.send(JSON.stringify(msg));
+ addContent(`${you}: ${content}
`);
$chatInput.focus();
}
}
@@ -110,19 +126,14 @@ Select a userinput
whenChatMessage();
return false;
})
-
- $chatInput.addEventListener("keypress", (e) => {
- if( e.keyCode === 13 ) { whenChatMessage(); } ;
- return false;
- }, false)
-
+
$userForm.addEventListener("submit", function(e){
e.preventDefault();
e.stopImmediatePropagation();
whenUserName();
return false;
});
-
+
const connection = new SocketConnection(onMessageReceived);
connection.start();
diff --git a/examples/chat.jl b/examples/chat.jl
index 8983e4d..dfaa307 100644
--- a/examples/chat.jl
+++ b/examples/chat.jl
@@ -1,38 +1,44 @@
using HttpServer
using WebSockets
+using JSON
+struct User
+ name::String
+ client::WebSocket
+end
#global Dict to store open connections in
-global connections = Dict{Int,WebSocket}()
-global usernames = Dict{Int,String}()
+global connections = Dict{String,User}()
function decodeMessage( msg )
- String(copy(msg))
+ JSON.parse(String(copy(msg)))
end
wsh = WebSocketHandler() do req, client
global connections
- @show connections[client.id] = client
while true
msg = read(client)
msg = decodeMessage(msg)
- if startswith(msg, "setusername:")
- println("SETTING USERNAME: $msg")
- usernames[client.id] = msg[13:end]
+ id = msg["id"]
+ if haskey(msg,"userName") && !haskey(connections,id)
+ uname = msg["userName"]
+ println("SETTING USERNAME: $(uname)")
+ connections[id] = User(uname,client)
end
- if startswith(msg, "say:")
- println("EMITTING MESSAGE: $msg")
+ if haskey(msg,"say")
+ content = msg["say"]
+ println("EMITTING MESSAGE: $(content)")
for (k,v) in connections
- if k != client.id
- write(v, (usernames[client.id] * ": " * msg[5:end]))
+ if k != id
+ write(v.client, (v.name * ": " * content))
end
end
end
end
end
-onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
httph = HttpHandler() do req::Request, res::Response
- Response(onepage)
+ onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
+ Response(onepage)
end
server = Server(httph, wsh)
diff --git a/examples/server.jl b/examples/server.jl
index 0cf883b..78ac7da 100644
--- a/examples/server.jl
+++ b/examples/server.jl
@@ -1,10 +1,6 @@
using HttpServer
using WebSockets
-#global Dict to store open connections in
-global connections = Dict{Int,WebSocket}()
-global usernames = Dict{Int,String}()
-
function decodeMessage( msg )
String(copy(msg))
end
@@ -19,8 +15,6 @@ function eval_or_describe_error(strmsg)
end
wsh = WebSocketHandler() do req, client
- global connections
- connections[client.id] = client
while true
val = client |> read |> decodeMessage |> eval_or_describe_error
output = String(take!(Base.mystreamvar))
diff --git a/src/HTTP.jl b/src/HTTP.jl
new file mode 100644
index 0000000..f713c42
--- /dev/null
+++ b/src/HTTP.jl
@@ -0,0 +1,76 @@
+info("Loading HTTP methods...")
+
+function open(f::Function, url; binary=false, verbose=false, kw...)
+
+ key = base64encode(rand(UInt8, 16))
+
+ headers = [
+ "Upgrade" => "websocket",
+ "Connection" => "Upgrade",
+ "Sec-WebSocket-Key" => key,
+ "Sec-WebSocket-Version" => "13"
+ ]
+
+ HTTP.open("GET", url, headers;
+ reuse_limit=0, verbose=verbose ? 2 : 0, kw...) do http
+
+ HTTP.startread(http)
+
+ status = http.message.status
+ if status != 101
+ return
+ end
+
+ check_upgrade(http)
+
+ if HTTP.header(http, "Sec-WebSocket-Accept") != generate_websocket_key(key)
+ throw(WebSocketError(0, "Invalid Sec-WebSocket-Accept\n" *
+ "$(http.message)"))
+ end
+
+ io = HTTP.ConnectionPool.getrawstream(http)
+ f(WebSocket(io,false))
+ end
+end
+
+function upgrade(f::Function, http::HTTP.Stream; binary=false)
+
+ check_upgrade(http)
+ if !HTTP.hasheader(http, "Sec-WebSocket-Version", "13")
+ throw(WebSocketError(0, "Expected \"Sec-WebSocket-Version: 13\"!\n$(http.message)"))
+ end
+
+ HTTP.setstatus(http, 101)
+ HTTP.setheader(http, "Upgrade" => "websocket")
+ HTTP.setheader(http, "Connection" => "Upgrade")
+ key = HTTP.header(http, "Sec-WebSocket-Key")
+ HTTP.setheader(http, "Sec-WebSocket-Accept" => generate_websocket_key(key))
+
+ HTTP.startwrite(http)
+
+ io = HTTP.ConnectionPool.getrawstream(http)
+ f(WebSocket(io, true))
+end
+
+function check_upgrade(http)
+ if !HTTP.hasheader(http, "Upgrade", "websocket")
+ throw(WebSocketError(0, "Expected \"Upgrade: websocket\"!\n$(http.message)"))
+ end
+
+ if !HTTP.hasheader(http, "Connection", "upgrade")
+ throw(WebSocketError(0, "Expected \"Connection: upgrade\"!\n$(http.message)"))
+ end
+end
+
+function is_upgrade(r::HTTP.Message)
+ (r isa HTTP.Request && r.method == "GET" || r.status == 101) &&
+ HTTP.hasheader(r, "Connection", "upgrade") &&
+ HTTP.hasheader(r, "Upgrade", "websocket")
+end
+
+# function listen(f::Function, host::String="localhost", port::UInt16=UInt16(8081); binary=false, verbose=false)
+# HTTP.listen(host, port; verbose=verbose) do http
+# upgrade(f, http; binary=binary)
+# end
+# end
+
diff --git a/src/HttpServer.jl b/src/HttpServer.jl
new file mode 100644
index 0000000..6d8523f
--- /dev/null
+++ b/src/HttpServer.jl
@@ -0,0 +1,74 @@
+info("Loading HttpServer methods...")
+
+export WebSocketHandler
+
+
+"""
+Responds to a WebSocket handshake request.
+Checks for required headers and subprotocols; sends Response(400) if they're missing or bad. Otherwise, transforms client key into accept value, and sends Reponse(101).
+Function returns true for accepted handshakes.
+"""
+function websocket_handshake(request,client)
+ if !haskey(request.headers, "Sec-WebSocket-Key")
+ Base.write(client.sock, HttpServer.Response(400))
+ return false
+ end
+ if get(request.headers, "Sec-WebSocket-Version", "13") != "13"
+ response = HttpServer.Response(400)
+ response.headers["Sec-WebSocket-Version"] = "13"
+ Base.write(client.sock, response)
+ return false
+ end
+
+ key = request.headers["Sec-WebSocket-Key"]
+ if length(base64decode(key)) != 16 # Key must be 16 bytes
+ Base.write(client.sock, HttpServer.Response(400))
+ return false
+ end
+ resp_key = generate_websocket_key(key)
+
+ response = HttpServer.Response(101)
+ response.headers["Upgrade"] = "websocket"
+ response.headers["Connection"] = "Upgrade"
+ response.headers["Sec-WebSocket-Accept"] = resp_key
+
+ if haskey(request.headers, "Sec-WebSocket-Protocol")
+ if hasprotocol(request.headers["Sec-WebSocket-Protocol"])
+ response.headers["Sec-WebSocket-Protocol"] = request.headers["Sec-WebSocket-Protocol"]
+ else
+ Base.write(client.sock, HttpServer.Response(400))
+ return false
+ end
+ end
+
+ Base.write(client.sock, response)
+ return true
+end
+
+""" Implement the WebSocketInterface, for compatilibility with HttpServer."""
+struct WebSocketHandler <: HttpServer.WebSocketInterface
+ handle::Function
+end
+
+"""
+Performs handshake. If successfull, establishes WebSocket type and calls
+handler with the WebSocket and the original request. On exit from handler, closes websocket. No return value.
+"""
+function HttpServer.handle(handler::WebSocketHandler, req::HttpServer.Request, client::HttpServer.Client)
+ websocket_handshake(req, client) || return
+ sock = WebSocket(client.sock,true)
+ handler.handle(req, sock)
+ if isopen(sock)
+ try
+ close(sock)
+ end
+ end
+end
+
+function HttpServer.is_websocket_handshake(handler::WebSocketHandler, req::HttpServer.Request)
+ is_get = req.method == "GET"
+ # "upgrade" for Chrome and "keep-alive, upgrade" for Firefox.
+ is_upgrade = contains(lowercase(get(req.headers, "Connection", "")),"upgrade")
+ is_websockets = lowercase(get(req.headers, "Upgrade", "")) == "websocket"
+ return is_get && is_upgrade && is_websockets
+end
\ No newline at end of file
diff --git a/src/WebSockets.jl b/src/WebSockets.jl
index 95ac3f1..bb90be3 100644
--- a/src/WebSockets.jl
+++ b/src/WebSockets.jl
@@ -20,9 +20,8 @@ an http server.
"""
module WebSockets
-using HTTP
-import HTTP: digest, MD_SHA1
-
+import MbedTLS: digest, MD_SHA1
+using Requires
export WebSocket,
write,
@@ -410,48 +409,6 @@ function generate_websocket_key(key)
return base64encode(digest(MD_SHA1, hashkey))
end
-# """
-# Responds to a WebSocket handshake request.
-# Checks for required headers and subprotocols; sends Response(400) if they're missing or bad. Otherwise, transforms client key into accept value, and sends Reponse(101).
-# Function returns true for accepted handshakes.
-# """
-# function websocket_handshake(request,client)
-# if !haskey(request.headers, "Sec-WebSocket-Key")
-# Base.write(client.sock, Response(400))
-# return false
-# end
-# if get(request.headers, "Sec-WebSocket-Version", "13") != "13"
-# response = Response(400)
-# response.headers["Sec-WebSocket-Version"] = "13"
-# Base.write(client.sock, response)
-# return false
-# end
-
-# key = request.headers["Sec-WebSocket-Key"]
-# if length(decode(Base64,key)) != 16 # Key must be 16 bytes
-# Base.write(client.sock, Response(400))
-# return false
-# end
-# resp_key = generate_websocket_key(key)
-
-# response = Response(101)
-# response.headers["Upgrade"] = "websocket"
-# response.headers["Connection"] = "Upgrade"
-# response.headers["Sec-WebSocket-Accept"] = resp_key
-
-# if haskey(request.headers, "Sec-WebSocket-Protocol")
-# if hasprotocol(request.headers["Sec-WebSocket-Protocol"])
-# response.headers["Sec-WebSocket-Protocol"] = request.headers["Sec-WebSocket-Protocol"]
-# else
-# Base.write(client.sock, Response(400))
-# return false
-# end
-# end
-
-# Base.write(client.sock, response)
-# return true
-# end
-
function mask!(data, mask=rand(UInt8, 4))
for i in 1:length(data)
data[i] = data[i] ⊻ mask[((i-1) % 4)+1]
@@ -459,117 +416,8 @@ function mask!(data, mask=rand(UInt8, 4))
return mask
end
-function open(f::Function, url; binary=false, verbose=false, kw...)
-
- key = HTTP.base64encode(rand(UInt8, 16))
-
- headers = [
- "Upgrade" => "websocket",
- "Connection" => "Upgrade",
- "Sec-WebSocket-Key" => key,
- "Sec-WebSocket-Version" => "13"
- ]
-
- HTTP.open("GET", url, headers;
- reuse_limit=0, verbose=verbose ? 2 : 0, kw...) do http
-
- HTTP.startread(http)
-
- status = http.message.status
- if status != 101
- return
- end
-
- check_upgrade(http)
-
- if HTTP.header(http, "Sec-WebSocket-Accept") != generate_websocket_key(key)
- throw(WebSocketError(0, "Invalid Sec-WebSocket-Accept\n" *
- "$(http.message)"))
- end
-
- io = HTTP.ConnectionPool.getrawstream(http)
- f(WebSocket(io,false))
- end
-end
-
-# mutable struct WebSocket
-# id::Int
-# socket::TCPSock
-# state::ReadyState
-
-# function WebSocket(id::Int,socket::TCPSock)
-# init_socket(socket)
-# new(id, socket, CONNECTED)
-# end
-# end
-
-# function listen(f::Function, host::String="localhost", port::UInt16=UInt16(8081); binary=false, verbose=false)
-# HTTP.listen(host, port; verbose=verbose) do http
-# upgrade(f, http; binary=binary)
-# end
-# end
-
-function upgrade(f::Function, http::HTTP.Stream; binary=false)
-
- check_upgrade(http)
- if !HTTP.hasheader(http, "Sec-WebSocket-Version", "13")
- throw(WebSocketError(0, "Expected \"Sec-WebSocket-Version: 13\"!\n$(http.message)"))
- end
-
- HTTP.setstatus(http, 101)
- HTTP.setheader(http, "Upgrade" => "websocket")
- HTTP.setheader(http, "Connection" => "Upgrade")
- key = HTTP.header(http, "Sec-WebSocket-Key")
- HTTP.setheader(http, "Sec-WebSocket-Accept" => generate_websocket_key(key))
-
- HTTP.startwrite(http)
-
- io = HTTP.ConnectionPool.getrawstream(http)
- f(WebSocket(io, true))
-end
-
-function check_upgrade(http)
- if !HTTP.hasheader(http, "Upgrade", "websocket")
- throw(WebSocketError(0, "Expected \"Upgrade: websocket\"!\n$(http.message)"))
- end
-
- if !HTTP.hasheader(http, "Connection", "upgrade")
- throw(WebSocketError(0, "Expected \"Connection: upgrade\"!\n$(http.message)"))
- end
-end
-
-function is_upgrade(r::HTTP.Message)
- (r isa HTTP.Request && r.method == "GET" || r.status == 101) &&
- HTTP.hasheader(r, "Connection", "upgrade") &&
- HTTP.hasheader(r, "Upgrade", "websocket")
-end
+@require HTTP include("HTTP.jl")
-# """ Implement the WebSocketInterface, for compatilibility with HttpServer."""
-# struct WebSocketHandler <: HttpServer.WebSocketInterface
-# handle::Function
-# end
-
-# import HttpServer: handle, is_websocket_handshake
-# """
-# Performs handshake. If successfull, establishes WebSocket type and calls
-# handler with the WebSocket and the original request. On exit from handler, closes websocket. No return value.
-# """
-# function handle(handler::WebSocketHandler, req::Request, client::HttpServer.Client)
-# websocket_handshake(req, client) || return
-# sock = WebSocket(client.id, client.sock)
-# handler.handle(req, sock)
-# if isopen(sock)
-# try
-# close(sock)
-# end
-# end
-# end
-# function is_websocket_handshake(handler::WebSocketHandler, req::Request)
-# is_get = req.method == "GET"
-# # "upgrade" for Chrome and "keep-alive, upgrade" for Firefox.
-# is_upgrade = contains(lowercase(get(req.headers, "Connection", "")),"upgrade")
-# is_websockets = lowercase(get(req.headers, "Upgrade", "")) == "websocket"
-# return is_get && is_upgrade && is_websockets
-# end
+@require HttpServer include("HttpServer.jl")
end # module WebSockets
diff --git a/test/HTTP.jl b/test/HTTP.jl
new file mode 100644
index 0000000..25d7b36
--- /dev/null
+++ b/test/HTTP.jl
@@ -0,0 +1,53 @@
+@testset "HTTP" begin
+
+using HTTP
+using WebSockets
+# using HTTP.IOExtras
+using Base.Test
+
+import WebSockets: CONNECTED, CLOSING, CLOSED
+
+info("Testing ws...")
+WebSockets.open("ws://echo.websocket.org") do ws
+ write(ws, "Foo")
+ @test String(read(ws)) == "Foo"
+
+ close(ws)
+end
+sleep(1)
+
+info("Testing wss...")
+WebSockets.open("wss://echo.websocket.org") do ws
+ write(ws, "Foo")
+ @test String(read(ws)) == "Foo"
+
+ close(ws)
+end
+sleep(1)
+
+p = UInt16(8000)
+@async HTTP.listen("127.0.0.1",p) do http
+ if WebSockets.is_upgrade(http.message)
+ WebSockets.upgrade(http) do ws
+ while ws.state == CONNECTED
+ data = String(read(ws))
+ write(ws,data)
+ end
+ end
+ end
+end
+
+sleep(2)
+
+info("Testing local server...")
+WebSockets.open("ws://127.0.0.1:$(p)") do ws
+ write(ws, "Foo")
+ @test String(read(ws)) == "Foo"
+
+ write(ws, "Bar")
+ @test String(read(ws)) == "Bar"
+
+ close(ws)
+end
+
+end # testset
\ No newline at end of file
diff --git a/test/HttpServer.jl b/test/HttpServer.jl
new file mode 100644
index 0000000..7a7351a
--- /dev/null
+++ b/test/HttpServer.jl
@@ -0,0 +1,211 @@
+@testset "HttpServer" begin
+
+using HttpServer
+using WebSockets
+import WebSockets: generate_websocket_key,
+ write_fragment,
+ read_frame,
+ websocket_handshake
+ # is_websocket_handshake,
+ # handle
+import HttpCommon: Request, Response
+
+
+@sync yield() # avoid mixing of output with possible deprecation warnings from .juliarc
+info("Starting test WebSockets...")
+#is_control_frame is one line, checking one bit.
+#get_websocket_key grabs a header.
+#is_websocket_handshake grabs a header.
+#generate_websocket_key makes a call to a library.
+info("Test generate_websocket_key")
+@test generate_websocket_key("dGhlIHNhbXBsZSBub25jZQ==") == "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
+
+# Test writing
+
+function xor_payload(maskkey, data)
+ out = Array{UInt8,1}(length(data))
+ for i in 1:length(data)
+ d = data[i]
+ d = xor(d , maskkey[mod(i - 1, 4) + 1])
+ out[i] = d
+ end
+ out
+end
+
+const io = IOBuffer()
+
+info("Test length less than 126")
+for len = [8, 125], op = (rand(UInt8) & 0b1111), fin=[true, false]
+
+ test_str = randstring(len)
+ write_fragment(io, fin, op, false, Vector{UInt8}(test_str))
+
+ frame = take!(io)
+
+ @test bits(frame[1]) == (fin ? "1" : "0") * "000" * bits(op)[end-3:end]
+ @test frame[2] == UInt8(len)
+ @test String(frame[3:end]) == test_str
+
+ # The new WebSockets can handle both client and server so can handle no masks
+ # ===========================================================================
+ # # Check to see if reading message without a mask fails
+ # in_buf = IOBuffer(String(frame))
+ # @test_throws ErrorException read_frame(in_buf)
+ # close(in_buf)
+
+ # add a mask
+ maskkey = rand(UInt8, 4)
+ data = vcat(
+ frame[1],
+ frame[2] | 0b1000_0000,
+ maskkey,
+ xor_payload(maskkey, frame[3:end])
+ )
+ frame_back = read_frame(IOBuffer(data))
+
+ @test frame_back.is_last == fin
+ @test frame_back.rsv1 == false
+ @test frame_back.rsv2 == false
+ @test frame_back.rsv3 == false
+ @test frame_back.opcode == op
+ @test frame_back.is_masked == true
+ @test frame_back.payload_len == len
+ @test all(map(==, frame_back.maskkey, maskkey))
+ @test test_str == String(frame_back.data)
+end
+
+info("Test length 126 or more")
+for len = 126:129, op = 0b1111, fin=[true, false]
+
+ test_str = randstring(len)
+ write_fragment(io, fin, op, false, Vector{UInt8}(test_str))
+
+ frame = take!(io)
+
+ @test bits(frame[1]) == (fin ? "1" : "0") * "000" * bits(op)[end-3:end]
+ @test frame[2] == 126
+
+ @test bits(frame[4])*bits(frame[3]) == bits(hton(UInt16(len)))
+
+ # add a mask
+ maskkey = rand(UInt8, 4)
+ data = vcat(
+ frame[1],
+ frame[2] | 0b1000_0000,
+ frame[3],
+ frame[4],
+ maskkey,
+ xor_payload(maskkey, frame[5:end])
+ )
+ frame_back = read_frame(IOBuffer(data))
+
+ @test frame_back.is_last == fin
+ @test frame_back.rsv1 == false
+ @test frame_back.rsv2 == false
+ @test frame_back.rsv3 == false
+ @test frame_back.opcode == op
+ @test frame_back.is_masked == true
+ @test frame_back.payload_len == len
+ @test all(map(==, frame_back.maskkey, maskkey))
+ @test test_str == String(frame_back.data)
+end
+
+# TODO: test for length > typemax(Uint32)
+
+info("Tests for is_websocket_handshake")
+chromeheaders = Dict{String, String}(
+ "Connection"=>"Upgrade",
+ "Upgrade"=>"websocket"
+ )
+chromerequest = HttpCommon.Request(
+ "GET",
+ "",
+ chromeheaders,
+ ""
+ )
+
+firefoxheaders = Dict{String, String}(
+ "Connection"=>"keep-alive, Upgrade",
+ "Upgrade"=>"websocket"
+ )
+
+firefoxrequest= Request(
+ "GET",
+ "",
+ firefoxheaders,
+ ""
+ )
+
+wshandler = WebSocketHandler((x,y)->nothing);#Dummy wshandler
+
+for request in [chromerequest, firefoxrequest]
+ @test HttpServer.is_websocket_handshake(wshandler,request) == true
+end
+
+info("Test of handshake response")
+takefirstline(buf) = split(buf |> take! |> String, "\r\n")[1]
+
+take!(io)
+Base.write(io, "test")
+@test takefirstline(io) == "test"
+
+info("Test reject / switch format")
+const SWITCH = "HTTP/1.1 101 Switching Protocols "
+const REJECT = "HTTP/1.1 400 Bad Request "
+Base.write(io, Response(400))
+@test takefirstline(io) == REJECT
+Base.write(io, Response(101))
+@test takefirstline(io) == SWITCH
+
+function handshakeresponse(request)
+ cli = HttpServer.Client(2, IOBuffer())
+ websocket_handshake(request, cli)
+ takefirstline(cli.sock)
+end
+
+info("Test simple handshakes that are unacceptable")
+for request in [chromerequest, firefoxrequest]
+ @test handshakeresponse(request) == REJECT
+ push!(request.headers, "Sec-WebSocket-Version" => "13")
+ @test handshakeresponse(request) == REJECT
+ push!(request.headers, "Sec-WebSocket-Key" => "mumbojumbobo")
+ @test handshakeresponse(request) == REJECT
+ push!(request.headers, "Sec-WebSocket-Version" => "11")
+ push!(request.headers, "Sec-WebSocket-Key" => "zkG1WqHM8BJdQMXytFqiUw==")
+ @test handshakeresponse(request) == REJECT
+end
+
+info("Test simple handshakes, acceptable")
+for request in [chromerequest, firefoxrequest]
+ push!(request.headers, "Sec-WebSocket-Version" => "13")
+ push!(request.headers, "Sec-WebSocket-Key" => "zkG1WqHM8BJdQMXytFqiUw==")
+ @test handshakeresponse(request) == SWITCH
+end
+
+info("Test unacceptable subprotocol handshake subprotocol")
+for request in [chromerequest, firefoxrequest]
+ push!(request.headers, "Sec-WebSocket-Version" => "13")
+ push!(request.headers, "Sec-WebSocket-Key" => "zkG1WqHM8BJdQMXytFqiUw==")
+ push!(request.headers, "Sec-WebSocket-Protocol" => "my.server/json-zmq")
+ @test handshakeresponse(request) == REJECT
+end
+
+info("add simple subprotocol to acceptable list")
+@test true == WebSockets.addsubproto("xml")
+
+info("add subprotocol with difficult name")
+@test true == WebSockets.addsubproto("my.server/json-zmq")
+
+info("Test handshake subprotocol now acceptable")
+for request in [chromerequest, firefoxrequest]
+ push!(request.headers, "Sec-WebSocket-Version" => "13")
+ push!(request.headers, "Sec-WebSocket-Key" => "zkG1WqHM8BJdQMXytFqiUw==")
+ push!(request.headers, "Sec-WebSocket-Protocol" => "xml")
+ @test handshakeresponse(request) == SWITCH
+ push!(request.headers, "Sec-WebSocket-Protocol" => "my.server/json-zmq")
+ @test handshakeresponse(request) == SWITCH
+end
+close(io)
+include("browsertest.jl")
+
+end
\ No newline at end of file
diff --git a/test/browsertest.html b/test/browsertest.html
index 63216ff..941031a 100644
--- a/test/browsertest.html
+++ b/test/browsertest.html
@@ -24,6 +24,14 @@
ws3 Websocket: server_denies_protocol