Skip to content

Commit

Permalink
Add serve methods, clean-up
Browse files Browse the repository at this point in the history
mod:: README.md	Add 'Errors?', 'Switch to HTTP'
mod::   benchmark/benchmark.jl 	Include benchmark_prepare.jl
mod::   benchmark/benchmark_prepare.jl   Avoid redefinitions
mod::   benchmark/functions_open_browsers.jl  Mutable default address
mod::   examples/chat_explore.jl    usews(ws) -> coroutine(ws)
new:   examples/minimal_server.jl   Readme.md example
new:   examples/minimal_server_HTTP.jl Readme.md example for HTTP
deleted:    examples/repl-client.html  Delete bad practice
deleted:    examples/server.jl     Delete insecure practice: eval.
mod::   src/HTTP.jl          Add methods for serve, fewer arguments.
mod::   test/client_server_test.jl  Use fewer arguments
mod::   test/error_test.jl    Use new methods serve
  • Loading branch information
hustf committed Jun 9, 2018
1 parent 5134187 commit 2974814
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 143 deletions.
53 changes: 34 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Call `run`, which is a wrapper for calling `listen`. See inline docs.
##### Using HTTP
Call `WebSockets.serve`, which is a wrapper for `HTTP.listen`. See inline docs.

## What does `WebSockets.jl` enable?
## What does WebSockets.jl enable?

- reading and writing between entities you can program or know about
- low latency messaging
Expand All @@ -39,28 +39,28 @@ Call `WebSockets.serve`, which is a wrapper for `HTTP.listen`. See inline docs.
- heartbeating, relaying
- build a network including browser clients
- convenience functions for gatekeeping with a common interface for HttpServer and HTTP
- writing http handlers and websockets 'handlers' in the same process can be an advantage. Exchanging unique tokens via http(s)
before accepting websockets is recommended for improved security.
- writing http handlers and websocket coroutines ('handlers') in the same process can be an advantage. Exchanging unique tokens via http(s)
before accepting websockets is recommended for improved security

