Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change dependencies -> master #94

Merged
merged 36 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
77a8f8d
Use HTTP.jl (#87)
Feb 13, 2018
1dd534b
Http.jl (#88)
Feb 16, 2018
86c57ff
Http.jl (#89)
Feb 17, 2018
7d5fb44
Update README, graceful close, add ping/pong. Temp tests. (#90)
Feb 17, 2018
02c0fcb
new file: examples/chat_explore.html
hustf Feb 18, 2018
054e1ce
modified: appveyor.yml - also test change_dependencies branch. Rem…
hustf Feb 18, 2018
4f40a50
modified: README.md - temporary badges for CI tests on appveyor.
hustf Feb 18, 2018
9b01fe6
Http.jl (#92)
Feb 20, 2018
03380cd
modified: examples/chat_explore.jl - extended with HTTP server and …
hustf Feb 21, 2018
bafb175
Only copy String bytes if masked (#93)
Feb 21, 2018
7be02aa
modified: examples/chat_explore.jl - changed interface to gatekeepe…
hustf Feb 22, 2018
4844174
Merge branch 'change_dependencies' of https://github.com/JuliaWeb/Web…
hustf Feb 22, 2018
d0c98a3
modified: examples/chat_explore.html
hustf Feb 25, 2018
6122157
mod: examples/chat-client.html
hustf Apr 1, 2018
5b7c661
mod: src/HTTP.jl
hustf Apr 3, 2018
42df9ac
modified: src/HTTP.jl # Fix bug: HTTP.Request has no .status field.
hustf Apr 9, 2018
4da478e
modified: src/HTTP.jl fixed last fix... Request-> Response o…
hustf Apr 10, 2018
807fc3f
modified: src/HTTP.jl
hustf Apr 12, 2018
1ee61d5
modified: src/HTTP.jl Expanced comment on 'upgrade', removed…
hustf Apr 16, 2018
1741332
modified: README.md Testing include a picture.
hustf Apr 16, 2018
b5017e5
mod: README.md Image experiment
hustf Apr 16, 2018
32c6bee
modified: README.md Took away temporary network graph (github …
hustf Apr 16, 2018
5fca921
new file: benchmark/REQUIRE No direct effect, lists
hustf May 13, 2018
404b3b6
modified: benchmark/functions_benchmark.jl Improved explanations,
hustf May 14, 2018
8edb459
Do not return control frames like ping.
hustf May 24, 2018
bb9af69
modified: src/WebSockets.jl reinstate check for present mask wi…
hustf May 26, 2018
15adbdf
Status codes implemented for closing handshake
hustf May 26, 2018
22525af
modified: benchmark/logs/benchmark_results_readable.log
hustf May 27, 2018
dce6a49
Expand HttpServer tests to HTTP
hustf May 28, 2018
ac86883
Unit tests 93%, excluding browsertests
hustf Jun 2, 2018
f8f9303
modified: src/WebSockets.jl
hustf Jun 2, 2018
da008ce
Add sleep(3) after start async server for travis-test
hustf Jun 2, 2018
b510f88
Server channels for exceptions and traces
hustf Jun 3, 2018
a4279f6
Info during tests, more sleeping
hustf Jun 3, 2018
7f244b0
mod: README.md Large rewrite
hustf Jun 4, 2018
ada7f2d
modified: README.md Another spin
hustf Jun 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ os:
- linux
- osx
julia:
- 0.5
- 0.6
sudo: false
notifications:
Expand Down
206 changes: 149 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,184 @@
WebSockets.jl
=============
# 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.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.

This package works with [HttpServer.jl](https://github.com/JuliaWeb/HttpServer.jl), which is what you use to set up a server that accepts HTTP(S) connections.
Server and client side [Websockets](https://tools.ietf.org/html/rfc6455) protocol in Julia. WebSockets is a small overhead message protocol layered over [TCP](https://tools.ietf.org/html/rfc793). It uses HTTP(S) for establishing the connections.

## Getting started
WebSockets.jl must be used with either HttpServer.jl or HTTP.jl, but neither is a dependency of this package. You will need to first add one or both, i.e.:

```julia
julia> Pkg.add("HttpServer")
julia> Pkg.add("HTTP")
julia> Pkg.add("WebSockets")
```
### Open a client side connection
Client side websockets are created by calling `WebSockets.open` (with a server running). Client side websockets require [HTTP.jl](https://github.com/JuliaWeb/HttpServer.jl).

### Accept server side connections

Server side websockets are asyncronous [tasks](https://docs.julialang.org/en/stable/stdlib/parallel/#Tasks-1), spawned by either
[HttpServer.jl](https://github.com/JuliaWeb/HttpServer.jl) or HTTP.jl.

##### Using HttpServer
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?

- reading and writing between entities you can program or know about
- low latency messaging
- implement your own 'if X send this, Y do that' subprotocols
- implement registered [websocket subprotocols](https://www.iana.org/assignments/websocket/websocket.xml#version-number)
- 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.

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.

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.

You should also have a look at 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.
- 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.

As a first example, we can create a WebSockets echo server:
## 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.

```julia
using HttpServer
using WebSockets

wsh = WebSocketHandler() do req,client
while true
msg = read(client)
write(client, msg)
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
end

server = Server(wsh)
run(server,8080)
```
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

This sets up a server running on localhost, port 8080.
It will accept WebSockets connections.
The function in `wsh` will be called once per connection; it takes over that connection.
In this case, it reads each `msg` from the `client` and then writes the same message back: a basic echo server.
handle(req, res) = Response(200)

The function that you pass to the `WebSocketHandler` constructor takes two arguments:
a `Request` from [HttpCommon.jl](https://github.com/JuliaWeb/HttpCommon.jl/blob/master/src/HttpCommon.jl#L142),
and a `WebSocket` from [here](https://github.com/JuliaWeb/WebSockets.jl/blob/master/src/WebSockets.jl#L17).
server = Server(HttpHandler(handle),
WebSocketHandler(gatekeeper))

## What can you do with a `WebSocket`?
You can:
run(server, 8080)
```

* `write` data to it
* `read` data from it
* send `ping` or `pong` messages
* `close` the connection
Now open a browser on http://127.0.0.1:8080/ and press F12. In the console, type the lines following ≫:
```javascript
≫ws = new WebSocket("ws://127.0.0.1:8080")
← WebSocket { url: "ws://127.0.0.1:8080/", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, onclose: null, extensions: "", protocol: "", onmessage: null, binaryType: "blob" }
≫ws.send("Peer, you're lying!")
← undefined
≫ws.onmessage = function(e){console.log(e.data)}
← function onmessage()
≫ws.send("Well, then.")
← undefined
Why? debugger eval code:1:28
```

## Installation/Setup
If you now navigate or close the browser, this happens:
1. the client side of the websocket connection will quickly send a close request and go away.
2. Server side `readguarded(ws)` has been waiting for messages, but instead closes 'ws' and returns ("", false)
3. `coroutine(ws)` is finished and the task's control flow returns to HttpServer
4. HttpServer does nothing other than exit this task. In fact, it often crashes because
somebody else (the browser) has closed the underlying TCP stream. If you had replaced the last Julia line with '@async run(server, 8080', you would see some long error messages.
5. The server, which spawned the task, continues to listen for incoming connections, and you're stuck. Ctrl + C!

~~~julia
julia> Pkg.add("WebSockets")
~~~
You could replace 'using HttpServer' with 'using HTTP'. Also:
Serve -> ServeWS
HttpHandler -> HTTP.Handler
WebSocketHandler -> WebSockets.WebsocketHandler

At this point, you can use the examples below to test that it all works.

## [Chat client/server example](https://github.com/JuliaWeb/WebSockets.jl/blob/master/examples/chat.jl):
## Client side

1. Move to the `~/.julia/<version>/WebSockets` directory
2. Run `julia examples/chat.jl`
3. In a web browser, open `localhost:8000`
4. You should see a basic IRC-like chat application
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.

## Echo server example:
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 HttpServer
```julia

using HTTP
using WebSockets

wsh = WebSocketHandler() do req,client
while true
msg = read(client)
write(client, msg)
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
end
end
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)))
end
```

The output in a console session is barely readable, which is irritating. To build real-time applications, we need more code.

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




server = Server(wsh)
run(server,8080)
~~~

To play with a WebSockets echo server, you can:

1. Paste the above code in to the Julia REPL
2. Open `localhost:8080` in Chrome
3. Open the Chrome developers tools console
4. Type `ws = new WebSocket("ws://localhost:8080");` into the console
5. Type `ws.send("hi")` into the console.
6. Switch to the 'Network' tab; click on the request; click on the 'frames' tab.
7. You will see the two frames containing "hi": one sent and one received.

~~~~
::::::::::::::::
Expand Down
7 changes: 2 additions & 5 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
julia 0.5
Compat 0.28.0
HttpCommon
HttpServer
Codecs
julia 0.6
MbedTLS
Requires
7 changes: 3 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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:
only:
- change_dependencies
- master
- /release-.*/

Expand Down Expand Up @@ -35,7 +34,7 @@ build_script:
- IF EXIST .git\shallow (git fetch --unshallow)
- ps: |
Write-Host "This is PowerShell - STDERR output will be red. Redirect! "
$env:PATH+=";C:\projects\julia\bin\"
$env:PATH+=";C:\projects\julia\bin\"
julia -e 'versioninfo();redirect_stderr(STDOUT);println(STDOUT, \"stdout\"); println(STDERR, \"stderr\")'
julia -e 'redirect_stderr(STDOUT);Pkg.init()'
julia --depwarn=no -e 'redirect_stderr(STDOUT);Pkg.add(\"HttpServer\");using HttpServer;'
Expand All @@ -44,4 +43,4 @@ build_script:
test_script:
- ps: |
Write-Host "This is PS"
julia -e 'redirect_stderr(STDOUT);info(\"Local websockets directory:\", Pkg.dir(\"WebSockets\"));Pkg.test(\"WebSockets\")'
julia -e 'redirect_stderr(STDOUT);info(\"Local websockets directory:\", Pkg.dir(\"WebSockets\"));Pkg.test(\"WebSockets\")'
5 changes: 5 additions & 0 deletions benchmark/REQUIRE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Being placed in the benchmark folder, this file has no effect on Julia.
# To run benchmarks, you may need to Pkg.add("..package below...")
HTTP
IndexedTables
UnicodePlots
97 changes: 97 additions & 0 deletions benchmark/bce.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>WS text</title>
</head>
<body>
<p>BCE echoing websocket.
</p>
<ul>
<li> ws1 </li>
</ul>
<p>|
Initiates a websocket connection.
For every received websocket message, sends one empty message and then the received message. The websocket is closed by
the server, or when user navigates away.<br>
<p>
<p id = "init"></p>
<p>&nbsp;&nbsp;This is <b id = "browserid"> ... </b></p>
<div id = "ws1" style="border:thick solid black;"><p>ws1 Websocket</p></div>
<script>

window.onload= function(){
plog("init", "<p>:window.onload</p>");
ws1 = addwebsocket("ws1");
}

// log to DOM element ws
function plog(ws, shtm){
document.getElementById(ws).innerHTML += shtm
}

function addwebsocket(instancename, subprotocol){
var wsuri = document.URL.replace("http:","ws:");
if (typeof subprotocol === "undefined") {
var ws = new WebSocket(wsuri)
} else {
var ws = new WebSocket(wsuri, subprotocol)
}
ws.mynam = instancename;
ws.onclose = function(e){
plog(e.target.mynam, "<p>: " + e.target.mynam + ".onclose:" +
"wasClean: " + e.wasClean + "; " +
"code: " + e.code + "; " + codeDesc[e.code] + "; " +
"reason: " + e.reason + "; " +
"<br>&nbsp;&nbsp;Websocket state is now " + e.target.readyState +
" " + readystateDesc[e.target.readyState] + ".</p>");
}
ws.onerror = function(e){
plog(e.target.mynam, "<p>: " + e.target.mynam + ".onerror: " +
"<br>&nbsp;&nbsp;Websocket state is now " + e.target.readyState +
" " + readystateDesc[e.target.readyState] + ".</p>");
}
ws.onopen = function(e){
ws.binaryType = "arraybuffer";
plog(e.target.mynam, "<p>: " + e.target.mynam + ".onopen: " +
"<br>&nbsp;&nbsp;Websocket state is now " + e.target.readyState +
" " + readystateDesc[e.target.readyState] +
" reading messages as binary arraybuffers"
)
}
ws.onmessage = function (e){
var msg = e.data;
ws.send("")
ws.send(msg)
}
plog(instancename, "<p>" + instancename + " created with event handlers.</p>");
return ws
} // addwebsocket


var codeDesc ={1000:"Normal",
1001:"Going Away",
1002:"Protocol Error",
1003:"Unsupported Data",
1004:"Reserved",
1005:"No Status Recvd- reserved",
1006:"Abnormal Closure- reserved",
1007:"Invalid frame payload data",
1008:"Policy Violation",
1009:"Message too big",
1010:"Missing Extension",
1011:"Internal Error",
1012:"Service Restart",
1013:"Try Again Later",
1014:"Bad Gateway",
1015:"TLS Handshake"};

var readystateDesc ={0:"CONNECTING",
1:"OPEN",
2:"CLOSING",
3:"CLOSED"};
</script>
</body>
</html>
Loading