Skip to content

Commit

Permalink
Stricter checks open client wsuri
Browse files Browse the repository at this point in the history
	modified:   README.md    Replace client example
	new:examples/client_repl_input.jl  Same as README.md
	modified:   examples/minimal_server.jl   More prints, as in README
	modified:   examples/minimal_server_HTTP.jl    More prints
	modified:   src/HTTP.jl   Stricter wsuri checks
	modified:   test/client_test.jl   Check errors bad wsuri
  • Loading branch information
hustf committed Jun 9, 2018
1 parent 2974814 commit cca4849
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 50 deletions.
77 changes: 38 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ You should also have a look at alternative Julia packages: [DandelionWebSockets]

## Server side example

As a first example, we can create a WebSockets echo server. We use named function arguments for more readable stacktraces while debugging.
As a first example, we can create a WebSockets echo server. We use named function arguments for more readable stacktraces while debugging.

```julia
using HttpServer
Expand All @@ -74,10 +74,8 @@ function coroutine(ws)
while isopen(ws)
data, = readguarded(ws)
s = String(data)
if s == ""
break
end
println(s)
s == "" && break
println("Received: ", s)
if s[1] == "P"
writeguarded(ws, "No, I'm not!")
else
Expand All @@ -87,10 +85,12 @@ function coroutine(ws)
end

function gatekeeper(req, ws)
if origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
println("\nOrigin:", origin(req), " Target:", target(req), " subprotocol:", subprotocol(req))
# Non-browser clients don't send Origin. We liberally accept in this case.
if origin(req) == "" || origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
coroutine(ws)
else
println(origin(req))
println("Inacceptable request")
end
end

Expand All @@ -99,7 +99,7 @@ handle(req, res) = Response(200)
server = Server(HttpHandler(handle),
WebSocketHandler(gatekeeper))

run(server, 8080)
@async run(server, 8080)
```

Now open a browser on http://127.0.0.1:8080/ and press F12. In the console, type the lines following ≫:
Expand Down Expand Up @@ -131,55 +131,54 @@ You could replace 'using HttpServer' with 'using HTTP'. Also:

## Client side example

