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

WebSockets: A server must not mask any frames that it sends to the client. #176

Closed
EricForgy opened this issue Jan 25, 2018 · 4 comments
Closed

Comments

@EricForgy
Copy link
Contributor

Hi,

I've almost got WebSockets working, i.e. communicating between Julia and a browser.

I load a page into a browser with a JS library that connects to a Julia WebSocket server. I can send messages from the browser to Julia, so I know the Julia WebSocket server and the JS library are working. However, when I try to send something from Julia to the browser, e.g.

julia> write(ws,"Hello")

I get the following error in the dev console:

pages.js:17 WebSocket connection to 'ws://localhost:8000/examples/plot.ly' failed: A server must not mask any frames that it sends to the client.

Any idea what that means? Any idea how to fix it?

@EricForgy
Copy link
Contributor Author

EricForgy commented Jan 26, 2018

Here is my attempt at a MWE:

mwe.jl

using HTTP, JSON

function is_upgrade(req::HTTP.Request)
    is_get = req.method == "GET"
    # "upgrade" for Chrome and "keep-alive, upgrade" for Firefox.
    is_upgrade = HTTP.hasheader(req, "Connection", "upgrade")
    is_websockets = HTTP.hasheader(req, "Upgrade", "websocket")
    return is_get && is_upgrade && is_websockets
end

@async HTTP.listen(ip"127.0.0.1",8000) do http
    if is_upgrade(http.message)
        HTTP.WebSockets.upgrade(http) do client
            while !eof(client);
                msg = JSON.parse(String(readavailable(client)))
                println(msg)
                write(client, "Hello JavaScript! From Julia")
            end
        end
    else
        HTTP.Servers.handle_request(http) do req::HTTP.Request
            HTTP.Response(200,readstring(joinpath(dirname(@__FILE__),"mwe.html")))
        end
    end
end

@static if is_apple()
    launch(x) = run(`open $x`)
elseif is_linux()
    launch(x) = run(`xdg-open $x`)
elseif is_windows()
    launch(x) = run(`cmd /C start $x`)
end

sleep(2.0)
launch("http://127.0.0.1:8000/examples/mwe")

mwe.html

<!DOCTYPE html>
<html>
<body>

<p>Check the dev console.</p>

<script>

var href = window.location.href;
var sock = new WebSocket("ws"+href.substr(4,href.length));
sock.onmessage = function( message ){
    var msg = JSON.parse(message.data);
    console.log(msg);
}

sock.onopen = function () {
    sock.send(JSON.stringify("Hello Julia! From JavaScript"))
};

</script>

</body>
</html>

This example will start a server and launch a browser that reads mwe.html.

Inside mwe.html, it connects back to the Julia server and sends a message: "Hello Julia! From JavaScript".

The Julia server receives the message, prints it to the REPL and sends a message back: "Hello JavaScript! From Julia".

The output on the REPL looks like this:

julia> include(joinpath(Pkg.dir("Pages"),"examples","mwe.jl"));
I- Listening on: 127.0.0.1:8000

julia> I- Accept:  �    0↑     0↓    127.0.0.1:8000:8000 ≣16
I- Accept:  �    0↑     0↓    127.0.0.1:8000:8000 ≣16
Hello Julia! From JavaScript
E- HTTP.WebSockets.WebSocketError(14626, "r]IERROR (unhandled task failure): UnicodeError: invalid character index 4 (0xa3 is a continuation byte)
Stacktrace:
 [1] slow_utf8_next(::Ptr{UInt8}, ::UInt8, ::Int64, ::Int64) at .\strings\string.jl:172
 [2] next at .\strings\string.jl:204 [inlined]
 [3] escape_string(::IOContext{Base.TTY}, ::String, ::String) at .\strings\io.jl:235
 [4] print_quoted at .\strings\io.jl:253 [inlined]
 [5] show(::IOContext{Base.TTY}, ::String) at .\strings\io.jl:124
 [6] show_default(::Base.TTY, ::Any) at .\show.jl:140
 [7] print(::Base.TTY, ::HTTP.WebSockets.WebSocketError) at .\strings\io.jl:29
 [8] print(::Base.TTY, ::String, ::HTTP.WebSockets.WebSocketError, ::Vararg{Any,N} where N) at .\strings\io.jl:40
 [9] macro expansion at C:\Users\Eric\.julia\v0.6\HTTP\src\Servers.jl:436 [inlined]
 [10] (::HTTP.Servers.##46#47{##4#8,HTTP.ConnectionPool.Transaction{TCPSocket},HTTP.Streams.Stream{HTTP.Messages.Request,HTTP.ConnectionPool.Transaction{TCPSocket}}})() at .\task.jl:335

So Julia successfully serves mwe.html and receives the websocket message back FROM the browser, but fails to send the websocket message TO the browser.

In the browser console, it says:

mwe:10 WebSocket connection to 'ws://127.0.0.1:8000/examples/mwe' failed: A server must not mask any frames that it sends to the client.
(anonymous) @ mwe:10

Any ideas?

@EricForgy
Copy link
Contributor Author

EricForgy commented Jan 26, 2018

I'm learning WAY more about WebSockets than I ever wanted (or can afford) to know 😂

However, reading more, I see that messages sent from client to server should be masked, but messages sent from server to client should not be masked. Is this correct? I suspect this is my problem.

write(ws,"Hello")

from the Julia server is getting masked. What is the solution?

Update: The relevant code is here:

https://github.com/JuliaWeb/HTTP.jl/blob/master/src/WebSockets.jl#L184-L207

Need to figure out how to NOT mask when sent from server to client.

Update^2: The type def for WebSocket includes a field server.

mutable struct WebSocket{T <: IO} <: IO
    io::T
    frame_type::UInt8
    server::Bool
    rxpayload::Vector{UInt8}
    txpayload::Vector{UInt8}
    txclosed::Bool
    rxclosed::Bool
end

If server=true, then we should not mask the transmission.

@EricForgy
Copy link
Contributor Author

EricForgy commented Jan 26, 2018

I'm making a little progress. I can now get binary data from Julia into a browser, but not having luck with text. This is the small change I made to wswrite:

function wswrite(ws::WebSocket, opcode::UInt8, bytes::Vector{UInt8})

    n = length(bytes)
    len, extended_len = wslength(n)
    if !ws.server #                                         <<====  Added this if statement
        len |= WS_MASK
        mask = mask!(ws.txpayload, bytes, n)
    else
        mask = Uint8(0)
    end

    @debug 1 "WebSocket ⬅️  $(WebSocketHeader(opcode, len, extended_len, mask))"
    write(ws.io, opcode, len, extended_len, mask)

    @debug 2 "          ⬅️  $(ws.txpayload[1:n])"
    unsafe_write(ws.io, pointer(ws.txpayload), n)
end

If I set binary=true, I can get a blob into the browser, but if binary=false, I get the following error in the browser dev console:

mwe:10 WebSocket connection to 'ws://127.0.0.1:8000/examples/mwe' failed: Could not decode a text frame as UTF-8.
(anonymous) @ mwe:10

samoconnor added a commit that referenced this issue Jan 30, 2018
@EricForgy
Copy link
Contributor Author

Closed via f576d4d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant