Oxygen.jl Documentation

repository·master·Indexed 19 days ago

https://github.com/oxygenframework/oxygen.jl

A micro-framework built on top of HTTP.jl for straightforward web application development in Julia. It features built-in extractors for request data serialization, global and local validators, a shared application context, and route organization via Routers. Oxygen includes a cron scheduling system, repeat task registration, and integration with Revise.jl for hot reloading. It supports parallel mode via serveparallel(), Protocol Buffers, plot returns from CairoMakie, WGLMakie, and Bonito, as well as Mustache and Otera templating.

Tokens
16.9K
Snippets
63
Records
75
Agent score
64%

What's inside Oxygen.jl

  1. Apply type annotations to path parameters

    master

    Oxygen uses standard Julia type annotations in your request handler to automatically convert incoming path parameter strings into the specified types.

    • With annotations: Oxygen attempts to convert the data automatically. If the conversion fails, the route will not match or will error.
    • Without annotations: Oxygen treats the parameter as a String by default. You must manually parse the value using functions like parse().
    # Automatic conversion using type annotations
    @get "/multiply/{a}/{b}" function(req, a::Float64, b::Float64)
        return a * b
    end
    
    # Manual conversion without type annotations
    @get "/multiply/{a}/{b}" function(req, a, b)
        return parse(Float64, a) * parse(Float64, b)
    end
  2. How handlers work in Oxygen.jl

    master

    Handlers connect your code to the server by assigning a URL to a function. When an incoming request matches that URL, the function is invoked.

    Key concepts:

    • Type-based identification: The type of the first argument determines the handler type. If no type information is provided, Oxygen assumes it is a Request handler by default.
    • Syntax options: Handlers support standard function definitions, do..end blocks, arrow functions, and even functions declared in other modules.
    • Modularity: Handlers can be imported from other modules to keep your project organized.

    Supported handler types:

    1. Request Handlers: Handle standard HTTP requests. Use @get, @post, @put, @patch, @delete, or @route macros (or their function equivalents). They accept a HTTP.Request as the first argument.
    2. Stream Handlers: Used for streaming data. Use the @stream macro or stream() function. They accept a HTTP.Stream as the first argument. You must explicitly include the type definition so Oxygen can identify them.
    3. Websocket Handlers: Handle websocket connections. Use the @websocket macro or websocket() function. They accept a HTTP.WebSocket as the first argument. Note that websocket handshakes typically use the GET protocol, so they can also be assigned with @get or get() if the type is explicitly defined.
    using HTTP
    using Oxygen
    
    # Request Handler
    @get "/" function(req::HTTP.Request)
        ...
    end
    
    # Stream Handler
    @stream "/stream" function(stream::HTTP.Stream)
        ...
    end
    
    # Websocket Handler
    @websocket "/ws" function(ws::HTTP.WebSocket)
        ...
    end
  3. Use Extractors to serialize request data

    master

    Oxygen provides built-in extractors to reduce boilerplate when serializing inputs (path parameters, query params, headers, etc.) into structured types. You define a struct and use the corresponding extractor in your handler signature. The deserialized data is accessed via the .payload property.

    Supported Extractors:

    • Path: Extracts from path parameters.
    • Query: Extracts from query parameters.
    • Header: Extracts from request headers.
    • Form: Extracts form data from the request body.
    • Body: Serializes the entire request body to a type (e.g., String, Float64).
    • ProtoBuffer: Extracts ProtoBuf messages (requires package extension).
    • Json: Extracts JSON from the request body.
    • JsonFragment: Extracts a specific top-level key from a JSON body using the parameter name.
    struct Add
        b::Int
        c::Int
    end
    
    @get "/add/{a}/{b}/{c}" function(req, a::Int, pathparams::Path{Add})
        add = pathparams.payload # access the serialized payload
        return a + add.b + add.c
    end
  4. Tag routes for documentation organization

    master

    You can assign tags to routers and individual routes. These tags are used by Oxygen to automatically organize and group endpoints into sections within the auto-generated documentation.

    Tags are additive: if a router has a tag, all routes within that router inherit it. If a specific route also defines tags, those tags are appended to the router's tags.

  5. Access Application Context

    master

    The context is a shared global state (e.g., database pools) available throughout the application lifetime. You can set it when calling serve(context=...).

    Note: Oxygen does not provide built-in data race protections for the context. For mutable shared state, use Actors, Channels, or ReentrantLocks.

    There are three ways to access the context in a handler:

    1. Injection: Use the Context{T} struct in the function signature.
    2. Keyword Argument: Use the context keyword argument.
    3. Function Call: Use the context() function.
    using Oxygen
    
    struct Person
        name::String
    end
    
    # 1. Injection
    @get "/ctx-injection" function(req, ctx::Context{Person})
        person = ctx.payload
        return "Hello $(person.name)!"
    end
    
    # 2. Keyword argument
    @get "/ctx-kwarg" function(req; context)
        return "Hello $(context.name)!"
    end
    
    # 3. Function call
    @get "/ctx-function" function(req)
        return "Hello $(context().name)!"
    end
    
    person = Person("John")
    serve(context=person)
  6. Understand Cron Expression Syntax in Oxygen

    master

    Oxygen uses a cron scheduling system based on the Spring specification. A full cron expression consists of six single space-separated fields representing time and date. If you provide a partial expression, all subsequent fields are automatically defaulted to '*'.

    Field order:

    1. second (0-59)
    2. minute (0-59)
    3. hour (0-23)
    4. day of the month (1-31)
    5. month (1-12 or JAN-DEC)
    6. day of the week (1-7, where Monday is 1 and Sunday is 7)

    Example of a partial expression (runs every 2 seconds): */2

    # In this example we see only the `seconds` part of the expression is defined. 
    # This means that all following expressions are automatically defaulted to '*' expressions
    @cron "*/2" function()
        println("runs every 2 seconds")
    end
  7. Choose the appropriate HTTP method for your route

    master

    When designing an API in Oxygen.jl, select an HTTP method based on the type of data manipulation the route performs. While any method can technically be used for any operation, following these conventions is recommended for API clarity:

    • POST: Use when you want to create data.
    • GET: Use when you want to get (fetch) data.
    • PUT: Use to update existing data or create it if it doesn't exist.
    • PATCH: Use when you want to update part of existing data.
    • DELETE: Use when you want to delete data.
  8. Apply middleware at different levels

    master

    Oxygen allows you to apply middleware at three distinct levels. Middleware is additive, and the execution order is always: application -> router -> route.

    1. Application Level: Set via the middleware parameter in serve() or serveparallel().
    2. Router Level: Set via the middleware parameter in router().
    3. Route Level: Set via the middleware parameter in route macros (e.g., @get).

    To skip a layer and all preceding layers, set middleware=[] at that level.

    # Application level
    serve(middleware=[my_app_middleware])
    
    # Router level
    myrouter = router("/router", middleware=[my_router_middleware])
    
    # Route level
    @get myrouter("/example", middleware=[my_route_middleware]) function()
        return "example"
    end
  9. Schedule tasks with Cron expressions

    master

    Oxygen includes a built-in cron scheduling system. You can schedule endpoints via a router or standalone functions using the @cron macro. The parser follows the Spring Cron specification.

    Cron Syntax: Six space-separated fields: second minute hour day-of-month month day-of-week.

    Capabilities:

    • Endpoints: Use the cron keyword in router() to trigger an endpoint automatically.
    • Functions: Use @cron "expression" function() ... end to run background tasks that are not exposed via the API.
    • Lifecycle: Jobs start automatically when serve() or serveparallel() is called and stop when the server is terminated. Use startcronjobs() and stopcronjobs() for manual control.
    # Schedule an endpoint via router
    # Executes at 8, 9, and 10 o'clock every day
    @get router("/cron-example", cron="0 0 8-10 * * *") function(req)
        println("here")
    end
    
    # Schedule a background function
    @cron "*/2" function()
        println("runs every 2 seconds")
    end
  10. Run multiple Oxygen instances

    master

    Oxygen supports running multiple web servers within the same module using two approaches:

    Static approach with @oxidize

    Best if the number of instances is known ahead of time. Use the @oxidize macro in separate modules. Each module acts as an independent instance that can be started via its own .serve() method.

    Dynamic approach with instance()

    Best for creating completely independent instances at runtime. The instance() function dynamically creates a new Julia module. You can access all Oxygen methods (like .get(), .post(), .serve()) using dot syntax on the returned object.

    # Dynamic approach example
    using Oxygen
    
    app1 = instance()
    app1.get("/") do
        text("server A")
    end
    
    app2 = instance()
    app2.get("/") do
        text("server B")
    end
    
    try 
        app1.serve(port=8001, async=true)
        app2.serve(port=8002)
    finally
        app1.terminate()
        app2.terminate()
    end
  11. How middleware works in Oxygen

    master

    Middleware functions intercept incoming requests and outgoing responses. They are executed in the order they are provided (left to right).

    Middleware can be applied at three layers:

    1. Application layer: Passed to serve(middleware=[...]).
    2. Router layer: Passed to router(path, middleware=[...]).
    3. Route layer: Passed to route macros like @get(path, middleware=[...]).

    All middleware is additive. The execution order is always: application -> router -> route.

    # Example of layered middleware
    math = router("math", middleware=[middleware1])
    
    @get math("/divide/{a}/{b}", middleware=[middleware2]) function(req, a::Float64, b::Float64)
        return a / b
    end
    
    serve(middleware=[CorsMiddleware, AuthMiddleware])