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

feature/cairo-makie-integration #170

Merged
merged 16 commits into from
Mar 4, 2024
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
12 changes: 7 additions & 5 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
DataStructures = "^0.18.15"
CairoMakie = "0.11, 1"
DataStructures = "0.18.15, 1"
Dates = "^1"
HTTP = "^1"
JSON3 = "^1.9"
MIMEs = "^0.1.4"
MIMEs = "0.1.4, 1"
Mustache = "^1"
OteraEngine = "^0"
Pkg = "^1"
Expand All @@ -31,16 +32,17 @@ Requires = "^1"
Sockets = "^1"
Statistics = "^1"
StructTypes = "^1"
Suppressor = "^0.2.6"
Suppressor = "0.2.6, 1"
julia = "^1.6"

[extras]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
OteraEngine = "b2d7f28f-acd6-4007-8b26-bc27716e5513"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Mustache", "OteraEngine", "Pkg", "StructTypes", "Test", "Suppressor"]
test = ["CairoMakie", "Mustache", "OteraEngine", "Pkg", "StructTypes", "Test", "Suppressor"]
45 changes: 18 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Breathe easy knowing you can quickly spin up a web server with abstractions you'
- Middleware chaining (at the application, router, and route levels)
- Static & Dynamic file hosting
- Templating Support
- Plotting Support
- Route tagging
- Repeat tasks

Expand Down Expand Up @@ -536,7 +537,24 @@ end
# start the web server in parallel mode
serveparallel()
```
## Plotting Support

Oxygen has a package extension for the CairoMakie.jl library that helps us return Figures directly from a request handler while keeping all operations in-memory. Each helper function will read the figure into an IOBuffer, set the content type, and return the binary data inside a `HTTP.Response`.

Here are the currently supported helper utils: `png`, `svg`, `pdf`, `html`

```julia
using CairoMakie: heatmap
using Oxygen

# generate a random heatmap plot and return it as a png
@get "/plot/png" function()
fig, ax, pl = heatmap(rand(50, 50))
png(fig)
end

serve()
```

## Templating

Expand Down Expand Up @@ -886,33 +904,6 @@ mergeschema(
)
```

# Common Issues & Tips

## Problems working with Julia's REPL

This is a recurring issue that occurs when writing and testing code in the REPL. Often, people find that their changes are not reflected when they rerun the server. The reason for this is that all the routing utilities are defined as macros, and they are only executed during the precompilation stage. To have your changes take effect, you need to move your route declarations to the `__init__()` function in your module.

```julia
module OxygenExample
using Oxygen
using HTTP

# is called whenever you load this module
function __init__()
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end

# you can call this function from the REPL to start the server
function runserver()
serve()
end

end
```



# API Reference (macros)

Expand Down
33 changes: 33 additions & 0 deletions demo/CairoMakieDemo.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module CairoMakieDemo
using CairoMakie: heatmap
using Oxygen: text # CairoMakie also exports text
using Oxygen

get("/") do
text("welcome to the random plot api!")
end

# generate a random plot
get("/plot/png") do
fig, ax, pl = heatmap(rand(50, 50)) # or something
png(fig)
end

get("/plot/svg") do
fig, ax, pl = heatmap(rand(50, 50)) # or something
svg(fig)
end

get("/plot/pdf") do
fig, ax, pl = heatmap(rand(50, 50)) # or something
pdf(fig)
end

get("/plot/html") do
fig, ax, pl = heatmap(rand(50, 50)) # or something
html(fig)
end

serve()