You need to use [HTTP.jl](https://github.com/JuliaWeb/HttpServer.jl).

What you can't do is use a browser as the server side. The server side can be the example above, running in an asyncronous task. The server can also be running in a separate REPL, or in a a parallel task. The benchmarks puts the `client` side on a parallel task.
Clients need to use [HTTP.jl](https://github.com/JuliaWeb/HttpServer.jl).

The following example
- runs server in an asyncronous task, client in the REPL control flow
- uses [Do-Block-Syntax](https://docs.julialang.org/en/v0.6.3/manual/functions/#Do-Block-Syntax-for-Function-Arguments-1), which is a style choice
- the server `ugrade` skips checking origin(req)`
- the server is invoked with `listen(..)` instead of `serve()`
- read(ws) and write(ws, msg) instead of readguarded(ws), writeguarded(ws)

```julia

using HTTP
using WebSockets

const PORT = 8080

# Server side
@async HTTP.listen("127.0.0.1", PORT) do http
if WebSockets.is_upgrade(http.message)
WebSockets.upgrade(http) do req, ws
while isopen(ws)
msg = String(read(ws))
write(ws, msg)
end
function client_one_message(ws)
print_with_color(:green, STDOUT, "\nws|client input > ")
msg = readline(STDIN)
if writeguarded(ws, msg)
msg, stillopen = readguarded(ws)
println("Received:", String(msg))
if stillopen
println("The connection is active, but we leave. WebSockets.jl will close properly.")
else
println("Disconnect during reading.")
end
else
println("Disconnect during writing.")
end
end

sleep(2)


WebSockets.open("ws://127.0.0.1:$PORT") do ws
write(ws, "Peer, about your hunting")
println("echo received:" * String(read(ws)))
function main()
while true
println("\nWebSocket client side. WebSocket URI format:")
println("ws:// host [ \":\" port ] path [ \"?\" query ]")
println("Example:\nws://127.0.0.1:8080")
println("Where do you want to connect? Empty line to exit")
print_with_color(:green, STDOUT, "\nclient_repl_input > ")
wsuri = readline(STDIN)
wsuri == "" && break
res = WebSockets.open(client_one_message, wsuri)
!isa(res, HTTP.Response) && println(res)
end
println("Have a nice day")
end

main()
```

The output from the example in a console session is barely readable. Output from asyncronous tasks are intermixed. To build real-time applications, we need more code. See other examples in /test, /benchmark/ and /examples.
See other examples in /test, /benchmark/ and /examples. Some logging utilties for a running relay server are available in /logutils.

Some logging utilties for a running relay server are available in /logutils.

## Errors after updating?

The introduction of client side websockets to this package may require changes in your code:
- `using HttpServer` (or import) prior to `using WebSockets` (or import).
- The `WebSocket.id` field is no longer supported. You can generate unique counters by code similar to 'bencmark/functions_open_browsers.jl' COUNTBROWSER.
- You may want to modify error handling code. Examine WebSocketsClosedError.message.
- You may want to modify you error handling code. Examine WebSocketsClosedError.message.
- You may want to use `readguarded` and `writeguarded` to save on error handling code.

## Switching from HttpServer to HTTP?
Expand Down
33 changes: 33 additions & 0 deletions examples/client_repl_input.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using HTTP
using WebSockets
function client_one_message(ws)
print_with_color(:green, STDOUT, "\nws|client input > ")
msg = readline(STDIN)
if writeguarded(ws, msg)
msg, stillopen = readguarded(ws)
println("Received:", String(msg))
if stillopen
println("The connection is active, but we leave. WebSockets.jl will close properly.")
else
println("Disconnect during reading.")
end
else
println("Disconnect during writing.")
end
end
function main()
while true
println("\nWebSocket client side. WebSocket URI format:")
println("ws:// host [ \":\" port ] path [ \"?\" query ]")
println("Example:\nws://127.0.0.1:8080")
println("Where do you want to connect? Empty line to exit")
print_with_color(:green, STDOUT, "\nclient_repl_input > ")
wsuri = readline(STDIN)
wsuri == "" && break
res = WebSockets.open(client_one_message, wsuri)
!isa(res, HTTP.Response) && println(res)
end
println("Have a nice day")
end

main()
12 changes: 6 additions & 6 deletions examples/minimal_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ function coroutine(ws)
while isopen(ws)
data, = readguarded(ws)
s = String(data)
if s == ""
break
end
println(s)
s == "" && break
println("Received: ", s)
if s[1] == "P"
writeguarded(ws, "No, I'm not!")
else
Expand All @@ -18,10 +16,12 @@ function coroutine(ws)
end

function gatekeeper(req, ws)
if origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
println("\nOrigin:", origin(req), " Target:", target(req), " subprotocol:", subprotocol(req))
# Non-browser clients don't send Origin. We liberally accept in this case.
if origin(req) == "" || origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
coroutine(ws)
else
println(origin(req))
println("Inacceptable request")
end
end

Expand Down
8 changes: 5 additions & 3 deletions examples/minimal_server_HTTP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function coroutine(ws)
if s == ""
break
end
println(s)
println("Received: ", s)
if s[1] == "P"
writeguarded(ws, "No, I'm not!")
else
Expand All @@ -18,10 +18,12 @@ function coroutine(ws)
end

function gatekeeper(req, ws)
if origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
println("\nOrigin:", origin(req), " Target:", target(req), " subprotocol:", subprotocol(req))
# Non-browser clients don't send Origin. We liberally accept in this case.
if origin(req) == "" || origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
coroutine(ws)
else
println(origin(req))
println("Inacceptable request")
end
end

Expand Down
13 changes: 11 additions & 2 deletions src/HTTP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,22 @@ function open(f::Function, url; verbose=false, subprotocol = "", kw...)
push!(headers, "Sec-WebSocket-Protocol" => subprotocol )
end

if in('#', url)
throw(ArgumentError(" replace '#' with %23 in url: $url"))
end

uri = HTTP.URIs.URI(url)
if uri.scheme != "ws" && uri.scheme != "wss"
throw(ArgumentError(" bad argument url: Scheme not ws or wss. Input scheme: $(uri.scheme)"))
end


try
HTTP.open("GET", url, headers;
HTTP.open("GET", uri, headers;
reuse_limit=0, verbose=verbose ? 2 : 0, kw...) do http
_openstream(f, http, key)
end
catch err
# TODO don't pass on WebSocketClosedError and other known ones.
if typeof(err) <: Base.UVError
throw(WebSocketClosedError(" while open ws|client: $(string(err))"))
elseif typeof(err) <: HTTP.ExceptionRequest.StatusError
Expand Down
21 changes: 21 additions & 0 deletions test/client_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ end
@test typeof(caughterr) <: WebSockets.WebSocketClosedError
@test caughterr.message == " while open ws|client: connect: connection refused (ECONNREFUSED)"

info("try open with uknown scheme\n")
sleep(1)
caughterr = ArgumentError("")
try
WebSockets.open((_)->nothing, "ww://127.0.0.1:8099");
catch err
caughterr = err
end
@test typeof(caughterr) <: ArgumentError
@test caughterr.msg == " bad argument url: Scheme not ws or wss. Input scheme: ww"


caughterr = ArgumentError("")
try
WebSockets.open((_)->nothing, "ws://127.0.0.1:8099/svg/#");
catch err
caughterr = err
end
@test typeof(caughterr) <: ArgumentError
@test caughterr.msg == " replace '#' with %23 in url: ws://127.0.0.1:8099/svg/#"

info("start a client websocket that irritates by closing the TCP stream
connection without a websocket closing handshake. This
throws an error in the server task\n")
Expand Down

0 comments on commit cca4849

Please sign in to comment.