Extending Bonito
Connection
By default, Bonito uses its own WebSocket server to create the connection between Julia and JavaScript. By extending Bonito.FrontendConnection
, you can create a new type of connection, e.g. using WebRTC to connect Julia and JavaScript.
Your new connection type should support bidirectional messages of binary data.
mutable struct MyConnection <: Bonito.FrontendConnection
# TODO: your implementation here
isopen::Bool
socket
blabla
...
end
function MyConnection()
# If you need to do something like start an HTTP server, you can do it here, synchronously.
return new(
true,
...
)
end
function Base.write(connection::MyConnection, binary)
# TODO: send the data to JavaScript
write(connection.socket, binary)
end
Base.isopen(connection::MyConnection) = connection.isopen
Base.close(connection::MyConnection) = (connection.isopen = false)
open!(connection::MyConnection) = (connection.isopen = true)
function setup_connection(session::Session{MyConnection})
# If you need to do something specific for this new session, you can do it here.
return js"""
// TODO: create a connection
create_connection(...).then((conn) => {
// TODO: when your connection receives a message from Julia, relay the message to `Bonito.process_message(msg)`.
conn.on_msg((msg) => {
Bonito.process_message(msg)
});
// TODO: you need to define a JavaScript function that sends a given `binary_data` to Julia. On the Julia side, this should call `Bonito.process_message(connection.parent, binary_data)`.
const send_to_julia = (binary_data) => conn.send(binary)
// TODO: does your connection use Pako compression on its incoming and outgoing messages?
const compression_enabled = false
// Register your new connection
Bonito.on_connection_open(send_to_julia, compression_enabled);
})
"""
end
You can test your new connection like so:
Bonito.register_connection!(MyConnection) do
# you can make this registration conditional, e.g. only use the new connection type on Thursdays...
if i_want_to_use_it
return MyConnection()
else
return nothing
end
end
Managing your own WebSocket server
You should also extend Bonito.FrontendConnection
in case you would like to make a WebSocket-based connection, but manage the server yourself, e.g. by registering a websocket route using a web framework. In this case, you can reuse some of Bonito's websocket handling code as long as your websocket is internally using HTTP.WebSockets.WebSocket
objects.
You can do this using the WebSocketHandler
type and forwarding your connection type's isopen
, write
, and close
methods to it. You can then use the setup_websocket_connection_js
to return the correct Javascript snippet in your setup_connection
method and run the main I/O loop by calling run_connection_loop
from your websocket handler. You will need to take care of saving the session in your setup_connection
method and routing to the session in your handler, as well as cleaning up websocket sessions. You may need to use locks to accomplish some of these steps in a thread-safe manner.
mutable struct MyWebSocketConnection <: Bonito.FrontendConnection
# TODO: your implementation here
blabla
...
handler::WebSocketHandler
end
Base.isopen(ws::MyWebSocketConnection) = isopen(ws.handler)
Base.write(ws::MyWebSocketConnection, binary) = write(ws.handler, binary)
Base.close(ws::MyWebSocketConnection) = close(ws.handler)
function setup_connection(session::Session{MyWebSocketConnection})
return setup_connection(session, session.connection)
# TODO: Register the `session.id` as a route, and save it so it can be retrived in the handler
# You could alternatively register a single route and multiplex using a dictionary
# TODO: Start a cleanup task to remove routes/saved sessions when they are no longer open
external_url = # TODO: Get the external URL, which session IDs will be appended to
return setup_websocket_connection_js(external_url, session)
end
function my_web_framework_websocket_handler(my_web_framework_request)
session = # TODO: retrieve the session based upon the session ID in the URL in `my_web_framework_request`
websocket = # TODO: set up the websocket or retrive it from `my_web_framework_request`
connection = session.connection
try
run_connection_loop(session, connection.handler, websocket)
finally
# TODO: Option 1: Immediately end the session with `close(session)`
# You will also need to clean up the route/saved session
# TODO: Option 2: Use `soft_close(session)`
# This may allow for a temporary disconnected client to reconnect
# You may have to prevent your webframework from trying to close the websocket
# You will need to clean up the soft closed session and its route/saved session after some timeout in the cleanup task
end
end
# Here is the cleanup task. You might choose to arrange for exactly one of these to be started at some appropriate time.
# You may like to handle errors so that it always remains running.
@async while true
sleep(1)
# Iterate through the sessions and routes that have been set up in `setup_connection`
for (session, route) in my_route_data_structure
# Apply some cleanup policy to closed or soft closed sessions
if should_cleanup(session)
# mark route for removeal
end
end
# remove marked routes
end
You can get further details to guide your implementation by looking at Bonito's own implementation in Bonito/src/connections/websocket.jl
.
Please read the next section for information about how to make use of Bonito's websocket cleanup policy in your cleanup task.
Customising the websocket cleanup policy
You can create a custom cleanup policy by subclassing CleanupPolicy
. For example, you may like to choose to evict sessions based upon the resources they are using, or whether users are authenticated or not or some other criteria.
Implementing the should_cleanup
and allow_soft_close
methods is required.
struct MyCleanupPolicy <: Bonito.CleanupPolicy end
function Bonito.should_cleanup(policy::MyCleanupPolicy, session::Session)
...
end
function Bonito.allow_soft_close(policy::MyCleanupPolicy)
...
end
Bonito.set_cleanup_policy!(MyCleanupPolicy())
You can also make use of cleanup policies including DefaultCleanupPolicy()
if you manage your own websocket server as outlined in the previous section.
You can get further details to guide your implementation by looking at Bonito's own implementation in Bonito/src/connections/websocket.jl
.