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

Http.jl #89

Merged
merged 10 commits into from
Feb 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
julia 0.6
HTTP
MbedTLS
Requires
2 changes: 0 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
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:
Expand Down
35 changes: 23 additions & 12 deletions examples/chat-client.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ <h1>Select a userinput</h1>
var $chatForm = document.querySelector("#say_message");
var $chatInput = $chatForm.querySelector("input[name=say]");


function uuid4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
}
var id = uuid4();

function addContent(html) {
var $content = document.querySelector("#content");
var div = document.createElement("div");
Expand All @@ -86,20 +94,28 @@ <h1>Select a userinput</h1>
if( !uname.replace(/\s/gi,'').length ) {
alert("Please select a valid userinput");
} else {
connection.send('setusername:'+ uname);
var msg = {
"id": id,
"userName": uname
};
connection.send(JSON.stringify(msg));
$userName.innerHTML = uname;
$welcome.style.display = "none";
$chat.style.display = "block";
}
}

function whenChatMessage() {
var msg = $chatInput.value;
if(!msg.replace(/\s/gi,'').length) {
var content = $chatInput.value;
if( !content.replace(/\s/gi,'').length) {
/* nothing to do */
} else {
connection.send('say:'+ msg);
addContent(`<p class='sent'>${you}: ${msg}</p>`);
var msg = {
"id": id,
"say": content
};
connection.send(JSON.stringify(msg));
addContent(`<p class='sent'>${you}: ${content}</p>`);
$chatInput.focus();
}
}
Expand All @@ -110,19 +126,14 @@ <h1>Select a userinput</h1>
whenChatMessage();
return false;
})

$chatInput.addEventListener("keypress", (e) => {
if( e.keyCode === 13 ) { whenChatMessage(); } ;
return false;
}, false)


$userForm.addEventListener("submit", function(e){
e.preventDefault();
e.stopImmediatePropagation();
whenUserName();
return false;
});

const connection = new SocketConnection(onMessageReceived);
connection.start();

Expand Down
32 changes: 19 additions & 13 deletions examples/chat.jl
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
using HttpServer
using WebSockets
using JSON

struct User
name::String
client::WebSocket
end
#global Dict to store open connections in
global connections = Dict{Int,WebSocket}()
global usernames = Dict{Int,String}()
global connections = Dict{String,User}()

function decodeMessage( msg )
String(copy(msg))
JSON.parse(String(copy(msg)))
end

wsh = WebSocketHandler() do req, client
global connections
@show connections[client.id] = client
while true
msg = read(client)
msg = decodeMessage(msg)
if startswith(msg, "setusername:")
println("SETTING USERNAME: $msg")
usernames[client.id] = msg[13:end]
id = msg["id"]
if haskey(msg,"userName") && !haskey(connections,id)
uname = msg["userName"]
println("SETTING USERNAME: $(uname)")
connections[id] = User(uname,client)
end
if startswith(msg, "say:")
println("EMITTING MESSAGE: $msg")
if haskey(msg,"say")
content = msg["say"]
println("EMITTING MESSAGE: $(content)")
for (k,v) in connections
if k != client.id
write(v, (usernames[client.id] * ": " * msg[5:end]))
if k != id
write(v.client, (v.name * ": " * content))
end
end
end
end
end

onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
httph = HttpHandler() do req::Request, res::Response
Response(onepage)
onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
Response(onepage)
end

server = Server(httph, wsh)
Expand Down
6 changes: 0 additions & 6 deletions examples/server.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using HttpServer
using WebSockets

#global Dict to store open connections in
global connections = Dict{Int,WebSocket}()
global usernames = Dict{Int,String}()

function decodeMessage( msg )
String(copy(msg))
end
Expand All @@ -19,8 +15,6 @@ function eval_or_describe_error(strmsg)
end

wsh = WebSocketHandler() do req, client
global connections
connections[client.id] = client
while true
val = client |> read |> decodeMessage |> eval_or_describe_error
output = String(take!(Base.mystreamvar))
Expand Down
76 changes: 76 additions & 0 deletions src/HTTP.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
info("Loading HTTP methods...")

function open(f::Function, url; binary=false, verbose=false, kw...)

key = base64encode(rand(UInt8, 16))

headers = [
"Upgrade" => "websocket",
"Connection" => "Upgrade",
"Sec-WebSocket-Key" => key,
"Sec-WebSocket-Version" => "13"
]

HTTP.open("GET", url, headers;
reuse_limit=0, verbose=verbose ? 2 : 0, kw...) do http

HTTP.startread(http)

status = http.message.status
if status != 101
return
end

