From f14b20c073eca4a373d16750296fa6cb624cf94a Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Thu, 8 Aug 2024 23:57:46 +0300 Subject: [PATCH 1/8] Add Bonito connection extension --- src/extensions/load.jl | 6 +- src/extensions/plotting/bonito_connection.jl | 104 +++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/extensions/plotting/bonito_connection.jl diff --git a/src/extensions/load.jl b/src/extensions/load.jl index 1b6f12f4..af7a817a 100644 --- a/src/extensions/load.jl +++ b/src/extensions/load.jl @@ -43,7 +43,11 @@ function __init__() # Plotting Extensions # ################################################################ @require CairoMakie="13f3f980-e62b-5c42-98c6-ff1f3baf88f0" include("plotting/cairomakie.jl") - @require Bonito="824d6782-a2ef-11e9-3a09-e5662e0c26f8" include("plotting/bonito.jl") + @require Bonito="824d6782-a2ef-11e9-3a09-e5662e0c26f8" begin + @info "Loading Bonito plotting extension..." + include("plotting/bonito.jl") + include("plotting/bonito_connection.jl") + end @require WGLMakie="276b4fcb-3e11-5398-bf8b-a0c2d153d008" begin @require Bonito="824d6782-a2ef-11e9-3a09-e5662e0c26f8" begin include("plotting/wglmakie.jl") diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl new file mode 100644 index 00000000..80636252 --- /dev/null +++ b/src/extensions/plotting/bonito_connection.jl @@ -0,0 +1,104 @@ +using HTTP.WebSockets: WebSocket, WebSocketError +using HTTP.WebSockets: receive, isclosed +using HTTP.WebSockets + +import .Bonito: setup_connection +using .Bonito +using .Bonito: FrontendConnection, Websocket, process_message, save_read, save_write, Session, js_str + +export OxygenWebSocketConnection, bonito_websocket_handler + +mutable struct OxygenWebSocketConnection <: FrontendConnection + endpoint::String + socket::Union{Nothing,WebSocket} + lock::ReentrantLock + session::Union{Nothing,Session} +end + +const open_connections = Dict{String, Session{OxygenWebSocketConnection}}() +const open_connections_lock = ReentrantLock() + +function OxygenWebSocketConnection(endpoint::String) + return OxygenWebSocketConnection(endpoint, nothing, ReentrantLock(), nothing) +end + +function Base.isopen(ws::OxygenWebSocketConnection) + isnothing(ws.socket) && return false + # isclosed(ws.socket) returns readclosed && writeclosed + # but we consider it closed if either is closed? + if ws.socket.readclosed || ws.socket.writeclosed + return false + end + # So, it turns out, ws connection where the tab gets closed + # stay open indefinitely, but aren't writable anymore + # TODO, figure out how to check for that + return true +end + +function Base.write(ws::OxygenWebSocketConnection, binary) + if isnothing(ws.socket) + error("socket closed or not opened yet") + end + lock(ws.lock) do + written = save_write(ws.socket, binary) + if written != true + @debug "couldnt write, closing ws" + close(ws) + end + end +end + +function Base.close(ws::OxygenWebSocketConnection) + isnothing(ws.socket) && return + try + socket = ws.socket + ws.socket = nothing + isclosed(socket) || close(socket) + catch e + if !WebSockets.isok(e) + @warn "error while closing websocket" exception=(e, Base.catch_backtrace()) + end + end +end + +#@websocket "/bonito/{session_id}" function(websocket::HTTP.WebSocket, session_id::String) +#Oxygen.Core.register(CONTEXT[], WEBSOCKET, path, func) +function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) + session = nothing + lock(open_connections_lock) do + session = open_connections[session_id] + end + connection = session.connection + lock(connection.lock) do + connection.socket = websocket + end + + # the channel is used so that we can do async processing of messages + # While still keeping the order of messages + @debug("opening ws connection for session: $(session.id)") + while !isclosed(websocket) + bytes = save_read(websocket) + # nothing means the browser closed the connection so we're done + isnothing(bytes) && break + try + process_message(session, bytes) + catch e + # Only print any internal error to not close the connection + @warn "error while processing received msg" exception = (e, Base.catch_backtrace()) + end + end +end + + +function setup_connection(session::Session{OxygenWebSocketConnection}) + session_id = session.id + lock(open_connections_lock) do + open_connections[session_id] = session + end + proxy_url = session.connection.endpoint + return js""" + $(Websocket).then(WS => { + WS.setup_connection({proxy_url: $(proxy_url), session_id: $(session.id), compression_enabled: $(session.compression_enabled)}) + }) + """ +end From 7ce475e5a471cad7618737ec5d8db33ceef1a526 Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Mon, 19 Aug 2024 17:17:45 +0300 Subject: [PATCH 2/8] Use WebSocket handler from Oxygen --- src/extensions/plotting/bonito_connection.jl | 73 +++----------------- 1 file changed, 10 insertions(+), 63 deletions(-) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 80636252..9a147bdb 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -4,62 +4,26 @@ using HTTP.WebSockets import .Bonito: setup_connection using .Bonito -using .Bonito: FrontendConnection, Websocket, process_message, save_read, save_write, Session, js_str +using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js, run_connection_loop export OxygenWebSocketConnection, bonito_websocket_handler mutable struct OxygenWebSocketConnection <: FrontendConnection endpoint::String - socket::Union{Nothing,WebSocket} - lock::ReentrantLock session::Union{Nothing,Session} + handler::WebSocketHandler end const open_connections = Dict{String, Session{OxygenWebSocketConnection}}() const open_connections_lock = ReentrantLock() function OxygenWebSocketConnection(endpoint::String) - return OxygenWebSocketConnection(endpoint, nothing, ReentrantLock(), nothing) + return OxygenWebSocketConnection(endpoint, nothing, WebSocketHandler()) end -function Base.isopen(ws::OxygenWebSocketConnection) - isnothing(ws.socket) && return false - # isclosed(ws.socket) returns readclosed && writeclosed - # but we consider it closed if either is closed? - if ws.socket.readclosed || ws.socket.writeclosed - return false - end - # So, it turns out, ws connection where the tab gets closed - # stay open indefinitely, but aren't writable anymore - # TODO, figure out how to check for that - return true -end - -function Base.write(ws::OxygenWebSocketConnection, binary) - if isnothing(ws.socket) - error("socket closed or not opened yet") - end - lock(ws.lock) do - written = save_write(ws.socket, binary) - if written != true - @debug "couldnt write, closing ws" - close(ws) - end - end -end - -function Base.close(ws::OxygenWebSocketConnection) - isnothing(ws.socket) && return - try - socket = ws.socket - ws.socket = nothing - isclosed(socket) || close(socket) - catch e - if !WebSockets.isok(e) - @warn "error while closing websocket" exception=(e, Base.catch_backtrace()) - end - end -end +Base.isopen(ws::OxygenWebSocketConnection) = isopen(ws.handler) +Base.write(ws::OxygenWebSocketConnection, binary) = write(ws.handler, binary) +Base.close(ws::OxygenWebSocketConnection) = close(ws.handler) #@websocket "/bonito/{session_id}" function(websocket::HTTP.WebSocket, session_id::String) #Oxygen.Core.register(CONTEXT[], WEBSOCKET, path, func) @@ -68,25 +32,12 @@ function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) lock(open_connections_lock) do session = open_connections[session_id] end - connection = session.connection - lock(connection.lock) do - connection.socket = websocket - end + handler = session.connection.handler # the channel is used so that we can do async processing of messages # While still keeping the order of messages @debug("opening ws connection for session: $(session.id)") - while !isclosed(websocket) - bytes = save_read(websocket) - # nothing means the browser closed the connection so we're done - isnothing(bytes) && break - try - process_message(session, bytes) - catch e - # Only print any internal error to not close the connection - @warn "error while processing received msg" exception = (e, Base.catch_backtrace()) - end - end + run_connection_loop(session, handler, websocket) end @@ -95,10 +46,6 @@ function setup_connection(session::Session{OxygenWebSocketConnection}) lock(open_connections_lock) do open_connections[session_id] = session end - proxy_url = session.connection.endpoint - return js""" - $(Websocket).then(WS => { - WS.setup_connection({proxy_url: $(proxy_url), session_id: $(session.id), compression_enabled: $(session.compression_enabled)}) - }) - """ + external_url = session.connection.endpoint + return setup_websocket_connection_js(external_url, session) end From ea57f09bf145098c28fe8abd7f3928bced6d3a7b Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Thu, 5 Sep 2024 11:04:06 +0300 Subject: [PATCH 3/8] Add docs for OxygenWebSocketConnection and setup_bonito_connection(...) helper function --- docs/src/index.md | 46 ++++++++++ src/context.jl | 1 + src/extensions/plotting/bonito_connection.jl | 96 +++++++++++++++----- 3 files changed, 121 insertions(+), 22 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 7e6798d1..e9000350 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -827,6 +827,52 @@ end serve() ``` +In case you need the client side component, Bonito needs an asset server. A convenient option here is to use `NoServer`, which tells Bonito to include all assets inline in the html. + +```julia +force_asset_server!(NoServer()) +``` + +In case you need to server side component, Bonito will use its own `WebSocketConnection` by default. In this case Bonito will start another HTTP server on another port and serve. While this may work fine locally, in production you will typically want to have the websocket endpoint served by Oxygen so that it will be easily handled by any infastructure you have configured such as reverse proxies, load balancers, and firewall. For this case, you can use `OxygenWebSocketConnection`. + +*Note that the following part of the Bonito instructions is currently experimental and the API may change outside normal semver guarantees.* + +In order to make use the following functions defined in the Bonito extension, you must load `Bonito` before first loading `Oxygen`. + +```julia +using Bonito +using Oxygen +''' + +There are two parts to integrating Bonito with Oxygen: + + 1. Registering a websocket handler to pass requests to Bonito's ahndler + 3. Forcing Bonito to use this connection object `OxygenWebSocketConnection` object which points to the correct Oxygen context and URL + +To do both you can simply use the following command: + +```julia +function __init__() + # This must be done in __init__ so that functions from the Oxygen Bonito extension are available + Oxygen.setup_bonito_connection(CONTEXT[]; setup_all=true) +end +``` + +In case you want to change the url of the Bonito websocket route, you can do so via the `route_base` keyword argument, which defaults to `"/bonito_websocket/"`. + +You may like to add some custom behaviour to the route such as authentication. In this case, you will need to setup the route yourself like so: + +```julia +function __init__() + const route_base = "/foobar/" + oxygen_bonito = Oxygen.setup_bonito_connection(CONTEXT[]; setup_force_connection=true, route_base=foobar) + route_pattern = route_base * "{session_id}" + @websocket route_pattern function mybonitohandler(websocket::HTTP.WebSocket, session_id::String) + # Add custom behaviour here + oxygen_bonito.handler(websocket, session_id) + end +end +``` ## Templating diff --git a/src/context.jl b/src/context.jl index b404dc9c..2f057a57 100644 --- a/src/context.jl +++ b/src/context.jl @@ -55,6 +55,7 @@ end docs :: Documenation = Documenation() cron :: CronContext = CronContext() tasks :: TasksContext = TasksContext() + ext :: Dict{Symbol, Any} = Dict{Symbol, Any}() end Base.isopen(service::Service) = !isnothing(service.server[]) && isopen(service.server[]) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 9a147bdb..189a2cc4 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -3,49 +3,101 @@ using HTTP.WebSockets: receive, isclosed using HTTP.WebSockets import .Bonito: setup_connection -using .Bonito -using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js, run_connection_loop +using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js, run_connection_loop, force_connection! -export OxygenWebSocketConnection, bonito_websocket_handler +export OxygenWebSocketConnection, mk_bonito_websocket_handler, setup_bonito_connection mutable struct OxygenWebSocketConnection <: FrontendConnection + context::Context endpoint::String session::Union{Nothing,Session} handler::WebSocketHandler end -const open_connections = Dict{String, Session{OxygenWebSocketConnection}}() -const open_connections_lock = ReentrantLock() - -function OxygenWebSocketConnection(endpoint::String) - return OxygenWebSocketConnection(endpoint, nothing, WebSocketHandler()) +function OxygenWebSocketConnection(context::Context, endpoint::String) + return OxygenWebSocketConnection(context, endpoint, nothing, WebSocketHandler()) end Base.isopen(ws::OxygenWebSocketConnection) = isopen(ws.handler) Base.write(ws::OxygenWebSocketConnection, binary) = write(ws.handler, binary) Base.close(ws::OxygenWebSocketConnection) = close(ws.handler) -#@websocket "/bonito/{session_id}" function(websocket::HTTP.WebSocket, session_id::String) -#Oxygen.Core.register(CONTEXT[], WEBSOCKET, path, func) -function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) - session = nothing - lock(open_connections_lock) do - session = open_connections[session_id] +mutable struct BonitoConnectionContext + open_connections::Dict{String, Session{OxygenWebSocketConnection}} + open_connections_lock::ReentrantLock +end + +BonitoConnectionContext() = BonitoConnectionContext(Dict{String, Session{OxygenWebSocketConnection}}(), ReentrantLock(), DefaultCleanupPolicy(), nothing) + +""" + setup_bonito_connection( + context::Context; + setup_all=false, + setup_route=setup_all, + setup_force_connection=setup_all, + route_base="/bonito_websocket/" + ) + +This is the high level function to setup bonito connection. It will register the route and create a connection if needed. + +In the simplest use case you can simply pass `setup_bonito_connection(CONTEXT[]; setup_all=true)` and it will set up everything for you. Please see the guide for advanced usage. +""" +function setup_bonito_connection( + context::Context; + setup_all=false, + setup_route=setup_all, + setup_force_connection=setup_all, + route_base="/bonito_websocket/" +) + context.ext[:bonito_connection] = BonitoConnectionContext() + handler = mk_bonito_websocket_handler(context) + connection = OxygenWebSocketConnection(context, route_base) + if setup_route + Oxygen.Core.register(context, WEBSOCKET, route_base * "{session_id}", handler) + end + if setup_force_connection + force_connection!(connection) + end + return (; + connection, + handler + ) +end + + +function mk_bonito_websocket_handler(context::Context) + if !(:bonito_connection in keys(context.ext)) + error("bonito_connection not setup in context (did you call setup_bonito_connection(...)?)") end - handler = session.connection.handler + bonito_context = context.ext[:bonito_connection] + function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) + session = nothing + lock(bonito_context.open_connections_lock) do + session = bonito_context.open_connections[session_id] + end + handler = session.connection.handler - # the channel is used so that we can do async processing of messages - # While still keeping the order of messages - @debug("opening ws connection for session: $(session.id)") - run_connection_loop(session, handler, websocket) + @debug("opening ws connection for session: $(session.id)") + run_connection_loop(session, handler, websocket) + end + return bonito_websocket_handler end function setup_connection(session::Session{OxygenWebSocketConnection}) + if !(:bonito_connection in keys(session.connection.context.ext)) + error("bonito_connection not setup in context (did you call setup_bonito_connection(...)?)") + end + context = session.connection.context + bonito_context = context.ext[:bonito_connection] + if context.service.external_url[] == nothing + error("external_url not set in context (did you call start the server yet?)") + end + external_url_base = context.service.external_url[] session_id = session.id - lock(open_connections_lock) do - open_connections[session_id] = session + lock(bonito_context.open_connections_lock) do + bonito_context.open_connections[session_id] = session end - external_url = session.connection.endpoint + external_url = external_url_base * session.connection.endpoint return setup_websocket_connection_js(external_url, session) end From 26cbd4e374fbd169ec2a09d728aa189fedc3de1c Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Thu, 5 Sep 2024 13:46:49 +0300 Subject: [PATCH 4/8] Add cleanup logic to bonito connection --- src/extensions/plotting/bonito_connection.jl | 72 +++++++++++++++++--- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 189a2cc4..451ea1a7 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -3,7 +3,9 @@ using HTTP.WebSockets: receive, isclosed using HTTP.WebSockets import .Bonito: setup_connection -using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js, run_connection_loop, force_connection! +using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js +using .Bonito: run_connection_loop, force_connection! +using .Bonito: CleanupPolicy, DefaultCleanupPolicy, allow_soft_close, soft_close, should_cleanup export OxygenWebSocketConnection, mk_bonito_websocket_handler, setup_bonito_connection @@ -23,11 +25,13 @@ Base.write(ws::OxygenWebSocketConnection, binary) = write(ws.handler, binary) Base.close(ws::OxygenWebSocketConnection) = close(ws.handler) mutable struct BonitoConnectionContext + cleanup_policy::CleanupPolicy open_connections::Dict{String, Session{OxygenWebSocketConnection}} - open_connections_lock::ReentrantLock + cleanup_task::Union{Task, Nothing} + lock::ReentrantLock end -BonitoConnectionContext() = BonitoConnectionContext(Dict{String, Session{OxygenWebSocketConnection}}(), ReentrantLock(), DefaultCleanupPolicy(), nothing) +BonitoConnectionContext(policy=DefaultCleanupPolicy()) = BonitoConnectionContext(policy, Dict{String, Session{OxygenWebSocketConnection}}(), nothing, ReentrantLock()) """ setup_bonito_connection( @@ -71,32 +75,78 @@ function mk_bonito_websocket_handler(context::Context) end bonito_context = context.ext[:bonito_connection] function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) - session = nothing - lock(bonito_context.open_connections_lock) do + local session + lock(bonito_context.lock) do session = bonito_context.open_connections[session_id] end handler = session.connection.handler @debug("opening ws connection for session: $(session.id)") - run_connection_loop(session, handler, websocket) + try + run_connection_loop(session, handler, websocket) + finally + if allow_soft_close(bonito_context.cleanup_policy) + @debug("Soft closing: $(session.id)") + soft_close(session) + else + @debug("Closing: $(session.id)") + # might as well close it immediately + close(session) + lock(bonito_context.lock) do + delete!(bonito_context.open_connections, session.id) + end + end + end end return bonito_websocket_handler end +function cleanup_bonito_context(bonito_context) + remove = Set{OxygenWebSocketConnection}() + lock(bonito_context.lock) do + for (session_id, session) in bonito_context.open_connections + if should_cleanup(bonito_context.cleanup_policy, session) + push!(remove, session.connection) + end + end + for connection in remove + if !isnothing(connection.session) + session = connection.session + delete!(bonito_context.open_connections, session.id) + close(session) + end + end + end +end + +function cleanup_loop(bonito_context) + while true + try + sleep(1) + cleanup_bonito_context(bonito_context) + catch e + if !(e isa EOFError) + @warn "error while cleaning up server" exception=(e, Base.catch_backtrace()) + end + end + end +end function setup_connection(session::Session{OxygenWebSocketConnection}) - if !(:bonito_connection in keys(session.connection.context.ext)) + context = session.connection.context + if !(:bonito_connection in keys(context.ext)) error("bonito_connection not setup in context (did you call setup_bonito_connection(...)?)") end - context = session.connection.context bonito_context = context.ext[:bonito_connection] if context.service.external_url[] == nothing error("external_url not set in context (did you call start the server yet?)") end external_url_base = context.service.external_url[] - session_id = session.id - lock(bonito_context.open_connections_lock) do - bonito_context.open_connections[session_id] = session + lock(bonito_context.lock) do + if bonito_context.cleanup_task === nothing + bonito_context.cleanup_task = Threads.@spawn cleanup_loop(bonito_context) + end + bonito_context.open_connections[session.id] = session end external_url = external_url_base * session.connection.endpoint return setup_websocket_connection_js(external_url, session) From 562a2fbd29797f624e52d4b589fa7a3b2e2e3333 Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Fri, 11 Oct 2024 15:25:26 +0300 Subject: [PATCH 5/8] Store connections rather than sessions on Bonito context This makes the code mirror Bonito's open more closely --- src/extensions/plotting/bonito_connection.jl | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 451ea1a7..6e6b80bd 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -26,7 +26,7 @@ Base.close(ws::OxygenWebSocketConnection) = close(ws.handler) mutable struct BonitoConnectionContext cleanup_policy::CleanupPolicy - open_connections::Dict{String, Session{OxygenWebSocketConnection}} + open_connections::Dict{String, OxygenWebSocketConnection} cleanup_task::Union{Task, Nothing} lock::ReentrantLock end @@ -75,11 +75,12 @@ function mk_bonito_websocket_handler(context::Context) end bonito_context = context.ext[:bonito_connection] function bonito_websocket_handler(websocket::HTTP.WebSocket, session_id::String) - local session + local connection lock(bonito_context.lock) do - session = bonito_context.open_connections[session_id] + connection = bonito_context.open_connections[session_id] end - handler = session.connection.handler + session = connection.session + handler = connection.handler @debug("opening ws connection for session: $(session.id)") try @@ -104,9 +105,9 @@ end function cleanup_bonito_context(bonito_context) remove = Set{OxygenWebSocketConnection}() lock(bonito_context.lock) do - for (session_id, session) in bonito_context.open_connections - if should_cleanup(bonito_context.cleanup_policy, session) - push!(remove, session.connection) + for (session_id, connection) in bonito_context.open_connections + if should_cleanup(bonito_context.cleanup_policy, connection.session) + push!(remove, connection) end end for connection in remove @@ -132,8 +133,9 @@ function cleanup_loop(bonito_context) end end -function setup_connection(session::Session{OxygenWebSocketConnection}) - context = session.connection.context +function setup_connection(session::Session, connection::OxygenWebSocketConnection) + connection.session = session + context = connection.context if !(:bonito_connection in keys(context.ext)) error("bonito_connection not setup in context (did you call setup_bonito_connection(...)?)") end @@ -146,8 +148,12 @@ function setup_connection(session::Session{OxygenWebSocketConnection}) if bonito_context.cleanup_task === nothing bonito_context.cleanup_task = Threads.@spawn cleanup_loop(bonito_context) end - bonito_context.open_connections[session.id] = session + bonito_context.open_connections[session.id] = connection end external_url = external_url_base * session.connection.endpoint return setup_websocket_connection_js(external_url, session) end + +function setup_connection(session::Session{OxygenWebSocketConnection}) + return setup_connection(session, session.connection) +end From c51812dffd1e1ac492187a7a7d0d62ddba1ecb5c Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Fri, 11 Oct 2024 15:26:30 +0300 Subject: [PATCH 6/8] Use strict comparison with nothing in setup_connection --- src/extensions/plotting/bonito_connection.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 6e6b80bd..519f5a0f 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -140,7 +140,7 @@ function setup_connection(session::Session, connection::OxygenWebSocketConnectio error("bonito_connection not setup in context (did you call setup_bonito_connection(...)?)") end bonito_context = context.ext[:bonito_connection] - if context.service.external_url[] == nothing + if context.service.external_url[] === nothing error("external_url not set in context (did you call start the server yet?)") end external_url_base = context.service.external_url[] From aecd5bba1652c28881a8fc3a9d8c1d60492b7d64 Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Fri, 11 Oct 2024 15:31:27 +0300 Subject: [PATCH 7/8] Use register connection rather than force_connection Otherwise we can only have one connection(!) >_< --- docs/src/index.md | 2 +- src/extensions/plotting/bonito_connection.jl | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index e9000350..9db21838 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -865,7 +865,7 @@ You may like to add some custom behaviour to the route such as authentication. I ```julia function __init__() const route_base = "/foobar/" - oxygen_bonito = Oxygen.setup_bonito_connection(CONTEXT[]; setup_force_connection=true, route_base=foobar) + oxygen_bonito = Oxygen.setup_bonito_connection(CONTEXT[]; setup_register_connection=true, route_base=route_base) route_pattern = route_base * "{session_id}" @websocket route_pattern function mybonitohandler(websocket::HTTP.WebSocket, session_id::String) # Add custom behaviour here diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 519f5a0f..806fd3f8 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -4,7 +4,7 @@ using HTTP.WebSockets import .Bonito: setup_connection using .Bonito: FrontendConnection, WebSocketHandler, Session, setup_websocket_connection_js -using .Bonito: run_connection_loop, force_connection! +using .Bonito: run_connection_loop, register_connection! using .Bonito: CleanupPolicy, DefaultCleanupPolicy, allow_soft_close, soft_close, should_cleanup export OxygenWebSocketConnection, mk_bonito_websocket_handler, setup_bonito_connection @@ -38,7 +38,7 @@ BonitoConnectionContext(policy=DefaultCleanupPolicy()) = BonitoConnectionContext context::Context; setup_all=false, setup_route=setup_all, - setup_force_connection=setup_all, + setup_register_connection=setup_all, route_base="/bonito_websocket/" ) @@ -50,20 +50,22 @@ function setup_bonito_connection( context::Context; setup_all=false, setup_route=setup_all, - setup_force_connection=setup_all, - route_base="/bonito_websocket/" + setup_register_connection=setup_all, + route_base="/bonito-websocket/" ) context.ext[:bonito_connection] = BonitoConnectionContext() handler = mk_bonito_websocket_handler(context) - connection = OxygenWebSocketConnection(context, route_base) if setup_route Oxygen.Core.register(context, WEBSOCKET, route_base * "{session_id}", handler) end - if setup_force_connection - force_connection!(connection) + function mk_connection() + return OxygenWebSocketConnection(context, route_base) + end + if setup_register_connection + register_connection!(mk_connection, OxygenWebSocketConnection) end return (; - connection, + mk_connection, handler ) end From 4ffc28de0b61ee84b957488e10b659fa80b88913 Mon Sep 17 00:00:00 2001 From: Frankie Robertson Date: Sun, 13 Oct 2024 13:23:29 +0300 Subject: [PATCH 8/8] Make Bonito/WGLMakie offline default configurable --- src/extensions/plotting/bonito.jl | 10 +++++++--- src/extensions/plotting/bonito_connection.jl | 1 + src/extensions/plotting/wglmakie.jl | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/extensions/plotting/bonito.jl b/src/extensions/plotting/bonito.jl index 15b9a7fc..f2059e67 100644 --- a/src/extensions/plotting/bonito.jl +++ b/src/extensions/plotting/bonito.jl @@ -4,12 +4,16 @@ import .Bonito: Page, App export html + +const BONITO_OFFLINE::Ref{Bool} = Ref(true) + + """ Converts a Figure object to the designated MIME type and wraps it inside an HTTP response. """ -function response(content::App, mime_type::MIME, status::Int, headers::Vector) +function response(content::App, mime_type::MIME, status::Int, headers::Vector, offline=BONITO_OFFLINE[]) # Force inlining all data & js dependencies - Page(exportable=true, offline=true) + Page(exportable=true, offline=offline) # Convert & load the figure into an IOBuffer io = IOBuffer() @@ -29,4 +33,4 @@ end Convert a Bonito.App to HTML and wrap it inside an HTTP response. """ -html(app::App, status=200, headers=[]) :: HTTP.Response = response(app, HTML, status, headers) +html(app::App, status=200, headers=[], offline=BONITO_OFFLINE[]) :: HTTP.Response = response(app, HTML, status, headers, offline) diff --git a/src/extensions/plotting/bonito_connection.jl b/src/extensions/plotting/bonito_connection.jl index 806fd3f8..432fc2c5 100644 --- a/src/extensions/plotting/bonito_connection.jl +++ b/src/extensions/plotting/bonito_connection.jl @@ -53,6 +53,7 @@ function setup_bonito_connection( setup_register_connection=setup_all, route_base="/bonito-websocket/" ) + BONITO_OFFLINE[] = false context.ext[:bonito_connection] = BonitoConnectionContext() handler = mk_bonito_websocket_handler(context) if setup_route diff --git a/src/extensions/plotting/wglmakie.jl b/src/extensions/plotting/wglmakie.jl index 975a68fb..57cf5301 100644 --- a/src/extensions/plotting/wglmakie.jl +++ b/src/extensions/plotting/wglmakie.jl @@ -8,9 +8,9 @@ export html """ Converts a Figure object to the designated MIME type and wraps it inside an HTTP response. """ -function response(content::FigureLike, mime_type::MIME, status::Int, headers::Vector) +function response(content::FigureLike, mime_type::MIME, status::Int, headers::Vector, offline=BONITO_OFFLINE[]) # Force inlining all data & js dependencies - Page(exportable=true, offline=true) + Page(exportable=true, offline=offline) # Convert & load the figure into an IOBuffer io = IOBuffer() @@ -30,5 +30,5 @@ end Convert a Makie figure to HTML and wrap it inside an HTTP response. """ -html(fig::FigureLike, status=200, headers=[]) :: HTTP.Response = response(fig, HTML, status, headers) +html(fig::FigureLike, status=200, headers=[], offline=BONITO_OFFLINE[]) :: HTTP.Response = response(fig, HTML, status, headers, offline)