From 846a49f57523c15a209f3ed8a328c0dbfa20b84e Mon Sep 17 00:00:00 2001 From: Iagoba Apellaniz Date: Wed, 11 Jan 2017 18:21:37 +0100 Subject: [PATCH 1/6] Fixed the address of chat-client.html --- examples/chat.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/chat.jl b/examples/chat.jl index 05b5ca9..94d707a 100644 --- a/examples/chat.jl +++ b/examples/chat.jl @@ -30,7 +30,7 @@ wsh = WebSocketHandler() do req, client end end -onepage = readall(Pkg.dir("Websockets","examples","chat-client.html")) +onepage = readall(Pkg.dir("WebSockets","examples","chat-client.html")) httph = HttpHandler() do req::Request, res::Response Response(onepage) end From 01f33a9f9e19aa1793c1d8d0f6521cd763de91b0 Mon Sep 17 00:00:00 2001 From: hustf Date: Mon, 17 Apr 2017 21:44:35 +0200 Subject: [PATCH 2/6] modified: REQUIRE Compat version covering relevant changes in 0.6 modified: examples/chat-client.html Prepending messages -> Appending. modified: examples/chat.jl 0.6 update (take_buf, Array constructors) modified: examples/repl-client.html Prepending messages -> Appending. Javascript feedback, no script error when connection close. modified: examples/server.jl 0.6 update (take_buf, Array constructors). 'Eval' error handling and feedback. More type stability. modified: src/WebSockets.jl 0.6 update (take_buf, Array constructors). Precompilation. modified: test/runtests.jl 0.6 update (take_buf, Array constructors) --- REQUIRE | 4 ++-- examples/chat-client.html | 4 ++-- examples/chat.jl | 4 ++-- examples/repl-client.html | 16 ++++++++++++---- examples/server.jl | 23 +++++++++++++++-------- src/WebSockets.jl | 9 +++++---- test/runtests.jl | 14 +++++++------- 7 files changed, 45 insertions(+), 29 deletions(-) diff --git a/REQUIRE b/REQUIRE index b627762..08503e6 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,5 @@ -julia 0.4 -Compat 0.7.16 +julia 0.5 +Compat 0.16.0 HttpCommon 0.0.3 HttpServer 0.0.4 Codecs 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 94d707a..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 dce3d21..2dc3fe8 100644 --- a/src/WebSockets.jl +++ b/src/WebSockets.jl @@ -1,3 +1,4 @@ +__precompile__() module WebSockets # This module implements the server side of the WebSockets protocol. Some @@ -136,7 +137,7 @@ else 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) @@ -282,15 +283,15 @@ function read_frame(io::IO) payload_len = ntoh(read(io,UInt64)) # 8 bytes end - maskkey = Array(UInt8,4) + maskkey = Array{UInt8,1}(4) for i in 1:4 maskkey[i] = read(io,UInt8) end - data = Array(UInt8, payload_len) + data = Array{UInt8,1}(payload_len) for i in 1:payload_len d = read(io, UInt8) - d = d $ maskkey[mod(i - 1, 4) + 1] + d = xor(d , maskkey[mod(i - 1, 4) + 1]) data[i] = d 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 From d0986a01be0a8e7386e35a1d425415ecb91b604e Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Tue, 18 Apr 2017 02:48:09 +0530 Subject: [PATCH 3/6] update badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. From 2901cfbf7012dd902836016b734c247209ab09ce Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Tue, 18 Apr 2017 02:49:03 +0530 Subject: [PATCH 4/6] don't test this branch on Julia 0.4 anymore --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5be4390..9106f33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ os: - linux - osx julia: - - 0.4 - 0.5 - nightly sudo: false From 80c8414c412c9f9a205186f8e35614653a9c5e5f Mon Sep 17 00:00:00 2001 From: hustf Date: Thu, 20 Apr 2017 21:38:26 +0200 Subject: [PATCH 5/6] Reinstate 0.4, clean-up .travis.yml Reinstated Julia 0.4 tests REQUIRE Compat version down to 0.9.5 src/WebSockets.jl Removed dead code for Julia 0.3 Four-space indents Comment code -> Inline docs everywhere --- .travis.yml | 1 + REQUIRE | 4 +- src/WebSockets.jl | 489 +++++++++++++++++++++++----------------------- 3 files changed, 251 insertions(+), 243 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9106f33..5be4390 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ os: - linux - osx julia: + - 0.4 - 0.5 - nightly sudo: false diff --git a/REQUIRE b/REQUIRE index 08503e6..1c234eb 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,5 @@ -julia 0.5 -Compat 0.16.0 +julia 0.4 +Compat 0.9.5 HttpCommon 0.0.3 HttpServer 0.0.4 Codecs diff --git a/src/WebSockets.jl b/src/WebSockets.jl index 2dc3fe8..1347818 100644 --- a/src/WebSockets.jl +++ b/src/WebSockets.jl @@ -1,23 +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 @@ -32,25 +34,25 @@ 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) -# 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 @@ -77,110 +79,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, Vector{UInt8}(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) @@ -188,46 +190,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 @@ -239,129 +243,132 @@ 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 - wait(ws.socket.closenotify) - 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 + wait(ws.socket.closenotify) + 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,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 + 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) -# 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. + # 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 +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) @@ -371,7 +378,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 From 18a88a0300b27020acba04a0d50138ba710b0f95 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Mon, 24 Apr 2017 20:12:35 +0530 Subject: [PATCH 6/6] add appveyor.yml --- appveyor.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 appveyor.yml 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\")"