check_upgrade(http)

if HTTP.header(http, "Sec-WebSocket-Accept") != generate_websocket_key(key)
throw(WebSocketError(0, "Invalid Sec-WebSocket-Accept\n" *
"$(http.message)"))
end

io = HTTP.ConnectionPool.getrawstream(http)
f(WebSocket(io,false))
end
end

function upgrade(f::Function, http::HTTP.Stream; binary=false)

check_upgrade(http)
if !HTTP.hasheader(http, "Sec-WebSocket-Version", "13")
throw(WebSocketError(0, "Expected \"Sec-WebSocket-Version: 13\"!\n$(http.message)"))
end

HTTP.setstatus(http, 101)
HTTP.setheader(http, "Upgrade" => "websocket")
HTTP.setheader(http, "Connection" => "Upgrade")
key = HTTP.header(http, "Sec-WebSocket-Key")
HTTP.setheader(http, "Sec-WebSocket-Accept" => generate_websocket_key(key))

HTTP.startwrite(http)

io = HTTP.ConnectionPool.getrawstream(http)
f(WebSocket(io, true))
end

function check_upgrade(http)
if !HTTP.hasheader(http, "Upgrade", "websocket")
throw(WebSocketError(0, "Expected \"Upgrade: websocket\"!\n$(http.message)"))
end

if !HTTP.hasheader(http, "Connection", "upgrade")
throw(WebSocketError(0, "Expected \"Connection: upgrade\"!\n$(http.message)"))
end
end

function is_upgrade(r::HTTP.Message)
(r isa HTTP.Request && r.method == "GET" || r.status == 101) &&
HTTP.hasheader(r, "Connection", "upgrade") &&
HTTP.hasheader(r, "Upgrade", "websocket")
end

# function listen(f::Function, host::String="localhost", port::UInt16=UInt16(8081); binary=false, verbose=false)
# HTTP.listen(host, port; verbose=verbose) do http
# upgrade(f, http; binary=binary)
# end
# end

74 changes: 74 additions & 0 deletions src/HttpServer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
info("Loading HttpServer methods...")

export WebSocketHandler


"""
Responds to a WebSocket handshake request.
Checks for required headers and subprotocols; sends Response(400) if they're missing or bad. Otherwise, transforms client key into accept value, and sends Reponse(101).
Function returns true for accepted handshakes.
"""
function websocket_handshake(request,client)
if !haskey(request.headers, "Sec-WebSocket-Key")
Base.write(client.sock, HttpServer.Response(400))
return false
end
if get(request.headers, "Sec-WebSocket-Version", "13") != "13"
response = HttpServer.Response(400)
response.headers["Sec-WebSocket-Version"] = "13"
Base.write(client.sock, response)
return false
end

key = request.headers["Sec-WebSocket-Key"]
if length(base64decode(key)) != 16 # Key must be 16 bytes
Base.write(client.sock, HttpServer.Response(400))
return false
end
resp_key = generate_websocket_key(key)

response = HttpServer.Response(101)
response.headers["Upgrade"] = "websocket"
response.headers["Connection"] = "Upgrade"
response.headers["Sec-WebSocket-Accept"] = resp_key

if haskey(request.headers, "Sec-WebSocket-Protocol")
if hasprotocol(request.headers["Sec-WebSocket-Protocol"])
response.headers["Sec-WebSocket-Protocol"] = request.headers["Sec-WebSocket-Protocol"]
else
Base.write(client.sock, HttpServer.Response(400))
return false
end
end

Base.write(client.sock, response)
return true
end

""" Implement the WebSocketInterface, for compatilibility with HttpServer."""
struct WebSocketHandler <: HttpServer.WebSocketInterface
handle::Function
end

"""
Performs handshake. If successfull, establishes WebSocket type and calls
handler with the WebSocket and the original request. On exit from handler, closes websocket. No return value.
"""
function HttpServer.handle(handler::WebSocketHandler, req::HttpServer.Request, client::HttpServer.Client)
websocket_handshake(req, client) || return
sock = WebSocket(client.sock,true)
handler.handle(req, sock)
if isopen(sock)
try
close(sock)
end
end
end

function HttpServer.is_websocket_handshake(handler::WebSocketHandler, req::HttpServer.Request)
is_get = req.method == "GET"
# "upgrade" for Chrome and "keep-alive, upgrade" for Firefox.
is_upgrade = contains(lowercase(get(req.headers, "Connection", "")),"upgrade")
is_websockets = lowercase(get(req.headers, "Upgrade", "")) == "websocket"
return is_get && is_upgrade && is_websockets
end
Loading