end
2 changes: 2 additions & 0 deletions demo/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[deps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
MIMEs = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
Expand Down
68 changes: 33 additions & 35 deletions demo/paralleldemo.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,47 @@
module ParallelDemo
using Oxygen
using HTTP
using JSON3
using StructTypes
using SwaggerMarkdown
using Base.Threads

include("../src/Oxygen.jl")
using .Oxygen
using HTTP
using JSON3
using StructTypes
using SwaggerMarkdown
using Base.Threads
############## Atomic variable example ##############

############## Atomic variable example ##############
StructTypes.StructType(::Type{Atomic{Int64}}) = StructTypes.Struct()

StructTypes.StructType(::Type{Atomic{Int64}}) = StructTypes.Struct()
x = Atomic{Int64}(0);

x = Atomic{Int64}(0);

@get "/atomic/show" function(req)
return x
end
@get "/atomic/show" function(req)
return x
end

@get "/atomic/increment" function()
atomic_add!(x, 1)
return x
end
@get "/atomic/increment" function()
atomic_add!(x, 1)
return x
end

############## ReentrantLock example ##############
############## ReentrantLock example ##############

global a = 0
rl = ReentrantLock()
global a = 0
rl = ReentrantLock()

@get "/lock/show" function()
return a
end
@get "/lock/show" function()
return a
end

@get "/lock/increment" function()
lock(rl)
try
global a
a += 1
finally
unlock(rl)
end
return a
@get "/lock/increment" function()
lock(rl)
try
global a
a += 1
finally
unlock(rl)
end
return a
end

# start the web server in parallel mode
serveparallel()
# start the web server in parallel mode
serveparallel()

end
45 changes: 18 additions & 27 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Breathe easy knowing you can quickly spin up a web server with abstractions you'
- Middleware chaining (at the application, router, and route levels)
- Static & Dynamic file hosting
- Templating Support
- Plotting Support
- Route tagging
- Repeat tasks

Expand Down Expand Up @@ -536,7 +537,24 @@ end
# start the web server in parallel mode
serveparallel()
```
## Plotting Support

Oxygen has a package extension for the CairoMakie.jl library that helps us return Figures directly from a request handler while keeping all operations in-memory. Each helper function will read the figure into an IOBuffer, set the content type, and return the binary data inside a `HTTP.Response`.

Here are the currently supported helper utils: `png`, `svg`, `pdf`, `html`

```julia
using CairoMakie: heatmap
using Oxygen

# generate a random heatmap plot and return it as a png
@get "/plot/png" function()
fig, ax, pl = heatmap(rand(50, 50))
png(fig)
end

serve()
```

## Templating

Expand Down Expand Up @@ -886,33 +904,6 @@ mergeschema(
)
```

# Common Issues & Tips

## Problems working with Julia's REPL

This is a recurring issue that occurs when writing and testing code in the REPL. Often, people find that their changes are not reflected when they rerun the server. The reason for this is that all the routing utilities are defined as macros, and they are only executed during the precompilation stage. To have your changes take effect, you need to move your route declarations to the `__init__()` function in your module.

```julia
module OxygenExample
using Oxygen
using HTTP

# is called whenever you load this module
function __init__()
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end

# you can call this function from the REPL to start the server
function runserver()
serve()
end

end
```



# API Reference (macros)

Expand Down
1 change: 0 additions & 1 deletion src/Oxygen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module Oxygen

include("core.jl"); using .Core
include("instances.jl"); using .Instances
# Load any optional extensions
include("extensions/load.jl");

import HTTP: Request, Response
Expand Down
4 changes: 2 additions & 2 deletions src/autodoc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ using DataStructures
using Reexport
using RelocatableFolders

using ..Util: html, recursive_merge
using ..Constants
using ..Util
using ..Core: Context, Documenation
using ..AppContext: Context, Documenation
using ..Types: TaggedRoute, TaskDefinition, CronDefinition, Nullable

export registerschema,
Expand Down
20 changes: 9 additions & 11 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ oxygen_title = raw"""

"""

function serverwelcome(host::String, port::Int, docs::Bool, metrics::Bool, docspath::String)
function serverwelcome(host::String, port::Int, docs::Bool, metrics::Bool, parallel::Bool, docspath::String)
printstyled(oxygen_title, color = :blue, bold = true)
@info "📦 Version 1.5.0 (2024-02-26)"
@info "✅ Started server: http://$host:$port"
docs && @info "📖 Documentation: http://$host:$port$docspath"
metrics && @info "📊 Metrics: http://$host:$port$docspath/metrics"
docs && @info "📖 Documentation: http://$host:$port$docspath"
metrics && @info "📊 Metrics: http://$host:$port$docspath/metrics"
parallel && @info "🚀 Running in parallel mode with $(Threads.nthreads()) available threads"
end


Expand Down Expand Up @@ -77,7 +78,7 @@ function serve(ctx::Context;
handle_stream = handler(configured_middelware)

# The cleanup of resources are put at the topmost level in `methods.jl`
return startserver(ctx, show_banner, host, port, docs, metrics, kwargs, async, (kwargs) ->
return startserver(ctx; host, port, show_banner, docs, metrics, kwargs, async, start=(kwargs) ->
HTTP.serve!(handle_stream, host, port; kwargs...))
end

Expand Down Expand Up @@ -126,7 +127,7 @@ function serveparallel(ctx::Context;
# setup the primary stream handler function (can be customized by the caller)
handle_stream = handler(configured_middelware) |> parallel_stream_handler

return startserver(ctx, show_banner, host, port, docs, metrics, kwargs, async, (kwargs) ->
return startserver(ctx; host, port, show_banner, docs, metrics, parallel=true, async, kwargs, start=(kwargs) ->
HTTP.serve!(handle_stream, host, port; kwargs...))
end

Expand Down Expand Up @@ -252,9 +253,9 @@ end
"""
Internal helper function to launch the server in a consistent way
"""
function startserver(ctx::Context, show_banner, host, port, docs, metrics, kwargs, async, start) :: Server
function startserver(ctx::Context; host, port, show_banner=false, docs=false, metrics=false, parallel=false, async=false, kwargs, start) :: Server

show_banner && serverwelcome(host, port, docs, metrics, ctx.docs.docspath[])
show_banner && serverwelcome(host, port, docs, metrics, parallel, ctx.docs.docspath[])

docs && setupdocs(ctx)
metrics && setupmetrics(ctx)
Expand Down Expand Up @@ -577,10 +578,7 @@ function setupmetrics(router::Router, history::History, docspath::String, histor

staticfiles(router, "$DATA_PATH/dashboard", "$docspath/metrics"; loadfile=loadfile)


"""
Create a thread-safe copy of the history object and it's internal data
"""
# Create a thread-safe copy of the history object and it's internal data
function safe_get_transactions(history::History) :: Vector{HTTPTransaction}
transactions = []
lock(history_lock) do
Expand Down
4 changes: 2 additions & 2 deletions src/cron.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module Cron
import Base: @kwdef
using Dates
using ..Util: countargs
using ..Core: CronContext

using ..Types: ActiveCron, RegisteredCron, Nullable
using ..AppContext: CronContext

export cron, startcronjobs, stopcronjobs, clearcronjobs

Expand Down
5 changes: 5 additions & 0 deletions src/extensions/load.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ function __init__()
@require Mustache="ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" include("templating/mustache.jl")
@require OteraEngine="b2d7f28f-acd6-4007-8b26-bc27716e5513" include("templating/oteraengine.jl")


################################################################
# Plotting Extensions #
################################################################
@require CairoMakie="13f3f980-e62b-5c42-98c6-ff1f3baf88f0" include("plotting/cairomakie.jl")
end
Loading
Loading