diff --git a/README.md b/README.md
index 2c1f258..eac28ac 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ WebSockets.jl
[![Build Status](https://travis-ci.org/JuliaWeb/WebSockets.jl.png)](https://travis-ci.org/JuliaWeb/WebSockets.jl)
[![Coverage Status](https://img.shields.io/coveralls/JuliaWeb/WebSockets.jl.svg)](https://coveralls.io/r/JuliaWeb/WebSockets.jl)
-[![WebSockets](http://pkg.julialang.org/badges/WebSockets_0.3.svg)](http://pkg.julialang.org/?pkg=WebSockets&ver=0.3)
-[![WebSockets](http://pkg.julialang.org/badges/WebSockets_0.4.svg)](http://pkg.julialang.org/?pkg=WebSockets&ver=0.4)
+[![WebSockets](http://pkg.julialang.org/badges/WebSockets_0.5.svg)](http://pkg.julialang.org/?pkg=WebSockets&ver=0.5)
+[![WebSockets](http://pkg.julialang.org/badges/WebSockets_0.6.svg)](http://pkg.julialang.org/?pkg=WebSockets&ver=0.6)
This is a server-side implementation of the WebSockets protocol in Julia. If you want to write a web app in Julia that uses WebSockets, you'll need this package.
diff --git a/REQUIRE b/REQUIRE
index b627762..1c234eb 100644
--- a/REQUIRE
+++ b/REQUIRE
@@ -1,5 +1,5 @@
julia 0.4
-Compat 0.7.16
+Compat 0.9.5
HttpCommon 0.0.3
HttpServer 0.0.4
Codecs
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..f4d2a99
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,36 @@
+environment:
+ matrix:
+ - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe"
+ - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe"
+ - JULIAVERSION: "julialang/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe"
+ - JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe"
+ - JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe"
+ - JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe"
+
+branches:
+ only:
+ - master
+ - /release-.*/
+
+notifications:
+ - provider: Email
+ on_build_success: false
+ on_build_failure: false
+ on_build_status_changed: false
+
+install:
+# Download most recent Julia Windows binary
+ - ps: (new-object net.webclient).DownloadFile(
+ $("http://s3.amazonaws.com/"+$env:JULIAVERSION),
+ "C:\projects\julia-binary.exe")
+# Run installer silently, output to C:\projects\julia
+ - C:\projects\julia-binary.exe /S /D=C:\projects\julia
+
+build_script:
+# Need to convert from shallow to complete for Pkg.clone to work
+ - IF EXIST .git\shallow (git fetch --unshallow)
+ - C:\projects\julia\bin\julia -e "versioninfo();
+ Pkg.clone(pwd(), \"WebSockets\"); Pkg.build(\"WebSockets\")"
+
+test_script:
+ - C:\projects\julia\bin\julia -e "Pkg.test(\"WebSockets\")"
diff --git a/examples/chat-client.html b/examples/chat-client.html
index c9f10a1..0740411 100644
--- a/examples/chat-client.html
+++ b/examples/chat-client.html
@@ -33,7 +33,7 @@
Select a username
var you = "you";
connection.onmessage = function( message ){
window.lastmessage = message;
- $("#content").prepend( $("").html( message.data ) );
+ $("#content").append( $("").html( message.data ) );
}
function changeUsername( username ){
you = username;
@@ -42,7 +42,7 @@ Select a username
}
function sendMessage( message ){
connection.send('say:'+ message);
- $("#content").prepend( $("").html( you + ": " + message ) );
+ $("#content").append( $("").html( you + ": " + message ) );
}
$("#sayer input[type=submit]").click(function(){
if( $("#sayer input[name=say]").val().replace(/\s/gi,'').length )
diff --git a/examples/chat.jl b/examples/chat.jl
index 05b5ca9..8983e4d 100644
--- a/examples/chat.jl
+++ b/examples/chat.jl
@@ -6,7 +6,7 @@ global connections = Dict{Int,WebSocket}()
global usernames = Dict{Int,String}()
function decodeMessage( msg )
- bytestring(msg)
+ String(copy(msg))
end
wsh = WebSocketHandler() do req, client
@@ -30,7 +30,7 @@ wsh = WebSocketHandler() do req, client
end
end
-onepage = readall(Pkg.dir("Websockets","examples","chat-client.html"))
+onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
httph = HttpHandler() do req::Request, res::Response
Response(onepage)
end
diff --git a/examples/repl-client.html b/examples/repl-client.html
index 0dcab5b..aad5b83 100644
--- a/examples/repl-client.html
+++ b/examples/repl-client.html
@@ -26,12 +26,20 @@
var you = "you";
connection.onmessage = function( message ){
window.lastmessage = message;
- $("#content").prepend( $("").html( message.data ) );
+ $("#content").append( $("").html( message.data ) );
}
function sendMessage( message ){
- connection.send(message);
- $("#content").prepend( $("").html( you + ": " + message ) );
- }
+ if(typeof connection === 'undefined' || connection === null){
+ $("#content").append( $("Connection not defined
"));
+ } else {
+ if(connection.readyState==1){
+ connection.send(message);
+ $("#content").append( $("").html( you + ": " + message ) );
+ } else {
+ $("#content").append( $("Connection not ready
"));
+ };
+ };
+ };
$("#sayer input[type=submit]").click(function(){
if( $("#sayer input[name=say]").val().replace(/\s/gi,'').length )
sendMessage( $("#sayer input[name=say]").val() );
diff --git a/examples/server.jl b/examples/server.jl
index 3a7cebe..0cf883b 100644
--- a/examples/server.jl
+++ b/examples/server.jl
@@ -6,29 +6,36 @@ global connections = Dict{Int,WebSocket}()
global usernames = Dict{Int,String}()
function decodeMessage( msg )
- bytestring(msg)
+ String(copy(msg))
end
-
+function eval_or_describe_error(strmsg)
+ try
+ return eval(parse(strmsg, raise = false))
+ catch err
+ iob = IOBuffer()
+ dump(iob, err)
+ return String(take!(iob))
+ end
+end
+
wsh = WebSocketHandler() do req, client
global connections
connections[client.id] = client
while true
- msg = read(client)
- msg = decodeMessage(msg)
- val = eval(parse(msg))
- output = takebuf_string(Base.mystreamvar)
+ val = client |> read |> decodeMessage |> eval_or_describe_error
+ output = String(take!(Base.mystreamvar))
val = val == nothing ? "
" : val
write(client,"$val
$output")
end
end
-onepage = readall(Pkg.dir("WebSockets","examples","repl-client.html"))
+onepage = readstring(Pkg.dir("WebSockets","examples","repl-client.html"))
httph = HttpHandler() do req::Request, res::Response
Response(onepage)
end
server = Server(httph, wsh)
-println("Chat server listening on 8080...")
+println("Repl server listening on 8080...")
eval(Base,parse("mystreamvar = IOBuffer()"))
eval(Base,parse("STDOUT = mystreamvar"))
diff --git a/src/WebSockets.jl b/src/WebSockets.jl
index 596f62a..eb60fef 100644
--- a/src/WebSockets.jl
+++ b/src/WebSockets.jl
@@ -1,22 +1,25 @@
+__precompile__()
+"""
+ WebSockets
+This module implements the server side of the WebSockets protocol. Some
+things would need to be added to implement a WebSockets client, such as
+masking of sent frames.
+
+WebSockets expects to be used with HttpServer to provide the HttpServer
+for accepting the HTTP request that begins the opening handshake. WebSockets
+implements a subtype of the WebSocketInterface from HttpServer; this means
+that you can create a WebSocketsHandler and pass it into the constructor for
+an http server.
+
+ Future improvements:
+1. Logging of refused requests and closures due to bad behavior of client.
+2. Better error handling (should we always be using "error"?)
+3. Unit tests with an actual client -- to automatically test the examples.
+4. Send close messages with status codes.
+5. Allow users to receive control messages if they want to.
+"""
module WebSockets
-# This module implements the server side of the WebSockets protocol. Some
-# things would need to be added to implement a WebSockets client, such as
-# masking of sent frames.
-#
-# WebSockets expects to be used with HttpServer to provide the HttpServer
-# for accepting the HTTP request that begins the opening handshake. WebSockets
-# implements a subtype of the WebSocketInterface from HttpServer; this means
-# that you can create a WebSocketsHandler and pass it into the constructor for
-# an http server.
-#
-# Future improvements:
-# 1. Logging of refused requests and closures due to bad behavior of client.
-# 2. Better error handling (should we always be using "error"?)
-# 3. Unit tests with an actual client -- to automatically test the examples.
-# 4. Send close messages with status codes.
-# 5. Allow users to receive control messages if they want to.
-
using HttpCommon
using HttpServer
using Codecs
@@ -31,28 +34,29 @@ export WebSocket,
send_ping,
send_pong
-const TCPSock = VERSION < v"0.4.0-dev" ? Base.TcpSocket : Base.TCPSocket
-if VERSION < v"0.4.0-dev"
- init_socket(sock) = nothing
-else
- init_socket(sock) = Base.buffer_writes(sock) # Buffer writes to socket till flush(sock)
-end
+const TCPSock = Base.TCPSocket
+
+""" Buffer writes to socket till flush (sock)"""
+init_socket(sock) = Base.buffer_writes(sock)
+
type WebSocketClosedError <: Exception end
Base.showerror(io::IO, e::WebSocketClosedError) = print(io, "Error: client disconnected")
-# A WebSocket is a wrapper over a TcpSocket. It takes care of wrapping outgoing
-# data in a frame and unwrapping (and concatenating) incoming data.
+"""
+A WebSocket is a wrapper over a TcpSocket. It takes care of wrapping outgoing
+data in a frame and unwrapping (and concatenating) incoming data.
+"""
type WebSocket
- id::Int
- socket::TCPSock
- is_closed::Bool
- sent_close::Bool
-
- function WebSocket(id::Int,socket::TCPSock)
- init_socket(socket)
- new(id,socket, !isopen(socket), false)
- end
+ id::Int
+ socket::TCPSock
+ is_closed::Bool
+ sent_close::Bool
+
+ function WebSocket(id::Int,socket::TCPSock)
+ init_socket(socket)
+ new(id,socket, !isopen(socket), false)
+ end
end
# WebSocket Frames
@@ -79,110 +83,110 @@ end
# Opcode values
-# * %x0 denotes a continuation frame
+""" * %x0 denotes a continuation frame"""
const OPCODE_CONTINUATION = 0x00
-# * %x1 denotes a text frame
+""" * %x1 denotes a text frame"""
const OPCODE_TEXT = 0x1
-# * %x2 denotes a binary frame
+""" * %x2 denotes a binary frame"""
const OPCODE_BINARY = 0x2
# * %x3-7 are reserved for further non-control frames
#
-# * %x8 denotes a connection close
+""" * %x8 denotes a connection close"""
const OPCODE_CLOSE = 0x8
-# * %x9 denotes a ping
+""" * %x9 denotes a ping"""
const OPCODE_PING = 0x9
-# * %xA denotes a pong
+""" * %xA denotes a pong"""
const OPCODE_PONG = 0xA
-# * %xB-F are reserved for further control frames
+# * %xB-F are reserved for further control frames
-# Write the raw frame to a bufffer
+"""
+ write_fragment(io, islast, data::Array{UInt8}, opcode)
+Write the raw frame to a bufffer
+"""
function write_fragment(io::IO, islast::Bool, data::Array{UInt8}, opcode)
- l = length(data)
- b1::UInt8 = (islast ? 0b1000_0000 : 0b0000_0000) | opcode
-
- # TODO: Do the mask xor thing??
- # 1. set bit 8 to 1,
- # 2. set a mask
- # 3. xor data with mask
-
- if l <= 125
- write(io, b1)
- write(io, @compat UInt8(l))
- write(io, data)
- elseif l <= typemax(UInt16)
- write(io, b1)
- write(io, @compat UInt8(126))
- write(io, hton(@compat UInt16(l)))
- write(io, data)
- elseif l <= typemax(UInt64)
- write(io, b1)
- write(io, @compat UInt8(127))
- write(io, hton(@compat UInt64(l)))
- write(io, data)
- else
- error("Attempted to send too much data for one websocket fragment\n")
- end
+ l = length(data)
+ b1::UInt8 = (islast ? 0b1000_0000 : 0b0000_0000) | opcode
+
+ # TODO: Do the mask xor thing??
+ # 1. set bit 8 to 1,
+ # 2. set a mask
+ # 3. xor data with mask
+
+ if l <= 125
+ write(io, b1)
+ write(io, @compat UInt8(l))
+ write(io, data)
+ elseif l <= typemax(UInt16)
+ write(io, b1)
+ write(io, @compat UInt8(126))
+ write(io, hton(@compat UInt16(l)))
+ write(io, data)
+ elseif l <= typemax(UInt64)
+ write(io, b1)
+ write(io, @compat UInt8(127))
+ write(io, hton(@compat UInt64(l)))
+ write(io, data)
+ else
+ error("Attempted to send too much data for one websocket fragment\n")
+ end
end
-# A version of send_fragment for text data.
+"""
+ write_fragment(io, islast, data::String, opcode)
+A version of send_fragment for text data."""
function write_fragment(io::IO, islast::Bool, data::String, opcode)
- write_fragment(io, islast, data.data, opcode)
+ write_fragment(io, islast, data.data, opcode)
end
-if VERSION < v"0.4.0-dev"
- function locked_write(io::IO, islast::Bool, data, opcode)
- buf = IOBuffer()
- write_fragment(buf, islast, data, opcode)
- write(io, takebuf_array(buf))
- end
-else
- function locked_write(io::IO, islast::Bool, data, opcode)
+""" Write without interruptions"""
+function locked_write(io::IO, islast::Bool, data, opcode)
isa(io, TCPSock) && lock(io.lock)
try
- write_fragment(io, islast, data, opcode)
+ write_fragment(io, islast, Vector{UInt8}(data), opcode)
finally
- if isa(io, TCPSock)
- flush(io)
- unlock(io.lock)
- end
+ if isa(io, TCPSock)
+ flush(io)
+ unlock(io.lock)
+ end
end
- end
end
-
-# Write text data; will be sent as one frame.
+""" Write text data; will be sent as one frame."""
function Base.write(ws::WebSocket,data::String)
- if ws.is_closed
- @show ws
- error("Attempted write to closed WebSocket\n")
- end
- locked_write(ws.socket, true, data, OPCODE_TEXT)
+ if ws.is_closed
+ @show ws
+ error("Attempted write to closed WebSocket\n")
+ end
+ locked_write(ws.socket, true, data, OPCODE_TEXT)
end
-# Write binary data; will be sent as one frmae.
+""" Write binary data; will be sent as one frame."""
function Base.write(ws::WebSocket, data::Array{UInt8})
- if ws.is_closed
- @show ws
- error("attempt to write to closed WebSocket\n")
- end
- locked_write(ws.socket, true, data, OPCODE_BINARY)
+ if ws.is_closed
+ @show ws
+ error("attempt to write to closed WebSocket\n")
+ end
+ locked_write(ws.socket, true, data, OPCODE_BINARY)
end
-# Send a ping message, optionally with data.
+
function write_ping(io::IO, data = "")
- locked_write(io, true, data, OPCODE_PING)
+ locked_write(io, true, data, OPCODE_PING)
end
-
+""" Send a ping message, optionally with data."""
send_ping(ws, data...) = write_ping(ws.socket, data...)
-# Send a pong message, optionally with data.
+
function write_pong(io::IO, data = "")
- locked_write(io, true, data, OPCODE_PONG)
+ locked_write(io, true, data, OPCODE_PONG)
end
-
+""" Send a pong message, optionally with data."""
send_pong(ws, data...) = write_pong(ws.socket, data...)
-# Send a close message.
+"""
+ close(ws::WebSocket)
+Send a close message.
+"""
function Base.close(ws::WebSocket)
# Tell client to close connection
locked_write(ws.socket, true, "", OPCODE_CLOSE)
@@ -190,46 +194,48 @@ function Base.close(ws::WebSocket)
# Wait till client responds with an OPCODE_CLOSE
while true
- wsf = read_frame(ws.socket)
- # ALERT: stuff might get lost in ether here
- is_control_frame(wsf) || continue
- wsf.opcode == OPCODE_CLOSE || continue
- break
+ wsf = read_frame(ws.socket)
+ # ALERT: stuff might get lost in ether here
+ is_control_frame(wsf) || continue
+ wsf.opcode == OPCODE_CLOSE || continue
+ break
end
close(ws.socket)
end
-
-# A WebSocket is closed if the underlying TCP socket closes, or if we send or
-# receive a close message.
+"""
+ isopen(WebSocket)-> Bool
+A WebSocket is closed if the underlying TCP socket closes, or if we send or
+receive a close message.
+"""
Base.isopen(ws::WebSocket) = !ws.is_closed && isopen(ws.socket)
-# Represents one (received) message frame.
+""" Represents one (received) message frame."""
type WebSocketFragment
- is_last::Bool
- rsv1::Bool
- rsv2::Bool
- rsv3::Bool
- opcode::UInt8 # This is actually a UInt4 value.
- is_masked::Bool
- payload_len::UInt64
- maskkey::Vector{UInt8} # This will be 4 bytes on frames from the client.
- data::Vector{UInt8} # For text messages, this is a String.
+ is_last::Bool
+ rsv1::Bool
+ rsv2::Bool
+ rsv3::Bool
+ opcode::UInt8 # This is actually a UInt4 value.
+ is_masked::Bool
+ payload_len::UInt64
+ maskkey::Vector{UInt8} # This will be 4 bytes on frames from the client.
+ data::Vector{UInt8} # For text messages, this is a String.
end
-# This constructor handles conversions from bytes to bools.
+""" This constructor handles conversions from bytes to bools."""
function WebSocketFragment(
- fin::UInt8
- ,rsv1::UInt8
- ,rsv2::UInt8
- ,rsv3::UInt8
- ,opcode::UInt8
- ,masked::UInt8
- ,payload_len::UInt64
- ,maskkey::Vector{UInt8}
- ,data::Vector{UInt8})
-
- WebSocketFragment(
+ fin::UInt8
+ , rsv1::UInt8
+ , rsv2::UInt8
+ , rsv3::UInt8
+ , opcode::UInt8
+ , masked::UInt8
+ , payload_len::UInt64
+ , maskkey::Vector{UInt8}
+ , data::Vector{UInt8})
+
+ WebSocketFragment(
fin != 0
, rsv1 != 0
, rsv2 != 0
@@ -241,130 +247,134 @@ function WebSocketFragment(
, data)
end
-# Control frames have opcodes with the highest bit = 1.
+""" Control frames have opcodes with the highest bit = 1."""
is_control_frame(msg::WebSocketFragment) = (msg.opcode & 0b0000_1000) > 0
-# Respond to pings, ignore pongs, respond to close.
+""" Respond to pings, ignore pongs, respond to close."""
function handle_control_frame(ws::WebSocket,wsf::WebSocketFragment)
- if wsf.opcode == OPCODE_CLOSE
- # Reply with an empty CLOSE frame
- locked_write(ws.socket, true, "", OPCODE_CLOSE)
- ws.is_closed = true
- close(ws.socket)
- throw(WebSocketClosedError())
- elseif wsf.opcode == OPCODE_PING
- write_pong(ws.socket,wsf.data)
- elseif wsf.opcode == OPCODE_PONG
- # Nothing to do here; no reply is needed for a pong message.
- else # %xB-F are reserved for further control frames
- error("Unknown opcode $(wsf.opcode)")
- end
+
+ if wsf.opcode == OPCODE_CLOSE
+ # Reply with an empty CLOSE frame
+ locked_write(ws.socket, true, "", OPCODE_CLOSE)
+ ws.is_closed = true
+ close(ws.socket)
+ throw(WebSocketClosedError())
+ elseif wsf.opcode == OPCODE_PING
+ write_pong(ws.socket,wsf.data)
+ elseif wsf.opcode == OPCODE_PONG
+ # Nothing to do here; no reply is needed for a pong message.
+ else # %xB-F are reserved for further control frames
+ error("Unknown opcode $(wsf.opcode)")
+ end
end
-# Read a frame: turn bytes from the websocket into a WebSocketFragment.
+""" Read a frame: turn bytes from the websocket into a WebSocketFragment."""
function read_frame(io::IO)
- a = read(io,UInt8)
- fin = a & 0b1000_0000 >>> 7 # If fin, then is final fragment
- rsv1 = a & 0b0100_0000 # If not 0, fail.
- rsv2 = a & 0b0010_0000 # If not 0, fail.
- rsv3 = a & 0b0001_0000 # If not 0, fail.
- opcode = a & 0b0000_1111 # If not known code, fail.
- # TODO: add validation somewhere to ensure rsv, opcode, mask, etc are valid.
-
- b = read(io,UInt8)
- mask = b & 0b1000_0000 >>> 7 # If not 1, fail.
-
- if mask != 1
- error("WebSocket reader cannot handle incoming messages without mask. " *
- "See http://tools.ietf.org/html/rfc6455#section-5.3")
- end
-
- payload_len::UInt64 = b & 0b0111_1111
- if payload_len == 126
- payload_len = ntoh(read(io,UInt16)) # 2 bytes
- elseif payload_len == 127
- payload_len = ntoh(read(io,UInt64)) # 8 bytes
- end
-
- maskkey = Array(UInt8,4)
- for i in 1:4
- maskkey[i] = read(io,UInt8)
- end
-
- data = Array(UInt8, payload_len)
- for i in 1:payload_len
- d = read(io, UInt8)
- d = d $ maskkey[mod(i - 1, 4) + 1]
- data[i] = d
- end
-
- return WebSocketFragment(fin,rsv1,rsv2,rsv3,opcode,mask,payload_len,maskkey,data)
-end
+ a = read(io,UInt8)
+ fin = a & 0b1000_0000 >>> 7 # If fin, then is final fragment
+ rsv1 = a & 0b0100_0000 # If not 0, fail.
+ rsv2 = a & 0b0010_0000 # If not 0, fail.
+ rsv3 = a & 0b0001_0000 # If not 0, fail.
+ opcode = a & 0b0000_1111 # If not known code, fail.
+ # TODO: add validation somewhere to ensure rsv, opcode, mask, etc are valid.
+
+ b = read(io,UInt8)
+ mask = b & 0b1000_0000 >>> 7 # If not 1, fail.
+
+ if mask != 1
+ error("WebSocket reader cannot handle incoming messages without mask. " *
+ "See http://tools.ietf.org/html/rfc6455#section-5.3")
+ end
-# Read one non-control message from a WebSocket. Any control messages that are
-# read will be handled by the handle_control_frame function. This function will
-# not return until a full non-control message has been read. If the other side
-# doesn't ever complete it's message, this function will never return. Only the
-# data (contents/body/payload) of the message will be returned from this
-# function.
-function Base.read(ws::WebSocket)
- if ws.is_closed
- error("Attempt to read from closed WebSocket")
- end
- frame = read_frame(ws.socket)
-
- # Handle control (non-data) messages.
- if is_control_frame(frame)
- # Don't return control frames; they're not interesting to users.
- handle_control_frame(ws,frame)
-
- # Recurse to return the next data frame.
- return read(ws)
- end
-
- # Handle data message that uses multiple fragments.
- if !frame.is_last
- return vcat(frame.data, read(ws))
- end
-
- return frame.data
+ payload_len::UInt64 = b & 0b0111_1111
+ if payload_len == 126
+ payload_len = ntoh(read(io,UInt16)) # 2 bytes
+ elseif payload_len == 127
+ payload_len = ntoh(read(io,UInt64)) # 8 bytes
+ end
+
+ maskkey = Array{UInt8,1}(4)
+ for i in 1:4
+ maskkey[i] = read(io,UInt8)
+ end
+
+ data = Array{UInt8,1}(payload_len)
+ for i in 1:payload_len
+ d = read(io, UInt8)
+ d = xor(d , maskkey[mod(i - 1, 4) + 1])
+ data[i] = d
+ end
+
+ return WebSocketFragment(fin,rsv1,rsv2,rsv3,opcode,mask,payload_len,maskkey,data)
end
+"""
+ read(ws::WebSocket)
+Read one non-control message from a WebSocket. Any control messages that are
+read will be handled by the handle_control_frame function. This function will
+not return until a full non-control message has been read. If the other side
+doesn't ever complete its message, this function will never return. Only the
+data (contents/body/payload) of the message will be returned from this
+function.
+"""
+function Base.read(ws::WebSocket)
+ if ws.is_closed
+ error("Attempt to read from closed WebSocket")
+ end
+ frame = read_frame(ws.socket)
-#
-# WebSocket Handshake
-#
+ # Handle control (non-data) messages.
+ if is_control_frame(frame)
+ # Don't return control frames; they're not interesting to users.
+ handle_control_frame(ws,frame)
+
+ # Recurse to return the next data frame.
+ return read(ws)
+ end
-# This function transforms a websocket client key into the server's accept
-# value. This is done in three steps:
-# 1. Concatenate key with magic string from RFC.
-# 2. SHA1 hash the resulting base64 string.
-# 3. Encode the resulting number in base64.
-# This function then returns the string of the base64-encoded value.
+ # Handle data message that uses multiple fragments.
+ if !frame.is_last
+ return vcat(frame.data, read(ws))
+ end
+
+ return frame.data
+end
+
+"""
+ WebSocket Handshake Procedure
+`generate_websocket_key(key)` transforms a websocket client key into the server's accept
+value. This is done in three steps:
+1. Concatenate key with magic string from RFC.
+2. SHA1 hash the resulting base64 string.
+3. Encode the resulting number in base64.
+This function then returns the string of the base64-encoded value.
+"""
function generate_websocket_key(key)
hashed_key = digest(MD_SHA1, key*"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
String(encode(Base64, hashed_key))
end
-# Responds to a WebSocket handshake request.
-# Checks for required headers; sends Response(400) if they're missing or bad.
-# Otherwise, transforms client key into accept value, and sends Reponse(101).
+"""
+Responds to a WebSocket handshake request.
+Checks for required headers; sends Response(400) if they're missing or bad.
+Otherwise, transforms client key into accept value, and sends Reponse(101).
+"""
function websocket_handshake(request,client)
- if !haskey(request.headers, "Sec-WebSocket-Key")
- Base.write(client.sock, Response(400))
- return
- 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
- 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
- end
+ if !haskey(request.headers, "Sec-WebSocket-Key")
+ Base.write(client.sock, Response(400))
+ return
+ 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
+ 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
+ end
resp_key = generate_websocket_key(key)
response = Response(101)
@@ -374,7 +384,7 @@ function websocket_handshake(request,client)
Base.write(client.sock, response)
end
-# Implement the WebSocketInterface, for compatilibility with HttpServer.
+""" Implement the WebSocketInterface, for compatilibility with HttpServer."""
immutable WebSocketHandler <: HttpServer.WebSocketInterface
handle::Function
end
diff --git a/test/runtests.jl b/test/runtests.jl
index c87966e..a77bee7 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -19,10 +19,10 @@ import HttpCommon: Request
# Test writing
function xor_payload(maskkey, data)
- out = Array(UInt8, length(data))
+ out = Array{UInt8,1}(length(data))
for i in 1:length(data)
d = data[i]
- d = d $ maskkey[mod(i - 1, 4) + 1]
+ d = xor(d , maskkey[mod(i - 1, 4) + 1])
out[i] = d
end
out
@@ -34,16 +34,16 @@ const io = IOBuffer()
for len = [8, 125], op = (rand(UInt8) & 0b1111), fin=[true, false]
test_str = randstring(len)
- write_fragment(io, fin, test_str, op)
+ write_fragment(io, fin, Vector{UInt8}(test_str), op)
- frame = takebuf_array(io)
+ frame = take!(io)
@test bits(frame[1]) == (fin ? "1" : "0") * "000" * bits(op)[end-3:end]
@test frame[2] == @compat UInt8(len)
@test String(frame[3:end]) == test_str
# Check to see if reading message without a mask fails
- in_buf = IOBuffer(frame)
+ in_buf = IOBuffer(String(frame))
@test_throws ErrorException read_frame(in_buf)
close(in_buf)
@@ -72,9 +72,9 @@ end
for len = 126:129, op = 0b1111, fin=[true, false]
test_str = randstring(len)
- write_fragment(io, fin, test_str, op)
+ write_fragment(io, fin, Vector{UInt8}(test_str), op)
- frame = takebuf_array(io)
+ frame = take!(io)
@test bits(frame[1]) == (fin ? "1" : "0") * "000" * bits(op)[end-3:end]
@test frame[2] == 126