WebSockets are well suited for user interactions via a browser. By calling compiled Javascript functions in browsers and using parallel workers,
user interaction and graphics workload, even development time can be moved off Julia resources.
WebSockets are well suited for user interactions via a browser or [cross-platform applications](https://electronjs.org/). User interaction and graphics workload, even development time can be moved off Julia resources. Use websockets to pass arguments between compiled functions on both sides; don't evaluate received code!

The /logutils folder contains some logging functionality that is quite fast and can make working with multiple asyncronous tasks easier. This functionality may be moved out of WebSockets in the future, depending on how other logging capabilities develop.
The /logutils folder contains some specialized logging functionality that is quite fast and can make working with multiple asyncronous tasks easier. See /benchmark code for how to use. Logging may be moved out of WebSockets in the future, depending on how other logging capabilities develop.

You should also have a look at Julia packages [DandelionWebSockets](https://github.com/dandeliondeathray/DandelionWebSockets.jl) or the implementation currently part of HTTP.jl.
You should also have a look at alternative Julia packages: [DandelionWebSockets](https://github.com/dandeliondeathray/DandelionWebSockets.jl) or the implementation currently part of HTTP.jl.

## What are the main downsides to WebSockets (in Julia)?

- Logging. We need customizable and very fast logging for building networked applications.
- Security. Julia's Http(s) servers are currently not working to our knowledge.
- Non-compliant proxies on the internet, company firewalls. Commercial applications often use competing technologies for this reason, according to some old articles at least. HTTP.jl lets you use such techniques.
- 'Warm-up', i.e. compilation when a method is first used. These are excluded from current benchmarks.
- Security. Julia's Http(s) servers are currently not fully working to our knowledge.
- Compression is not implemented.
- Possibly non-compliant proxies on the internet, company firewalls.
- 'Warm-up', i.e. compilation when a method is first used. Warm-up is excluded from current benchmarks.
- Garbage collection, which increases message latency at semi-random intervals. See benchmark plots.
- If a connection is closed improperly, the connection task will throw uncaught ECONNRESET and similar messages.
- TCP quirks, including 'warm-up' time with low transmission speed after a pause. Heartbeats can alleviate.
- Neither HTTP.jl or HttpServer.jl are made just for connecting WebSockets. You may need strong points from both.
- The optional dependencies increases load time compared to fixed dependencies.
- Since 'read' is a blocking function, you can easily end up reading indefinetely from both sides.
- Neither HTTP.jl or HttpServer.jl are made just for connecting WebSockets. You may need strong points from both.
- The optional dependencies may increase load time compared to fixed dependencies.
- Since 'read' is a blocking function, you can easily end up reading indefinitely from both sides.

## Server side example

Expand Down Expand Up @@ -129,7 +129,7 @@ You could replace 'using HttpServer' with 'using HTTP'. Also:
WebSocketHandler -> WebSockets.WebsocketHandler


## Client side
## Client side example

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

Expand Down Expand Up @@ -170,14 +170,29 @@ WebSockets.open("ws://127.0.0.1:$PORT") do ws
end
```

The output in a console session is barely readable, which is irritating. To build real-time applications, we need more code.
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.

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 use `readguarded` and `writeguarded` to save on error handling code.

## Switching from HttpServer to HTTP?
Some types and methods are not exported. See inline docs:
- `Server` -> `WebSockets.ServerWS`
- `WebSocketHandler` -> `WebSockets.WebsocketHandler`
- `run` -> `WebSockets.serve()`
- `Response` -> `HTTP.Response`
- `Request` -> `HTTP.Response`
- `HttpHandler`-> `HTTP.HandlerFunction`

You may also want to consider using `target`, `orgin`and `subprotocol`, which
are compatible with both of the types above.


~~~~
Expand Down
10 changes: 10 additions & 0 deletions benchmark/benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,13 @@ Outlined benchmarks for developing an application using WebSockets (postponed):
choice of message size and buffers for long-running calculations which
connects to a server for distribution of results
=#
if !isdefined(:SRCPATH)
const SRCPATH = Base.source_dir() == nothing ? Pkg.dir("WebSockets", "benchmark") :Base.source_dir()
const LOGGINGPATH = realpath(joinpath(SRCPATH, "../logutils/"))
SRCPATH LOAD_PATH && push!(LOAD_PATH, SRCPATH)
LOGGINGPATH LOAD_PATH && push!(LOAD_PATH, LOGGINGPATH)
include(joinpath(SRCPATH, "functions_open_browsers.jl"))
end
# Don't run this if the output files are recent?
include("benchmark_prepare.jl")
# include detailed benchmarks using the results from the above as nominal.
19 changes: 13 additions & 6 deletions benchmark/benchmark_prepare.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@

# This file collects data and tests functions.
# The intention is preparing input for a benchmark suite,
# but the output actually is sufficient for most purposes.
# but the output from this file actually is sufficient for some purposes.
#
# Both log file, results plots and tables are printed to the same file.
# Viewing the plots in a text editor is probably possible, see UnicodePlots.jl.
# Running this file on a Windows laptop with all browsers takes 5-10 minutes
# Pull request welcome if someone can figure out how to do it.
# Running this file on a Windows laptop with all browsers takes 5-10 minutes.


if !isdefined(:SRCPATH)
const SRCPATH = Base.source_dir() == nothing ? Pkg.dir("WebSockets", "benchmark") :Base.source_dir()
const LOGGINGPATH = realpath(joinpath(SRCPATH, "../logutils/"))
SRCPATH LOAD_PATH && push!(LOAD_PATH, SRCPATH)
LOGGINGPATH LOAD_PATH && push!(LOAD_PATH, LOGGINGPATH)
include(joinpath(SRCPATH, "functions_open_browsers.jl"))
end


"A vector of message sizes"
const VSIZE = reverse([i^3 * 1020 for i = 1:12])
const SRCPATH = Base.source_dir() == nothing ? Pkg.dir("WebSockets", "benchmark") :Base.source_dir()
const LOGGINGPATH = realpath(joinpath(SRCPATH, "../logutils/"))
SRCPATH LOAD_PATH && push!(LOAD_PATH, SRCPATH)
LOGGINGPATH LOAD_PATH && push!(LOAD_PATH, LOGGINGPATH)
include(joinpath(SRCPATH, "functions_open_browsers.jl"))
include(joinpath(SRCPATH, "functions_benchmark.jl"))
#
Expand Down
25 changes: 18 additions & 7 deletions benchmark/functions_open_browsers.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# Included in benchmark_2.jl
# Included in benchmark_prepare.jl and in browsertests.jl
# Refers logutils
if !isdefined(:SRCPATH)
const SRCPATH = Base.source_dir() == nothing ? Pkg.dir("WebSockets", "benchmark") :Base.source_dir()
const LOGGINGPATH = realpath(joinpath(SRCPATH, "../logutils/"))
SRCPATH LOAD_PATH && push!(LOAD_PATH, SRCPATH)
LOGGINGPATH LOAD_PATH && push!(LOAD_PATH, LOGGINGPATH)
end
using logutils_ws

"A list of potentially available browsers, to be tried in succession if present"
const BROWSERS = ["chrome", "firefox", "iexplore", "safari", "phantomjs"]
Expand All @@ -7,8 +15,11 @@ mutable struct Countbrowser;value::Int;end
(c::Countbrowser)() =COUNTBROWSER.value += 1
"For next value: COUNTBROWSER(). For current value: COUNTBROWSER.value"
const COUNTBROWSER = Countbrowser(0)
const PORT = 8000
const URL = "http://127.0.0.1:$PORT/bce.html"
const PORT = [8000]
const PAGE = ["bce.html"]
const URL = ["http://127.0.0.1:$(PORT[1])/$(PAGE[1])"]



"Get application path for developer applications"
function fwhich(s)
Expand Down Expand Up @@ -151,11 +162,11 @@ end

"Try to open one browser from BROWSERS.
In some cases we expect an immediate indication
of failure, for example when the corresponding file
of failure, for example when the corresponding browser
is not found on the system. In other cases, we will
just wait in vain for a request. In those cases,
call this function again. It remembers which browsers
were tried before.
just wait in vain. In those cases,
call this function again after a reasonable timeout.
The function remembers which browsers were tried before.
"
function open_a_browser()
id = "open_next_browser"
Expand Down
14 changes: 4 additions & 10 deletions examples/chat_explore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ particular websocket is open. The argument is an open websocket.
Other instances of the function run in other tasks. The tasks
are generated by either HTTP or HttpServer.
"""
function usews(thisws)
function coroutine(thisws)
global lastws = thisws
push!(WEBSOCKETS, thisws => length(WEBSOCKETS) +1 )
t1 = now() + CLOSEAFTER
Expand Down Expand Up @@ -115,7 +115,7 @@ end


"""
`Server => gatekeeper(Request, WebSocket) => usews(WebSocket)`
`Server => gatekeeper(Request, WebSocket) => coroutine(WebSocket)`
The gatekeeper makes it a little harder to connect with
malicious code. It inspects the request that was upgraded
Expand All @@ -126,7 +126,7 @@ function gatekeeper(req, ws)
global lastws = ws
orig = WebSockets.origin(req)
if startswith(orig, "http://localhost") || startswith(orig, "http://127.0.0.1")
usews(ws)
coroutine(ws)
else
warn("Unauthorized websocket connection, $orig not approved by gatekeeper")
end
Expand All @@ -143,7 +143,7 @@ server_httpserver = Server(HttpHandler(req2resp), WebSocketHandler(gatekeeper))
server_HTTP = WebSockets.ServerWS(HTTP.HandlerFunction(req2resp), WebSockets.WebsocketHandler(gatekeeper))

# Start the HTTP server asyncronously, and stop it later
litas_HTTP = @schedule WebSockets.serve(server_HTTP, URL, HTTPPORT, false)
litas_HTTP = @schedule WebSockets.serve(server_HTTP, URL, HTTPPORT)
@schedule begin
println("HTTP server listening on $URL:$HTTPPORT for $CLOSEAFTER")
sleep(CLOSEAFTER.value)
Expand All @@ -160,10 +160,4 @@ litas_httpserver = @schedule run(server_httpserver, HTTPSERVERPORT)
Base.throwto(litas_httpserver, InterruptException())
end

# Note that stopping the HttpServer in a while will send an error messages to the
# console. We could get rid of the messages by binding the task to a Channel.
# However, we can't get rid of ECONNRESET messages in that way. This is
# because the errors are triggered in tasks generated by litas_httpserver again,
# and those aren't channeled anywhere.

nothing
33 changes: 33 additions & 0 deletions examples/minimal_server.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using HttpServer
using WebSockets

function coroutine(ws)
while isopen(ws)
data, = readguarded(ws)
s = String(data)
if s == ""
break
end
println(s)
if s[1] == "P"
writeguarded(ws, "No, I'm not!")
else
writeguarded(ws, "Why?")
end
end
end

function gatekeeper(req, ws)
if origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
coroutine(ws)
else
println(origin(req))
end
end

handle(req, res) = Response(200)

server = Server(HttpHandler(handle),
WebSocketHandler(gatekeeper))

@async run(server, 8080)
33 changes: 33 additions & 0 deletions examples/minimal_server_HTTP.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using HTTP
using WebSockets

function coroutine(ws)
while isopen(ws)
data, = readguarded(ws)
s = String(data)
if s == ""
break
end
println(s)
if s[1] == "P"
writeguarded(ws, "No, I'm not!")
else
writeguarded(ws, "Why?")
end
end
end

function gatekeeper(req, ws)
if origin(req) == "http://127.0.0.1:8080" || origin(req) == "http://localhost:8080"
coroutine(ws)
else
println(origin(req))
end
end

handle(req, res) = HTTP.Response(200)

server = WebSockets.ServerWS(HTTP.HandlerFunction(handle),
WebSockets.WebsocketHandler(gatekeeper))

@async WebSockets.serve(server, 8080)
56 changes: 0 additions & 56 deletions examples/repl-client.html

This file was deleted.

37 changes: 0 additions & 37 deletions examples/server.jl

This file was deleted.

Loading

0 comments on commit 2974814

Please sign in to comment.