From e834ddfeee6bc12fd6662b163dcd42252fdda09e Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Mon, 2 Sep 2024 15:59:26 +0000 Subject: [PATCH] build based on 7a43234 --- dev/api/index.html | 2 +- dev/index.html | 2 +- dev/search/index.html | 2 +- dev/search_index.js | 2 +- dev/tutorial/bigger_applications/index.html | 2 +- dev/tutorial/cron_scheduling/index.html | 2 +- dev/tutorial/first_steps/index.html | 2 +- dev/tutorial/oauth2/index.html | 2 +- dev/tutorial/path_parameters/index.html | 2 +- dev/tutorial/query_parameters/index.html | 2 +- dev/tutorial/request_body/index.html | 2 +- dev/tutorial/request_types/index.html | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev/api/index.html b/dev/api/index.html index 44871237..072b9c02 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,2 +1,2 @@ -Api · Oxygen.jl

Api

Documentation for Oxygen.jl

Starting the webserver

Oxygen.serveFunction
serve(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, async=false, parallel=false, serialize=true, catch_errors=true, docs=true, metrics=true, show_errors=true, show_banner=true, docs_path="/docs", schema_path="/schema", kwargs...)

Start the webserver with your own custom request handler

source
Oxygen.serveparallelFunction
serveparallel(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, serialize=true, async=false, catch_errors=true, docs=true, metrics=true, kwargs...)
source

Routing

Oxygen.@getMacro
@get(path::String, func::Function)

Used to register a function to a specific endpoint to handle GET requests

source
Oxygen.@postMacro
@post(path::String, func::Function)

Used to register a function to a specific endpoint to handle POST requests

source
Oxygen.@putMacro
@put(path::String, func::Function)

Used to register a function to a specific endpoint to handle PUT requests

source
Oxygen.@patchMacro
@patch(path::String, func::Function)

Used to register a function to a specific endpoint to handle PATCH requests

source
Oxygen.@deleteMacro
@delete(path::String, func::Function)

Used to register a function to a specific endpoint to handle DELETE requests

source
Oxygen.@routeMacro
@route(methods::Array{String}, path::String, func::Function)

Used to register a function to a specific endpoint to handle mulitiple request types

source
Missing docstring.

Missing docstring for get(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for post(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for put(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for patch(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for delete(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for route(methods, path, func). Check Documenter's build log for details.

Mounting Files

Oxygen.@staticfilesMacro
@staticfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])

Mount all files inside the /static folder (or user defined mount point)

source
Oxygen.@dynamicfilesMacro
@dynamicfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])

Mount all files inside the /static folder (or user defined mount point), but files are re-read on each request

source
Oxygen.staticfilesFunction
staticfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)

Mount all files inside the /static folder (or user defined mount point). The headers array will get applied to all mounted files

source
Oxygen.dynamicfilesFunction
dynamicfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)

Mount all files inside the /static folder (or user defined mount point), but files are re-read on each request. The headers array will get applied to all mounted files

source

Autogenerated Docs

Oxygen.configdocsFunction
configdocs(docspath::String = "/docs", schemapath::String = "/schema")

Configure the default docs and schema endpoints

source
Missing docstring.

Missing docstring for enabledocs. Check Documenter's build log for details.

Missing docstring.

Missing docstring for disabledocs. Check Documenter's build log for details.

Missing docstring.

Missing docstring for isdocsenabled. Check Documenter's build log for details.

Oxygen.mergeschemaFunction
mergeschema(route::String, customschema::Dict)

Merge the schema of a specific route

mergeschema(customschema::Dict)

Merge the top-level autogenerated schema with a custom schema

source

Helper functions

Oxygen.Core.Util.formdataFunction
formdata(request::HTTP.Request)

Read the html form data from the body of a HTTP.Request

source
formdata(request::HTTP.Response)

Read the html form data from the body of a HTTP.Response

source
Oxygen.Core.Util.htmlFunction
html(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as HTML

source
Oxygen.Core.Util.textFunction
text(request::HTTP.Request)

Read the body of a HTTP.Request as a String

source
text(response::HTTP.Response)

Read the body of a HTTP.Response as a String

source
text(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as plain text

source
Oxygen.Core.Util.fileFunction
file(filepath::String; loadfile=nothing, status = 200, headers = []) :: HTTP.Response

Reads a file and returns a HTTP.Response. The file is read as binary. If the file does not exist, an ArgumentError is thrown. The MIME type and the size of the file are added to the headers.

Arguments

  • filepath: The path to the file to be read.
  • loadfile: An optional function to load the file. If not provided, the file is read using the open function.
  • status: The HTTP status code to be used in the response. Defaults to 200.
  • headers: Any additional headers to be included in the response. Defaults to an empty array.

Returns

  • A HTTP response.
source
Oxygen.Core.Util.xmlFunction
xml(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as XML

source
Oxygen.Core.Util.jsFunction
js(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as JavaScript

source
Oxygen.Core.Util.jsonFunction
json(request::HTTP.Request; keyword_arguments...)

Read the body of a HTTP.Request as JSON with additional arguments for the read/serializer.

source
json(request::HTTP.Request, classtype; keyword_arguments...)

Read the body of a HTTP.Request as JSON with additional arguments for the read/serializer into a custom struct.

source
json(response::HTTP.Response; keyword_arguments)

Read the body of a HTTP.Response as JSON with additional keyword arguments

source
json(response::HTTP.Response, classtype; keyword_arguments)

Read the body of a HTTP.Response as JSON with additional keyword arguments and serialize it into a custom struct

source
json(content::Any; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as JSON

source
json(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response

A helper function that can be passed binary data that should be interpreted as JSON. No conversion is done on the content since it's already in binary format.

source
Oxygen.Core.Util.cssFunction
css(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as CSS

source
Oxygen.Core.Util.binaryFunction
binary(request::HTTP.Request)

Read the body of a HTTP.Request as a Vector{UInt8}

source
binary(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a Vector of UInt8 that should be interpreted as binary data

source

Repeat Tasks & Cron Scheduling

Oxygen.@cronMacro
@cron(expression::String, func::Function)

Registers a function with a cron expression. This will extract either the function name or the random Id julia assigns to each lambda function.

source
@cron(expression::String, name::String, func::Function)

This variation provides way manually "name" a registered function. This information is used by the server on startup to log out all cron jobs.

source
Oxygen.startcronjobsFunction
startcronjobs()

Start all the cron cronjobs within their own async task. Each individual task will loop conintually and sleep untill the next time it's suppost to

source
Oxygen.stopcronjobsFunction
stopcronjobs()

Stop each background task by toggling a global reference that all cron jobs reference

source
Missing docstring.

Missing docstring for clearcronjobs. Check Documenter's build log for details.

Extra's

Oxygen.routerFunction

No documentation found.

Binding Oxygen.Core.AutoDoc.router does not exist.

source
Oxygen.internalrequestFunction
internalrequest(req::HTTP.Request; middleware::Vector=[], serialize::Bool=true, catch_errors::Bool=true)

Directly call one of our other endpoints registered with the router, using your own middleware and bypassing any globally defined middleware

source
+Api · Oxygen.jl

Api

Documentation for Oxygen.jl

Starting the webserver

Oxygen.serveFunction
serve(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, async=false, parallel=false, serialize=true, catch_errors=true, docs=true, metrics=true, show_errors=true, show_banner=true, docs_path="/docs", schema_path="/schema", external_url=nothing, kwargs...)

Start the webserver with your own custom request handler

source
Oxygen.serveparallelFunction
serveparallel(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, serialize=true, async=false, catch_errors=true, docs=true, metrics=true, kwargs...)
source

Routing

Oxygen.@getMacro
@get(path::String, func::Function)

Used to register a function to a specific endpoint to handle GET requests

source
Oxygen.@postMacro
@post(path::String, func::Function)

Used to register a function to a specific endpoint to handle POST requests

source
Oxygen.@putMacro
@put(path::String, func::Function)

Used to register a function to a specific endpoint to handle PUT requests

source
Oxygen.@patchMacro
@patch(path::String, func::Function)

Used to register a function to a specific endpoint to handle PATCH requests

source
Oxygen.@deleteMacro
@delete(path::String, func::Function)

Used to register a function to a specific endpoint to handle DELETE requests

source
Oxygen.@routeMacro
@route(methods::Array{String}, path::String, func::Function)

Used to register a function to a specific endpoint to handle mulitiple request types

source
Missing docstring.

Missing docstring for get(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for post(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for put(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for patch(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for delete(path, func). Check Documenter's build log for details.

Missing docstring.

Missing docstring for route(methods, path, func). Check Documenter's build log for details.

Mounting Files

Oxygen.@staticfilesMacro
@staticfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])

Mount all files inside the /static folder (or user defined mount point)

source
Oxygen.@dynamicfilesMacro
@dynamicfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])

Mount all files inside the /static folder (or user defined mount point), but files are re-read on each request

source
Oxygen.staticfilesFunction
staticfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)

Mount all files inside the /static folder (or user defined mount point). The headers array will get applied to all mounted files

source
Oxygen.dynamicfilesFunction
dynamicfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)

Mount all files inside the /static folder (or user defined mount point), but files are re-read on each request. The headers array will get applied to all mounted files

source

Autogenerated Docs

Oxygen.configdocsFunction
configdocs(docspath::String = "/docs", schemapath::String = "/schema")

Configure the default docs and schema endpoints

source
Missing docstring.

Missing docstring for enabledocs. Check Documenter's build log for details.

Missing docstring.

Missing docstring for disabledocs. Check Documenter's build log for details.

Missing docstring.

Missing docstring for isdocsenabled. Check Documenter's build log for details.

Oxygen.mergeschemaFunction
mergeschema(route::String, customschema::Dict)

Merge the schema of a specific route

mergeschema(customschema::Dict)

Merge the top-level autogenerated schema with a custom schema

source

Helper functions

Oxygen.Core.Util.formdataFunction
formdata(request::HTTP.Request)

Read the html form data from the body of a HTTP.Request

source
formdata(request::HTTP.Response)

Read the html form data from the body of a HTTP.Response

source
Oxygen.Core.Util.htmlFunction
html(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as HTML

source
Oxygen.Core.Util.textFunction
text(request::HTTP.Request)

Read the body of a HTTP.Request as a String

source
text(response::HTTP.Response)

Read the body of a HTTP.Response as a String

source
text(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as plain text

source
Oxygen.Core.Util.fileFunction
file(filepath::String; loadfile=nothing, status = 200, headers = []) :: HTTP.Response

Reads a file and returns a HTTP.Response. The file is read as binary. If the file does not exist, an ArgumentError is thrown. The MIME type and the size of the file are added to the headers.

Arguments

  • filepath: The path to the file to be read.
  • loadfile: An optional function to load the file. If not provided, the file is read using the open function.
  • status: The HTTP status code to be used in the response. Defaults to 200.
  • headers: Any additional headers to be included in the response. Defaults to an empty array.

Returns

  • A HTTP response.
source
Oxygen.Core.Util.xmlFunction
xml(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as XML

source
Oxygen.Core.Util.jsFunction
js(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as JavaScript

source
Oxygen.Core.Util.jsonFunction
json(request::HTTP.Request; keyword_arguments...)

Read the body of a HTTP.Request as JSON with additional arguments for the read/serializer.

source
json(request::HTTP.Request, classtype; keyword_arguments...)

Read the body of a HTTP.Request as JSON with additional arguments for the read/serializer into a custom struct.

source
json(response::HTTP.Response; keyword_arguments)

Read the body of a HTTP.Response as JSON with additional keyword arguments

source
json(response::HTTP.Response, classtype; keyword_arguments)

Read the body of a HTTP.Response as JSON with additional keyword arguments and serialize it into a custom struct

source
json(content::Any; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as JSON

source
json(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response

A helper function that can be passed binary data that should be interpreted as JSON. No conversion is done on the content since it's already in binary format.

source
Oxygen.Core.Util.cssFunction
css(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a String that should be interpreted as CSS

source
Oxygen.Core.Util.binaryFunction
binary(request::HTTP.Request)

Read the body of a HTTP.Request as a Vector{UInt8}

source
binary(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response

A convenience function to return a Vector of UInt8 that should be interpreted as binary data

source

Repeat Tasks & Cron Scheduling

Oxygen.@cronMacro
@cron(expression::String, func::Function)

Registers a function with a cron expression. This will extract either the function name or the random Id julia assigns to each lambda function.

source
@cron(expression::String, name::String, func::Function)

This variation provides way manually "name" a registered function. This information is used by the server on startup to log out all cron jobs.

source
Oxygen.startcronjobsFunction
startcronjobs()

Start all the cron cronjobs within their own async task. Each individual task will loop conintually and sleep untill the next time it's suppost to

source
Oxygen.stopcronjobsFunction
stopcronjobs()

Stop each background task by toggling a global reference that all cron jobs reference

source
Missing docstring.

Missing docstring for clearcronjobs. Check Documenter's build log for details.

Extra's

Oxygen.routerFunction

No documentation found.

Binding Oxygen.Core.AutoDoc.router does not exist.

source
Oxygen.internalrequestFunction
internalrequest(req::HTTP.Request; middleware::Vector=[], serialize::Bool=true, catch_errors::Bool=true)

Directly call one of our other endpoints registered with the router, using your own middleware and bypassing any globally defined middleware

source
diff --git a/dev/index.html b/dev/index.html index ab0bf509..3132c68c 100644 --- a/dev/index.html +++ b/dev/index.html @@ -630,4 +630,4 @@ ) ) ) -)

API Reference (macros)

@get, @post, @put, @patch, @delete

  @get(path, func)
ParameterTypeDescription
pathstring or router()Required. The route to register
funcfunctionRequired. The request handler for this route

Used to register a function to a specific endpoint to handle that corresponding type of request

@route

  @route(methods, path, func)
ParameterTypeDescription
methodsarrayRequired. The types of HTTP requests to register to this route
pathstring or router()Required. The route to register
funcfunctionRequired. The request handler for this route

Low-level macro that allows a route to be handle multiple request types

staticfiles

  staticfiles(folder, mount)
ParameterTypeDescription
folderstringRequired. The folder to serve files from
mountdirstringThe root endpoint to mount files under (default is "static")
set_headersfunctionCustomize the http response headers when returning these files
loadfilefunctionCustomize behavior when loading files

Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths.

dynamicfiles

  dynamicfiles(folder, mount)
ParameterTypeDescription
folderstringRequired. The folder to serve files from
mountdirstringThe root endpoint to mount files under (default is "static")
set_headersfunctionCustomize the http response headers when returning these files
loadfilefunctionCustomize behavior when loading files

Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths. The file is loaded on each request, potentially picking up any file changes.

Request helper functions

html()

  html(content, status, headers)
ParameterTypeDescription
contentstringRequired. The string to be returned as HTML
statusintegerThe HTTP response code (default is 200)
headersdictThe headers for the HTTP response (default has content-type header set to "text/html; charset=utf-8")

Helper function to designate when content should be returned as HTML

queryparams()

  queryparams(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the query parameters from a request as a Dict()

Body Functions

text()

  text(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the body of a request as a string

binary()

  binary(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the body of a request as a binary file (returns a vector of UInt8s)

json()

  json(request, classtype)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object
classtypestructA struct to deserialize a JSON object into

Deserialize the body of a request into a julia struct

+)

API Reference (macros)

@get, @post, @put, @patch, @delete

  @get(path, func)
ParameterTypeDescription
pathstring or router()Required. The route to register
funcfunctionRequired. The request handler for this route

Used to register a function to a specific endpoint to handle that corresponding type of request

@route

  @route(methods, path, func)
ParameterTypeDescription
methodsarrayRequired. The types of HTTP requests to register to this route
pathstring or router()Required. The route to register
funcfunctionRequired. The request handler for this route

Low-level macro that allows a route to be handle multiple request types

staticfiles

  staticfiles(folder, mount)
ParameterTypeDescription
folderstringRequired. The folder to serve files from
mountdirstringThe root endpoint to mount files under (default is "static")
set_headersfunctionCustomize the http response headers when returning these files
loadfilefunctionCustomize behavior when loading files

Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths.

dynamicfiles

  dynamicfiles(folder, mount)
ParameterTypeDescription
folderstringRequired. The folder to serve files from
mountdirstringThe root endpoint to mount files under (default is "static")
set_headersfunctionCustomize the http response headers when returning these files
loadfilefunctionCustomize behavior when loading files

Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths. The file is loaded on each request, potentially picking up any file changes.

Request helper functions

html()

  html(content, status, headers)
ParameterTypeDescription
contentstringRequired. The string to be returned as HTML
statusintegerThe HTTP response code (default is 200)
headersdictThe headers for the HTTP response (default has content-type header set to "text/html; charset=utf-8")

Helper function to designate when content should be returned as HTML

queryparams()

  queryparams(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the query parameters from a request as a Dict()

Body Functions

text()

  text(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the body of a request as a string

binary()

  binary(request)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object

Returns the body of a request as a binary file (returns a vector of UInt8s)

json()

  json(request, classtype)
ParameterTypeDescription
reqHTTP.RequestRequired. The HTTP request object
classtypestructA struct to deserialize a JSON object into

Deserialize the body of a request into a julia struct

diff --git a/dev/search/index.html b/dev/search/index.html index 9c66089d..137672ac 100644 --- a/dev/search/index.html +++ b/dev/search/index.html @@ -1,2 +1,2 @@ -Search · Oxygen.jl

Loading search...

    +Search · Oxygen.jl

    Loading search...

      diff --git a/dev/search_index.js b/dev/search_index.js index b75872d5..151decac 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"tutorial/bigger_applications/#Bigger-Applications-Multiple-Files","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"If you are building an application or a web API, it's rarely the case that you can put everything on a single file.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"As your application grows you'll need to spread your application's logic across multiple files. Oxygen provides some tools to help you do this while staying organized.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Let's say you have an application that looks something like this:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"app\n├── src\n│ ├── main.jl\n│ └── MathOperations.jl\n│\n├── Project.toml\n└── Manifest.toml","category":"page"},{"location":"tutorial/bigger_applications/#How-to-use-the-router()-function","page":"Bigger Applications - Multiple Files","title":"How to use the router() function","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Let's say you have a file dedicated to handling mathematical operations in the submodule at /src/MathOperations.jl.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You might want the first part of each path to have the same value and just switch out the subpath to keep things organized in your api. You can use the router function to do just that. ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function is an HOF (higher order function) that allows you to reuse the same properties across multiple endpoints.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Because the generated router is just a function, they can be exported and shared across multiple files & modules.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using Oxygen\n\nmath = router(\"/math\", tags=[\"math\"])\n\n@get math(\"/multiply/{a}/{b}\", tags=[\"multiplication\"]) function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get math(\"/divide/{a}/{b}\") function(req, a::Float64, b::Float64)\n return a / b\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/#Tagging-your-routes","page":"Bigger Applications - Multiple Files","title":"Tagging your routes","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"By using the hello router in both endpoints, it passes along all the properties as default values. For example If we look at the routes registered in the application they will look like:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"/math/multiply/{a}/{b}\n/math/divide/{a}/{b}","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Both endpoints in this case will be tagged to the math tag and the /multiply endpoint will have an additional tag appended just to this endpoint called multiplication. These tags are used by Oxygen when auto-generating the documentation to organize it by separating the endpoints into sections based off their tags. ","category":"page"},{"location":"tutorial/bigger_applications/#Middleware-and-router()","page":"Bigger Applications - Multiple Files","title":"Middleware & router()","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function has a middleware parameter which takes a vector of middleware functions which are used to intercept all incoming requests & outgoing responses.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"All middleware is additive and any middleware defined in these layers will be combined and executed.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You can assign middleware at three levels:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application \nrouter \nroute ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Middleware will always get executed in the following order:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application -> router -> route","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"the application layer can only be set from the serve() and serveparallel() functions. While the other two layers can be set using the router() function.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"# Set middleware at the application level\nserve(middleware=[])\n\n# Set middleware at the Router level\nmyrouter = router(\"/router\", middleware=[])\n\n# Set middleware at the Route level\n@get myrouter(\"/example\", middleware=[]) function()\n return \"example\"\nend","category":"page"},{"location":"tutorial/bigger_applications/#Router-Level-Middleware","page":"Bigger Applications - Multiple Files","title":"Router Level Middleware","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"At the router level, any middleware defined here will be reused across all other routes that use this router(). In the example below, both /greet/hello and /greet/bonjour routes will send requests through the same middleware functions before either endpoint is called","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\n# middleware1 is defined at the router level\ngreet = router(\"/greet\", middleware=[middleware1])\n\n@get greet(\"/hello\") function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend","category":"page"},{"location":"tutorial/bigger_applications/#Route-Specific-Middleware","page":"Bigger Applications - Multiple Files","title":"Route Specific Middleware","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"At the route level, you can customize what middleware functions should be applied on a route by route basis. In the example below, the /greet/hello route gets both middleware1 & middleware2 functions applied to it, while the /greet/bonjour route only has middleware1 function which it inherited from the greet router.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\nfunction middleware2(handle)\n function(req)\n println(\"this is the 2nd middleware function\")\n handle(req)\n end\nend\n\n# middleware1 is added at the router level\ngreet = router(\"/greet\", middleware=[middleware1])\n\n# middleware2 is added at the route level\n@get greet(\"/hello\", middleware=[middleware2]) function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/#Skipping-Middleware-layers","page":"Bigger Applications - Multiple Files","title":"Skipping Middleware layers","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Well, what if we don't want previous layers of middleware to run? By setting middleware=[], it clears all middleware functions at that layer and skips all layers that come before it. These changes are localized and only affect the components where these values are set.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"For example, setting middleware=[] at the:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application layer -> clears the application layer\nrouter layer -> no application middleware is applied to this router\nroute layer -> no router middleware is applied to this route","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You can set the router's middleware parameter to an empty vector to bypass any application level middleware. In the example below, all requests to endpoints registered to the greet router() will skip any application level middleware and get executed directly.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\ngreet = router(\"/greet\", middleware=[])\n\n@get greet(\"/hello\") function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend\n\nserve(middleware=[middleware1])","category":"page"},{"location":"tutorial/bigger_applications/#Repeat-Actions","page":"Bigger Applications - Multiple Files","title":"Repeat Actions","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function has an interval parameter which is used to call a request handler on a set interval (in seconds). ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"It's important to note that request handlers that use this property can't define additional function parameters outside of the default HTTP.Request parameter.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"In the example below, the /repeat/hello endpoint is called every 0.5 seconds and \"hello\" is printed to the console each time.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using Oxygen\n\nrepeat = router(\"/repeat\", interval=0.5, tags=[\"repeat\"])\n\n@get repeat(\"/hello\") function()\n println(\"hello\")\nend\n\n# you can override properties by setting route specific values \n@get repeat(\"/bonjour\", interval=1.5) function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"If you want to call an endpoint with parameters on a set interval, you're better off creating an endpoint to perform the action you want and a second endpoint to call the first on a set interval. ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using HTTP\nusing Oxygen\n\nrepeat = router(\"/repeat\", interval=1.5, tags=[\"repeat\"])\n\n@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get repeat(\"/multiply\") function()\n response = internalrequest(HTTP.Request(\"GET\", \"/multiply/3/5\"))\n println(response)\n return response\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The example above will print the response from the /multiply endpoint in the console below every 1.5 seconds and should look like this:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"\"\"\"\nHTTP/1.1 200 OK\nContent-Type: application/json; charset=utf-8\n\n15.0\"\"\"","category":"page"},{"location":"tutorial/request_types/#Request-Types","page":"Request Types","title":"Request Types","text":"","category":"section"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"When designing an API you need to first think about what type of requests and what routes or paths your api would need to function. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"For example, if we were to design a weather app we'd probably want a way to lookup weather alerts for a particular state","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"http://localhost:8080/weather/alerts/{state}","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This url can be broken down into several parts ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"host → http://localhost\nport → 8080\nroute or path → /weather/alerts/{state}\npath parameter → {state}","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Before we start writing code for we need to answer some questions: ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"What kind of data manipulation is this route going to perform?\nAre we adding/removing/updating data? (This determines our http method)\nWill this endpoint need any inputs?\nIf so, will we need to pass them through the path or inside the body of the http request?","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This is when knowing the different type of http methods comes in handy.","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Common HTTP methods:","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"POST → when you want to create some data\nGET → when you want to get data\nPUT → update some data if it already exists or create it\nPATCH → when you want to update some data\nDELETE → when you want to delete some data","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"(there are more methods that aren't in this list)","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"In the HTTP protocol, you can communicate to each path using one (or more) of these \"methods\".","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"In reality you can use any of these http methods to do any of those operations. But it's heavily recommended to use the appropriate http method so that people & machines can easily understand your web api. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Now back to our web example. Lets answer those questions:","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This endpoint will return alerts from the US National Weather service api\nThe only input we will need is the state abbreviation","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Since we will only be fetching data and not creating/updating/deleting anything, that means we will want to setup a GET route for our api to handle this type of action.","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"using Oxygen\nusing HTTP\n\n@get \"/weather/alerts/{state}\" function(req::HTTP.Request, state::String)\n return HTTP.get(\"https://api.weather.gov/alerts/active?area=$state\")\nend\n\nserve() ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"With our code in place, we can run this code and visit the endpoint in our browser to view the alerts. Try it out yourself by clicking on the link below. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"http://127.0.0.1:8080/weather/alerts/NY","category":"page"},{"location":"api/#Api","page":"Api","title":"Api","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"Documentation for Oxygen.jl","category":"page"},{"location":"api/#Starting-the-webserver","page":"Api","title":"Starting the webserver","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"serve\nserveparallel","category":"page"},{"location":"api/#Oxygen.serve","page":"Api","title":"Oxygen.serve","text":"serve(; middleware::Vector=[], handler=stream_handler, host=\"127.0.0.1\", port=8080, async=false, parallel=false, serialize=true, catch_errors=true, docs=true, metrics=true, show_errors=true, show_banner=true, docs_path=\"/docs\", schema_path=\"/schema\", kwargs...)\n\nStart the webserver with your own custom request handler\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.serveparallel","page":"Api","title":"Oxygen.serveparallel","text":"serveparallel(; middleware::Vector=[], handler=stream_handler, host=\"127.0.0.1\", port=8080, serialize=true, async=false, catch_errors=true, docs=true, metrics=true, kwargs...)\n\n\n\n\n\n","category":"function"},{"location":"api/#Routing","page":"Api","title":"Routing","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@get(path, func)\n@post(path, func)\n@put(path, func)\n@patch(path, func)\n@delete(path, func)\n@route(methods, path, func)\n\nget(path, func)\npost(path, func)\nput(path, func)\npatch(path, func)\ndelete(path, func)\nroute(methods, path, func)","category":"page"},{"location":"api/#Oxygen.@get-Tuple{Any, Any}","page":"Api","title":"Oxygen.@get","text":"@get(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle GET requests \n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@post-Tuple{Any, Any}","page":"Api","title":"Oxygen.@post","text":"@post(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle POST requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@put-Tuple{Any, Any}","page":"Api","title":"Oxygen.@put","text":"@put(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle PUT requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@patch-Tuple{Any, Any}","page":"Api","title":"Oxygen.@patch","text":"@patch(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle PATCH requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@delete-Tuple{Any, Any}","page":"Api","title":"Oxygen.@delete","text":"@delete(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle DELETE requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@route-Tuple{Any, Any, Any}","page":"Api","title":"Oxygen.@route","text":"@route(methods::Array{String}, path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle mulitiple request types\n\n\n\n\n\n","category":"macro"},{"location":"api/#Mounting-Files","page":"Api","title":"Mounting Files","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@staticfiles\n@dynamicfiles\nstaticfiles\ndynamicfiles","category":"page"},{"location":"api/#Oxygen.@staticfiles","page":"Api","title":"Oxygen.@staticfiles","text":"@staticfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])\n\nMount all files inside the /static folder (or user defined mount point)\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@dynamicfiles","page":"Api","title":"Oxygen.@dynamicfiles","text":"@dynamicfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])\n\nMount all files inside the /static folder (or user defined mount point), but files are re-read on each request\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.staticfiles","page":"Api","title":"Oxygen.staticfiles","text":"staticfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)\n\nMount all files inside the /static folder (or user defined mount point). The headers array will get applied to all mounted files\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.dynamicfiles","page":"Api","title":"Oxygen.dynamicfiles","text":"dynamicfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)\n\nMount all files inside the /static folder (or user defined mount point), but files are re-read on each request. The headers array will get applied to all mounted files\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Autogenerated-Docs","page":"Api","title":"Autogenerated Docs","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"configdocs\nenabledocs\ndisabledocs\nisdocsenabled\nmergeschema\nsetschema\ngetschema","category":"page"},{"location":"api/#Oxygen.configdocs","page":"Api","title":"Oxygen.configdocs","text":"configdocs(docspath::String = \"/docs\", schemapath::String = \"/schema\")\n\nConfigure the default docs and schema endpoints\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.mergeschema","page":"Api","title":"Oxygen.mergeschema","text":"mergeschema(route::String, customschema::Dict)\n\nMerge the schema of a specific route\n\n\n\n\n\nmergeschema(customschema::Dict)\n\nMerge the top-level autogenerated schema with a custom schema\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.setschema","page":"Api","title":"Oxygen.setschema","text":"setschema(customschema::Dict)\n\nOverwrites the entire internal schema\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.getschema","page":"Api","title":"Oxygen.getschema","text":"getschema()\n\nReturn the current internal schema for this app\n\n\n\n\n\n","category":"function"},{"location":"api/#Helper-functions","page":"Api","title":"Helper functions","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"queryparams\nformdata\nhtml\ntext\nfile\nxml\njs\njson\ncss\nbinary","category":"page"},{"location":"api/#Oxygen.Core.Util.queryparams","page":"Api","title":"Oxygen.Core.Util.queryparams","text":"queryparams(request::HTTP.Request)\n\nParse's the query parameters from the Requests URL and return them as a Dict\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.formdata","page":"Api","title":"Oxygen.Core.Util.formdata","text":"formdata(request::HTTP.Request)\n\nRead the html form data from the body of a HTTP.Request\n\n\n\n\n\nformdata(request::HTTP.Response)\n\nRead the html form data from the body of a HTTP.Response\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.html","page":"Api","title":"Oxygen.Core.Util.html","text":"html(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as HTML\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.text","page":"Api","title":"Oxygen.Core.Util.text","text":"text(request::HTTP.Request)\n\nRead the body of a HTTP.Request as a String\n\n\n\n\n\ntext(response::HTTP.Response)\n\nRead the body of a HTTP.Response as a String\n\n\n\n\n\ntext(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as plain text\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.file","page":"Api","title":"Oxygen.Core.Util.file","text":"file(filepath::String; loadfile=nothing, status = 200, headers = []) :: HTTP.Response\n\nReads a file and returns a HTTP.Response. The file is read as binary. If the file does not exist, an ArgumentError is thrown. The MIME type and the size of the file are added to the headers.\n\nArguments\n\nfilepath: The path to the file to be read.\nloadfile: An optional function to load the file. If not provided, the file is read using the open function.\nstatus: The HTTP status code to be used in the response. Defaults to 200.\nheaders: Any additional headers to be included in the response. Defaults to an empty array.\n\nReturns\n\nA HTTP response.\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.xml","page":"Api","title":"Oxygen.Core.Util.xml","text":"xml(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as XML\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.js","page":"Api","title":"Oxygen.Core.Util.js","text":"js(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as JavaScript\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.json","page":"Api","title":"Oxygen.Core.Util.json","text":"json(request::HTTP.Request; keyword_arguments...)\n\nRead the body of a HTTP.Request as JSON with additional arguments for the read/serializer.\n\n\n\n\n\njson(request::HTTP.Request, classtype; keyword_arguments...)\n\nRead the body of a HTTP.Request as JSON with additional arguments for the read/serializer into a custom struct.\n\n\n\n\n\njson(response::HTTP.Response; keyword_arguments)\n\nRead the body of a HTTP.Response as JSON with additional keyword arguments\n\n\n\n\n\njson(response::HTTP.Response, classtype; keyword_arguments)\n\nRead the body of a HTTP.Response as JSON with additional keyword arguments and serialize it into a custom struct\n\n\n\n\n\njson(content::Any; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as JSON\n\n\n\n\n\njson(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA helper function that can be passed binary data that should be interpreted as JSON. No conversion is done on the content since it's already in binary format.\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.css","page":"Api","title":"Oxygen.Core.Util.css","text":"css(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as CSS\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.binary","page":"Api","title":"Oxygen.Core.Util.binary","text":"binary(request::HTTP.Request)\n\nRead the body of a HTTP.Request as a Vector{UInt8}\n\n\n\n\n\nbinary(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a Vector of UInt8 that should be interpreted as binary data\n\n\n\n\n\n","category":"function"},{"location":"api/#Repeat-Tasks-and-Cron-Scheduling","page":"Api","title":"Repeat Tasks & Cron Scheduling","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@cron\nstarttasks\nstoptasks\ncleartasks\nstartcronjobs\nstopcronjobs\nclearcronjobs\nclearcronjobs","category":"page"},{"location":"api/#Oxygen.@cron","page":"Api","title":"Oxygen.@cron","text":"@cron(expression::String, func::Function)\n\nRegisters a function with a cron expression. This will extract either the function name or the random Id julia assigns to each lambda function. \n\n\n\n\n\n@cron(expression::String, name::String, func::Function)\n\nThis variation provides way manually \"name\" a registered function. This information is used by the server on startup to log out all cron jobs.\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.starttasks","page":"Api","title":"Oxygen.starttasks","text":"starttasks()\n\nStart all background repeat tasks\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.stoptasks","page":"Api","title":"Oxygen.stoptasks","text":"stoptasks()\n\nStop all background repeat tasks\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.cleartasks","page":"Api","title":"Oxygen.cleartasks","text":"cleartasks(ct::Context)\n\nClear any stored repeat task definitions\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.startcronjobs","page":"Api","title":"Oxygen.startcronjobs","text":"startcronjobs()\n\nStart all the cron cronjobs within their own async task. Each individual task will loop conintually and sleep untill the next time it's suppost to \n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.stopcronjobs","page":"Api","title":"Oxygen.stopcronjobs","text":"stopcronjobs()\n\nStop each background task by toggling a global reference that all cron jobs reference\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.clearcronjobs","page":"Api","title":"Oxygen.clearcronjobs","text":"Clears all cron job defintions\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Extra's","page":"Api","title":"Extra's","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"router\ninternalrequest\nredirect\nterminate\nresetstate","category":"page"},{"location":"api/#Oxygen.router","page":"Api","title":"Oxygen.router","text":"No documentation found.\n\nBinding Oxygen.Core.AutoDoc.router does not exist.\n\n\n\n","category":"function"},{"location":"api/#Oxygen.internalrequest","page":"Api","title":"Oxygen.internalrequest","text":"internalrequest(req::HTTP.Request; middleware::Vector=[], serialize::Bool=true, catch_errors::Bool=true)\n\nDirectly call one of our other endpoints registered with the router, using your own middleware and bypassing any globally defined middleware\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.redirect","page":"Api","title":"Oxygen.Core.Util.redirect","text":"redirect(path::String; code = 308)\n\nreturn a redirect response \n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.terminate","page":"Api","title":"Oxygen.terminate","text":"terminate(ctx)\n\nstops the webserver immediately\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.resetstate","page":"Api","title":"Oxygen.resetstate","text":"resetstate()\n\nReset all the internal state variables\n\n\n\n\n\n","category":"function"},{"location":"tutorial/oauth2/#OAuth2-with-Umbrella.jl","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Umbrella.jl is a simple Julia authentication plugin, it supports Google and GitHub OAuth2 with more to come. Umbrella integrates with Julia web framework such as Genie.jl, Oxygen.jl or Mux.jl effortlessly.","category":"page"},{"location":"tutorial/oauth2/#Prerequisite","page":"OAuth2 with Umbrella.jl","title":"Prerequisite","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Before using the plugin, you need to obtain OAuth 2 credentials, see Google Identity Step 1, GitHub: Creating an OAuth App for details.","category":"page"},{"location":"tutorial/oauth2/#Installation","page":"OAuth2 with Umbrella.jl","title":"Installation","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"pkg> add Umbrella","category":"page"},{"location":"tutorial/oauth2/#Basic-Usage","page":"OAuth2 with Umbrella.jl","title":"Basic Usage","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Many resources are available describing how OAuth 2 works, please advice OAuth 2.0, Google Identity, or GitHub OAuth 2 for details","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Follow the steps below to enable OAuth 2 in your application. ","category":"page"},{"location":"tutorial/oauth2/#.-Configuration","page":"OAuth2 with Umbrella.jl","title":"1. Configuration","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"OAuth 2 required parameters such as client_id, client_secret and redirect_uri need to be configured through Configuration.Options. ","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"scopes is a list of resources the application will access on user's behalf, it is vary from one provider to another.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"providerOptions configures the additional parameters at the redirection step, it is dependent on the provider.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"const options = Configuration.Options(;\n client_id = \"\", # client id from an OAuth 2 provider\n client_secret = \"\", # secret from an OAuth 2 provider\n redirect_uri = \"http://localhost:3000/oauth2/google/callback\",\n success_redirect = \"/protected\",\n failure_redirect = \"/error\",\n scopes = [\"profile\", \"openid\", \"email\"],\n providerOptions = GoogleOptions(access_type=\"online\")\n)","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"init function takes the provider and options, then returns an OAuth 2 instance. Available provider values are :google, :github and facebook. This list is growing as more providers are supported.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"oauth2_instance = init(:google, options)","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"The examples will use Oxygen.jl as the web framework, but the concept is the same for other web frameworks.","category":"page"},{"location":"tutorial/oauth2/#.-Handle-provider-redirection","page":"OAuth2 with Umbrella.jl","title":"2. Handle provider redirection","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Create two endpoints,","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"/ serve the login page which, in this case, is a Google OAuth 2 link.\n/oauth2/google handles redirections to an OAuth 2 server.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"@get \"/\" function ()\n return \"Authenticate with Google\"\nend\n\n@get \"/oauth2/google\" function ()\n oauth2_instance.redirect()\nend","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"redirect function generates the URL using the parameters in step 1, and redirects users to provider's OAuth 2 server to initiate the authentication and authorization process.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Once the users consent to grant access to one or more scopes requested by the application, OAuth 2 server responds the code for retrieving access token to a callback endpoint.","category":"page"},{"location":"tutorial/oauth2/#.-Retrieves-tokens","page":"OAuth2 with Umbrella.jl","title":"3. Retrieves tokens","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Finally, create the endpoint handling callback from the OAuth 2 server. The path must be identical to the path in redirect_uri from Configuration.Options.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"token_exchange function performs two actions,","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Use code responded by the OAuth 2 server to exchange an access token.\nGet user profile using the access token.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"A handler is required for access/refresh tokens and user profile handling.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"@get \"/oauth2/google/callback\" function (req)\n query_params = queryparams(req)\n code = query_params[\"code\"]\n\n oauth2_instance.token_exchange(code, function (tokens, user)\n # handle tokens and user profile here\n end\n )\nend","category":"page"},{"location":"tutorial/oauth2/#Full-Example","page":"OAuth2 with Umbrella.jl","title":"Full Example","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"using Oxygen\nusing Umbrella\nusing HTTP\n\nconst oauth_path = \"/oauth2/google\"\nconst oauth_callback = \"/oauth2/google/callback\"\n\nconst options = Configuration.Options(;\n client_id=\"\", # client id from Google API Console\n client_secret=\"\", # secret from Google API Console\n redirect_uri=\"http://127.0.0.1:8080$(oauth_callback)\",\n success_redirect=\"/protected\",\n failure_redirect=\"/no\",\n scopes=[\"profile\", \"openid\", \"email\"]\n)\n\nconst google_oauth2 = Umbrella.init(:google, options)\n\n@get \"/\" function()\n return \"Authenticate with Google\"\nend\n\n@get oauth_path function()\n # this handles the Google oauth2 redirect in the background\n google_oauth2.redirect()\nend\n\n@get oauth_callback function(req)\n query_params = queryparams(req)\n code = query_params[\"code\"]\n\n # handle tokens and user details\n google_oauth2.token_exchange(code, \n function (tokens::Google.Tokens, user::Google.User)\n println(tokens.access_token)\n println(tokens.refresh_token)\n println(user.email)\n end\n )\nend\n\n@get \"/protected\" function()\n \"Congrets, You signed in Successfully!\"\nend\n\n# start the web server\nserve()","category":"page"},{"location":"tutorial/query_parameters/#Query-Parameters","page":"Query Parameters","title":"Query Parameters","text":"","category":"section"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as \"query\" parameters.","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"In the example below, we have two query parameters passed to our request handler","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"debug = true \nlimit = 10","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"http://127.0.0.1:8000/echo?debug=true&limit=10","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"To show how this works, lets take a look at this route below:","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"@get \"/echo\" function(req)\n # the queryparams() function will extract all query paramaters from the url \n return queryparams(req)\nend","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"If we hit this route with a url like the one below we should see the query parameters returned as a JSON object ","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"{\n \"debug\": \"true\",\n \"limit\": \"10\"\n}","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"The important distinction between query parameters and path parameters is that they are not automatically converted for you. In this example debug & limit are set to a string even though those aren't the \"correct\" data types.","category":"page"},{"location":"tutorial/request_body/#Request-Body","page":"Request Body","title":"Request Body","text":"","category":"section"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"Whenever you need to send data from a client to your API, you send it as a request body.","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"A request body is data sent by the client to your API (usually JSON). A response body is the data your API sends to the client.","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"Request bodies are useful when you need to send more complicated information to an API. Imagine we wanted to request an uber/lyft to come pick us up. The app (a client) will have to send a lot of information to make this happen. It'd need to send information about the user (like location data, membership info) and data about the destination. The api in turn will have to figure out pricing, available drivers and potential routes to take. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"The inputs of this api are pretty complicated which means it's a perfect case where we'd want to use the request body to send this information. You could send this kind of information through the URL, but I'd highly recommend you don't. Request bodies can store data in pretty much any format which is a lot more flexible than what a URL can support.","category":"page"},{"location":"tutorial/request_body/#Example","page":"Request Body","title":"Example","text":"","category":"section"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"The request bodies can be read and converted to a Julia object by using the built-in json() helper function. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"struct Person\n name::String\n age::String\nend\n\n@post \"/create/struct\" function(req)\n # this will convert the request body directly into a Person struct\n person = json(req, Person)\n return \"hello $(person.name)!\"\nend\n\n@post \"/create/dict\" function(req)\n # this will convert the request body into a Julia Dict\n data = json(req)\n return \"\"\"hello $(data[\"name\"])!\"\"\"\nend","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"When converting JSON into struct's Oxygen will throw an error if the request body doesn't match the struct, all properties need to be visible and match the right type. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"If you don't pass a struct to convert the JSON into, then it will convert the JSON into a Julia Dictionary. This has the benefit of being able to take JSON of any shape which is helpful when your data can change shape or is unknown. ","category":"page"},{"location":"tutorial/path_parameters/#Path-Parameters","page":"Path Parameters","title":"Path Parameters","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"You can declare path \"parameters\" or \"variables\" inside your route with braces and those values are passed directly to your request handler. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"The values of {a} & {b} in the path will get passed to the request handler with the parameter with the same name. ","category":"page"},{"location":"tutorial/path_parameters/#Path-parameters-with-types","page":"Path Parameters","title":"Path parameters with types","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"You can declare the type of a path parameter in the function, using standard Julia type annotations","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Let's take a look back at our first example above we have code to add two numbers.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"In this line we have our request type, route, and function handler defined. Looking closer at our request handler, we can see our variables have type annotations attached to them. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Oxygen will use any type annotations you give it to try to convert the incoming data into that type. Granted, these are completely optional, if you leave out the type annotation then Oxygen will assume it's a string by default. Below is another way to write the same function without type annotations.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a, b)\n return parse(Float64, a) * parse(Float64, b)\nend","category":"page"},{"location":"tutorial/path_parameters/#Autogenerated-Docs-and-Path-Types","page":"Path Parameters","title":"Autogenerated Docs & Path Types","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"And when you open your browser at http://127.0.0.1:8080/docs, you will see the autogenerated interactive documentation for your api. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"If type annotations were provided in the request handler, they will be taken into account when generating the openapi spec. This means that the generated documentation will know what the input types will be and will not only show, but enforce those types through the interactive documentation. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Practically, this means that your users will know exactly how to call your endpoint and your inputs will always remain up to date with the code. ","category":"page"},{"location":"tutorial/path_parameters/#Additional-Parameter-Type-Support","page":"Path Parameters","title":"Additional Parameter Type Support","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Oxygen supports a lot of different path parameter types outside of Julia's base primitives. More complex types & structs are automatically parsed and passed to your request handlers.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"In most cases, Oxygen uses the built-in parse() function to parse incoming parameters. But when the parameter types start getting more complex (eg. Vector{Int64} or a custom struct), then Oxygen assumes the parameter is a JSON string and uses the JSON3 library to serialize the parameter into the corresponding type","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"using Dates\nusing Oxygen\nusing StructTypes\n\n@enum Fruit apple=1 orange=2 kiwi=3\n\nstruct Person \n name :: String \n age :: Int8\nend\n\n# Add a supporting struct types\nStructTypes.StructType(::Type{Person}) = StructTypes.Struct()\nStructTypes.StructType(::Type{Complex{Float64}}) = StructTypes.Struct()\n\n@get \"/fruit/{fruit}\" function(req, fruit::Fruit)\n return fruit\nend\n\n@get \"/date/{date}\" function(req, date::Date)\n return date\nend\n\n@get \"/datetime/{datetime}\" function(req, datetime::DateTime)\n return datetime\nend\n\n@get \"/complex/{complex}\" function(req, complex::Complex{Float64})\n return complex\nend\n\n@get \"/list/{list}\" function(req, list::Vector{Float32})\n return list\nend\n\n@get \"/data/{dict}\" function(req, dict::Dict{String, Any})\n return dict\nend\n\n@get \"/tuple/{tuple}\" function(req, tuple::Tuple{String, String})\n return tuple\nend\n\n@get \"/union/{value}\" function(req, value::Union{Bool, String, Float64})\n return value\nend\n\n@get \"/boolean/{bool}\" function(req, bool::Bool)\n return bool\nend\n\n@get \"/struct/{person}\" function(req, person::Person)\n return person\nend\n\n@get \"/float/{float}\" function (req, float::Float32)\n return float\nend\n\nserve()","category":"page"},{"location":"#Oxygen.jl","page":"Overview","title":"Oxygen.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"
      \n
      \n

      \n

      \n A breath of fresh air for programming web apps in Julia.\n

      \n

      \n Version\n documentation stable\n Build Status\n Coverage Status\n \n

      \n
      ","category":"page"},{"location":"#About","page":"Overview","title":"About","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen is a micro-framework built on top of the HTTP.jl library. Breathe easy knowing you can quickly spin up a web server with abstractions you're already familiar with.","category":"page"},{"location":"#Contact","page":"Overview","title":"Contact","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Need Help? Feel free to reach out on our social media channels.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"(Image: Chat on Discord) (Image: Discuss on GitHub)","category":"page"},{"location":"#Features","page":"Overview","title":"Features","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Straightforward routing\nReal-time Metrics Dashboard\nAuto-generated swagger documentation\nOut-of-the-box JSON serialization & deserialization (customizable)\nType definition support for path parameters\nRequest Extractors\nMultiple Instance Support\nMultithreading support\nWebsockets, Streaming, and Server-Sent Events\nCron Scheduling (on endpoints & functions)\nMiddleware chaining (at the application, router, and route levels)\nStatic & Dynamic file hosting\nTemplating Support\nPlotting Support\nProtocol Buffer Support\nRoute tagging\nRepeat tasks","category":"page"},{"location":"#Installation","page":"Overview","title":"Installation","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"pkg> add Oxygen","category":"page"},{"location":"#Minimalistic-Example","page":"Overview","title":"Minimalistic Example","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Create a web-server with very few lines of code","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\n@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Handlers","page":"Overview","title":"Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Handlers are used to connect your code to the server in a clean & straightforward way. They assign a url to a function and invoke the function when an incoming request matches that url.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Handlers can be imported from other modules and distributed across multiple files for better organization and modularity\nAll handlers have equivalent macro & function implementations and support do..end block syntax\nThe type of first argument is used to identify what kind of handler is being registered\nThis package assumes it's a Request handler by default when no type information is provided","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"There are 3 types of supported handlers:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Request Handlers\nStream Handlers\nWebsocket Handlers","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using HTTP\nusing Oxygen\n\n# Request Handler\n@get \"/\" function(req::HTTP.Request)\n ...\nend\n\n# Stream Handler\n@stream \"/stream\" function(stream::HTTP.Stream)\n ...\nend\n\n# Websocket Handler\n@websocket \"/ws\" function(ws::HTTP.WebSocket)\n ...\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"They are just functions which means there are many ways that they can be expressed and defined. Below is an example of several different ways you can express and assign a Request handler.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/greet\" function()\n \"hello world!\"\nend\n\n@get(\"/gruessen\") do \n \"Hallo Welt!\"\nend\n\n@get \"/saluer\" () -> begin\n \"Bonjour le monde!\"\nend\n\n@get \"/saludar\" () -> \"¡Hola Mundo!\"\n@get \"/salutare\" f() = \"ciao mondo!\"\n\n# This function can be declared in another module\nfunction subtract(req, a::Float64, b::Float64)\n return a - b\nend\n\n# register foreign request handlers like this\n@get \"/subtract/{a}/{b}\" subtract","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"
      More Handler Docs","category":"page"},{"location":"#Request-Handlers","page":"Overview","title":"Request Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Request handlers are used to handle HTTP requests. They are defined using macros or their function equivalents, and accept a HTTP.Request object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The default Handler when no type information is provided\nRouting Macros: @get, @post, @put, @patch, @delete, @route\nRouting Functions: get(), post(), put(), patch(), delete(), route()","category":"page"},{"location":"#Stream-Handlers","page":"Overview","title":"Stream Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Stream handlers are used to stream data. They are defined using the @stream macro or the stream() function and accept a HTTP.Stream object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@stream and stream() don't require a type definition on the first argument, they assume it's a stream.\nStream handlers can be assigned with standard routing macros & functions: @get, @post, etc\nYou need to explicitly include the type definition so Oxygen can identify this as a Stream handler","category":"page"},{"location":"#Websocket-Handlers","page":"Overview","title":"Websocket Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Websocket handlers are used to handle websocket connections. They are defined using the @websocket macro or the websocket() function and accept a HTTP.WebSocket object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@websocket and websocket() don't require a type definition on the first argument, they assume it's a websocket.\nWebsocket handlers can also be assigned with the @get macro or get() function, because the websocket protocol requires a GET request to initiate the handshake. \nYou need to explicitly include the type definition so Oxygen can identify this as a Websocket handler","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"
      ","category":"page"},{"location":"#Routing-Macro-and-Function-Syntax","page":"Overview","title":"Routing Macro & Function Syntax","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"There are two primary ways to register your request handlers: the standard routing macros or the routing functions which utilize the do-block syntax. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"For each routing macro, we now have a an equivalent routing function","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get -> get()\n@post -> post()\n@put -> put()\n@patch -> patch()\n@delete -> delete()\n@route -> route()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The only practical difference between the two is that the routing macros are called during the precompilation stage, whereas the routing functions are only called when invoked. (The routing macros call the routing functions under the hood)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# Routing Macro syntax\n@get \"/add/{x}/{y}\" function(request::HTTP.Request, x::Int, y::Int)\n x + y\nend\n\n# Routing Function syntax\nget(\"/add/{x}/{y}\") do request::HTTP.Request, x::Int, y::Int\n x + y\nend","category":"page"},{"location":"#Render-Functions","page":"Overview","title":"Render Functions","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen, by default, automatically identifies the Content-Type of the return value from a request handler when building a Response. This default functionality is quite useful, but it does have an impact on performance. In situations where the return type is known, It's recommended to use one of the pre-existing render functions to speed things up.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Here's a list of the currently supported render functions: html, text, json, file, xml, js, css, binary","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to use these functions:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen \n\nget(\"/html\") do \n html(\"

      Hello World

      \")\nend\n\nget(\"/text\") do \n text(\"Hello World\")\nend\n\nget(\"/json\") do \n json(Dict(\"message\" => \"Hello World\"))\nend\n\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In most cases, these functions accept plain strings as inputs. The only exceptions are the binary function, which accepts a Vector{UInt8}, and the json function which accepts any serializable type. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Each render function accepts a status and custom headers.\nThe Content-Type and Content-Length headers are automatically set by these render functions","category":"page"},{"location":"#Path-parameters","page":"Overview","title":"Path parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Path parameters are declared with braces and are passed directly to your request handler. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# use path params without type definitions (defaults to Strings)\n@get \"/add/{a}/{b}\" function(req, a, b)\n return parse(Float64, a) + parse(Float64, b)\nend\n\n# use path params with type definitions (they are automatically converted)\n@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend\n\n# The order of the parameters doesn't matter (just the name matters)\n@get \"/subtract/{a}/{b}\" function(req, b::Int64, a::Int64)\n return a - b\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Query-parameters","page":"Overview","title":"Query parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Query parameters can be declared directly inside of your handlers signature. Any parameter that isn't mentioned inside the route path is assumed to be a query parameter.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"If a default value is not provided, it's assumed to be a required parameter","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/query\" function(req::HTTP.Request, a::Int, message::String=\"hello world\")\n return (a, message)\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Alternatively, you can use the queryparams() function to extract the raw values from the url as a dictionary. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/query\" function(req::HTTP.Request)\n return queryparams(req)\nend","category":"page"},{"location":"#HTML-Forms","page":"Overview","title":"HTML Forms","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Use the formdata() function to extract and parse the form data from the body of a request. This function returns a dictionary of key-value pairs from the form","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# Setup a basic form\n@get \"/\" function()\n html(\"\"\"\n
      \n
      \n
      \n
      \n

      \n \n
      \n \"\"\")\nend\n\n# Parse the form data and return it\n@post \"/form\" function(req)\n data = formdata(req)\n return data\nend\n\nserve()","category":"page"},{"location":"#Return-JSON","page":"Overview","title":"Return JSON","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"All objects are automatically deserialized into JSON using the JSON3 library","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\n@get \"/data\" function(req::HTTP.Request)\n return Dict(\"message\" => \"hello!\", \"value\" => 99.3)\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Deserialize-and-Serialize-custom-structs","page":"Overview","title":"Deserialize & Serialize custom structs","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides some out-of-the-box serialization & deserialization for most objects but requires the use of StructTypes when converting structs","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\nusing StructTypes\n\nstruct Animal\n id::Int\n type::String\n name::String\nend\n\n# Add a supporting struct type definition so JSON3 can serialize & deserialize automatically\nStructTypes.StructType(::Type{Animal}) = StructTypes.Struct()\n\n@get \"/get\" function(req::HTTP.Request)\n # serialize struct into JSON automatically (because we used StructTypes)\n return Animal(1, \"cat\", \"whiskers\")\nend\n\n@post \"/echo\" function(req::HTTP.Request)\n # deserialize JSON from the request body into an Animal struct\n animal = json(req, Animal)\n # serialize struct back into JSON automatically (because we used StructTypes)\n return animal\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Extractors","page":"Overview","title":"Extractors","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen comes with several built-in extractors designed to reduce the amount of boilerplate required to serialize inputs to your handler functions. By simply defining a struct and specifying the data source, these extractors streamline the process of data ingestion & validation through a uniform api.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The serialized data is accessible through the payload property\nCan be used alongside other parameters and extractors\nDefault values can be assigned when defined with the @kwdef macro\nIncludes both global and local validators\nStruct definitions can be deeply nested","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Supported Extractors:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Path - extracts from path parameters\nQuery - extracts from query parameters, \nHeader - extracts from request headers\nForm - extracts form data from the request body\nBody - serializes the entire request body to a given type (String, Float64, etc..)\nProtoBuffer - extracts the ProtoBuf message from the request body (available through a package extension)\nJson - extracts json from the request body\nJsonFragment - extracts a \"fragment\" of the json body using the parameter name to identify and extract the corresponding top-level key","category":"page"},{"location":"#Using-Extractors-and-Parameters","page":"Overview","title":"Using Extractors & Parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In this example we show that the Path extractor can be used alongside regular path parameters. This Also works with regular query parameters and the Query extractor.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"struct Add\n b::Int\n c::Int\nend\n\n@get \"/add/{a}/{b}/{c}\" function(req, a::Int, pathparams::Path{Add})\n add = pathparams.payload # access the serialized payload\n return a + add.b + add.c\nend","category":"page"},{"location":"#Default-Values","page":"Overview","title":"Default Values","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Default values can be setup with structs using the @kwdef macro.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@kwdef struct Pet\n name::String\n age::Int = 10\nend\n\n@post \"/pet\" function(req, params::Json{Pet})\n return params.payload # access the serialized payload\nend","category":"page"},{"location":"#Validation","page":"Overview","title":"Validation","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"On top of serializing incoming data, you can also define your own validation rules by using the validate function. In the example below we show how to use both global and local validators in your code.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Validators are completely optional\nDuring the validation phase, oxygen will call the global validator before running a local validator.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"import Oxygen: validate\n\nstruct Person\n name::String\n age::Int\nend\n\n# Define a global validator \nvalidate(p::Person) = p.age >= 0\n\n# Only the global validator is ran here\n@post \"/person\" function(req, newperson::Json{Person})\n return newperson.payload\nend\n\n# In this case, both global and local validators are ran (this also makes sure the person is age 21+)\n# You can also use this sytnax instead: Json(Person, p -> p.age >= 21)\n@post \"/adult\" function(req, newperson = Json{Person}(p -> p.age >= 21))\n return newperson.payload\nend","category":"page"},{"location":"#Interpolating-variables-into-endpoints","page":"Overview","title":"Interpolating variables into endpoints","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"You can interpolate variables directly into the paths, which makes dynamically registering routes a breeze ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"(Thanks to @anandijain for the idea)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\noperations = Dict(\"add\" => +, \"multiply\" => *)\nfor (pathname, operator) in operations\n @get \"/$pathname/{a}/{b}\" function (req, a::Float64, b::Float64)\n return operator(a, b)\n end\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Routers","page":"Overview","title":"Routers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The router() function is an HOF (higher order function) that allows you to reuse the same path prefix & properties across multiple endpoints. This is helpful when your api starts to grow and you want to keep your path operations organized.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below are the arguments the router() function can take:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"router(prefix::String; tags::Vector, middleware::Vector, interval::Real, cron::String)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"tags - are used to organize endpoints in the autogenerated docs\nmiddleware - is used to setup router & route-specific middleware\ninterval - is used to support repeat actions (calling a request handler on a set interval in seconds)\ncron - is used to specify a cron expression that determines when to call the request handler.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# Any routes that use this router will be automatically grouped \n# under the 'math' tag in the autogenerated documenation\nmath = router(\"/math\", tags=[\"math\"])\n\n# You can also assign route specific tags\n@get math(\"/multiply/{a}/{b}\", tags=[\"multiplication\"]) function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get math(\"/divide/{a}/{b}\") function(req, a::Float64, b::Float64)\n return a / b\nend\n\nserve()","category":"page"},{"location":"#Cron-Scheduling","page":"Overview","title":"Cron Scheduling","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the Spring Cron Expressions page.","category":"page"},{"location":"#Cron-Expression-Syntax","page":"Overview","title":"Cron Expression Syntax","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The string has six single space-separated time and date fields:\n\n ┌───────────── second (0-59)\n │ ┌───────────── minute (0 - 59)\n │ │ ┌───────────── hour (0 - 23)\n │ │ │ ┌───────────── day of the month (1 - 31)\n │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)\n │ │ │ │ │ ┌───────────── day of the week (1 - 7)\n │ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)\n │ │ │ │ │ │\n * * * * * *","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to '*'). ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# In this example we see only the `seconds` part of the expression is defined. \n# This means that all following expressions are automatically defaulted to '*' expressions\n@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend","category":"page"},{"location":"#Scheduling-Endpoints","page":"Overview","title":"Scheduling Endpoints","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The router() function has a keyword argument called cron, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# execute at 8, 9 and 10 o'clock of every day.\n@get router(\"/cron-example\", cron=\"0 0 8-10 * * *\") function(req)\n println(\"here\")\nend\n\n# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)\nevery5 = router(\"/cron\", cron=\"*/5\")\n\n# this endpoint inherits the cron expression\n@get every5(\"/first\") function(req)\n println(\"first\")\nend\n\n# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5\n@get every5(\"/second\", cron=\"*/2\") function(req)\n println(\"second\")\nend","category":"page"},{"location":"#Scheduling-Functions","page":"Overview","title":"Scheduling Functions","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In addition to scheduling endpoints, you can also use the new @cron macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend\n\n@cron \"0 0/30 8-10 * * *\" function()\n println(\"runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day\")\nend","category":"page"},{"location":"#Starting-and-Stopping-Cron-Jobs","page":"Overview","title":"Starting & Stopping Cron Jobs","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.","category":"page"},{"location":"#Repeat-Tasks","page":"Overview","title":"Repeat Tasks","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Repeat tasks provide a simple api to run a function on a set interval. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"There are two ways to register repeat tasks: ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Through the interval parameter in a router()\nUsing the @repeat macro","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"It's important to note that request handlers that use this property can't define additional function parameters outside of the default HTTP.Request parameter.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In the example below, the /repeat/hello endpoint is called every 0.5 seconds and \"hello\" is printed to the console each time.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The router() function has an interval parameter which is used to call a request handler on a set interval (in seconds). ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\ntaskrouter = router(\"/repeat\", interval=0.5, tags=[\"repeat\"])\n\n@get taskrouter(\"/hello\") function()\n println(\"hello\")\nend\n\n# you can override properties by setting route specific values \n@get taskrouter(\"/bonjour\", interval=1.5) function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to register a repeat task outside of the router","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@repeat 1.5 function()\n println(\"runs every 1.5 seconds\")\nend\n\n# you can also \"name\" a repeat task \n@repeat 5 \"every-five\" function()\n println(\"runs every 5 seconds\")\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"When the server is ran, all tasks are started automatically. But the module also provides utilities to have more fine-grained control over the running tasks using the following functions: starttasks(), stoptasks(), and cleartasks()","category":"page"},{"location":"#Multiple-Instances","page":"Overview","title":"Multiple Instances","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In some advanced scenarios, you might need to spin up multiple web severs within the same module on different ports. Oxygen provides both a static and dynamic way to create multiple instances of a web server.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"As a general rule of thumb, if you know how many instances you need ahead of time it's best to go with the static approach.","category":"page"},{"location":"#Static:-multiple-instance's-with-@oxidise","page":"Overview","title":"Static: multiple instance's with @oxidise","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a new macro which makes it possible to setup and run multiple instances. It generates methods and binds them to a new internal state for the current module. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In the example below, two simple servers are defined within modules A and B and are started in the parent module. Both modules contain all of the functions exported from Oxygen which can be called directly as shown below.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"module A\n using Oxygen; @oxidise\n\n get(\"/\") do\n text(\"server A\")\n end\nend\n\nmodule B\n using Oxygen; @oxidise\n\n get(\"/\") do\n text(\"server B\")\n end\nend\n\ntry \n # start both instances\n A.serve(port=8001, async=true)\n B.serve(port=8002, async=false)\nfinally\n # shut down if we `Ctrl+C`\n A.terminate()\n B.terminate()\nend","category":"page"},{"location":"#Dynamic:-multiple-instance's-with-instance()","page":"Overview","title":"Dynamic: multiple instance's with instance()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The instance function helps you create a completely independent instance of an Oxygen web server at runtime. It works by dynamically creating a julia module at runtime and loading the Oxygen code within it.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"All of the same methods from Oxygen are available under the named instance. In the example below we can use the get, and serve by simply using dot syntax on the app1 variable to access the underlying methods.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n######### Setup the first app #########\n\napp1 = instance()\n\napp1.get(\"/\") do\n text(\"server A\")\nend\n\n######### Setup the second app #########\n\napp2 = instance()\n\napp2.get(\"/\") do\n text(\"server B\")\nend\n\n######### Start both instances #########\n\ntry \n # start both servers together\n app1.serve(port=8001, async=true)\n app2.serve(port=8002)\nfinally\n # clean it up\n app1.terminate()\n app2.terminate()\nend","category":"page"},{"location":"#Multithreading-and-Parallelism","page":"Overview","title":"Multithreading & Parallelism","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"For scenarios where you need to handle higher amounts of traffic, you can run Oxygen in a multithreaded mode. In order to utilize this mode, julia must have more than 1 thread to work with. You can start a julia session with 4 threads using the command below","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"julia --threads 4","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"serveparallel() Starts the webserver in streaming mode and handles requests in a cooperative multitasking approach. This function uses Threads.@spawn to schedule a new task on any available thread. Meanwhile, @async is used inside this task when calling each request handler. This allows the task to yield during I/O operations.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing StructTypes\nusing Base.Threads\n\n# Make the Atomic struct serializable\nStructTypes.StructType(::Type{Atomic{Int64}}) = StructTypes.Struct()\n\nx = Atomic{Int64}(0);\n\n@get \"/show\" function()\n return x\nend\n\n@get \"/increment\" function()\n atomic_add!(x, 1)\n return x\nend\n\n# start the web server in parallel mode\nserveparallel()","category":"page"},{"location":"#Protocol-Buffers","page":"Overview","title":"Protocol Buffers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen includes an extension for the ProtoBuf.jl package. This extension provides a protobuf() function, simplifying the process of working with Protocol Buffers in the context of web server. For a better understanding of this package, please refer to its official documentation.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"This function has overloads for the following scenarios:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Decoding a protobuf message from the body of an HTTP request.\nEncoding a protobuf message into the body of an HTTP request.\nEncoding a protobuf message into the body of an HTTP response.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using HTTP\nusing ProtoBuf\nusing Oxygen\n\n# The generated classes need to be created ahead of time (check the protobufs)\ninclude(\"people_pb.jl\");\nusing .people_pb: People, Person\n\n# Decode a Protocol Buffer Message \n@post \"/count\" function(req::HTTP.Request)\n # decode the request body into a People object\n message = protobuf(req, People)\n # count the number of Person objects\n return length(message.people)\nend\n\n# Encode & Return Protocol Buffer message\n@get \"/get\" function()\n message = People([\n Person(\"John Doe\", 20),\n Person(\"Alice\", 30),\n Person(\"Bob\", 35)\n ])\n # seralize the object inside the body of a HTTP.Response\n return protobuf(message)\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The following is an example of a schema that was used to create the necessary Julia bindings. These bindings allow for the encoding and decoding of messages in the above example.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"syntax = \"proto3\";\nmessage Person {\n string name = 1;\n sint32 age = 2;\n}\nmessage People {\n repeated Person people = 1;\n}","category":"page"},{"location":"#Plotting","page":"Overview","title":"Plotting","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen is equipped with several package extensions that enhance its plotting capabilities. These extensions make it easy to return plots directly from request handlers. All operations are performed in-memory using an IOBuffer and return a HTTP.Response","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Supported Packages and their helper utils:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"CairoMakie.jl: png, svg, pdf, html\nWGLMakie.jl: html\nBonito.jl: html","category":"page"},{"location":"#CairoMakie.jl","page":"Overview","title":"CairoMakie.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using CairoMakie: heatmap\nusing Oxygen\n\n@get \"/cairo\" function()\n fig, ax, pl = heatmap(rand(50, 50))\n png(fig)\nend\n\nserve()","category":"page"},{"location":"#WGLMakie.jl","page":"Overview","title":"WGLMakie.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using Bonito\nusing WGLMakie: heatmap\nusing Oxygen\nusing Oxygen: html # Bonito also exports html\n\n@get \"/wgl\" function()\n fig = heatmap(rand(50, 50))\n html(fig)\nend\n\nserve()","category":"page"},{"location":"#Bonito.jl","page":"Overview","title":"Bonito.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using Bonito\nusing WGLMakie: heatmap\nusing Oxygen\nusing Oxygen: html # Bonito also exports html\n\n@get \"/bonito\" function()\n app = App() do\n return DOM.div(\n DOM.h1(\"Random 50x50 Heatmap\"), \n DOM.div(heatmap(rand(50, 50)))\n )\n end\n return html(app)\nend\n\nserve()","category":"page"},{"location":"#Templating","page":"Overview","title":"Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Rather than building an internal engine for templating or adding additional dependencies, Oxygen provides two package extensions to support Mustache.jl and OteraEngine.jl templates.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a simple wrapper api around both packages that makes it easy to render templates from strings, templates, and files. This wrapper api returns a render function which accepts a dictionary of inputs to fill out the template.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In all scenarios, the rendered template is returned inside a HTTP.Response object ready to get served by the api. By default, the mime types are auto-detected either by looking at the content of the template or the extension name on the file. If you know the mime type you can pass it directly through the mime_type keyword argument to skip the detection process.","category":"page"},{"location":"#Mustache-Templating","page":"Overview","title":"Mustache Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Please take a look at the Mustache.jl documentation to learn the full capabilities of the package","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 1: Rendering a Mustache Template from a File","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Mustache\nusing Oxygen\n\n# Load the Mustache template from a file and create a render function\nrender = mustache(\"./templates/greeting.txt\", from_file=false)\n\n@get \"/mustache/file\" function()\n data = Dict(\"name\" => \"Chris\")\n return render(data) # This will return an HTML.Response with the rendered template\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 2: Specifying MIME Type for a plain string Mustache Template","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Mustache\nusing Oxygen\n\n# Define a Mustache template (both plain strings and mustache templates are supported)\ntemplate_str = \"Hello, {{name}}!\"\n\n# Create a render function, specifying the MIME type as text/plain\nrender = mustache(template_str, mime_type=\"text/plain\") # mime_type keyword arg is optional \n\n@get \"/plain/text\" function()\n data = Dict(\"name\" => \"Chris\")\n return render(data) # This will return a plain text response with the rendered template\nend","category":"page"},{"location":"#Otera-Templating","page":"Overview","title":"Otera Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Please take a look at the OteraEngine.jl documentation to learn the full capabilities of the package","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 1: Rendering an Otera Template with Logic and Loops","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using OteraEngine\nusing Oxygen\n\n# Define an Otera template\ntemplate_str = \"\"\"\n\n {{ title }}\n \n {% for name in names %}\n Hello {{ name }}
      \n {% end %}\n \n\n\"\"\"\n\n# Create a render function for the Otera template\nrender = otera(template_str)\n\n@get \"/otera/loop\" function()\n data = Dict(\"title\" => \"Greetings\", \"names\" => [\"Alice\", \"Bob\", \"Chris\"])\n return render(data) # This will return an HTML.Response with the rendered template\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In this example, an Otera template is defined with a for-loop that iterates over a list of names, greeting each name.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 2: Running Julia Code in Otera Template","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using OteraEngine\nusing Oxygen\n\n# Define an Otera template with embedded Julia code\ntemplate_str = \"\"\"\nThe square of {{ number }} is {< number^2 >}.\n\"\"\"\n\n# Create a render function for the Otera template\nrender = otera(template_str)\n\n@get \"/otera/square\" function()\n data = Dict(\"number\" => 5)\n return render(data) # This will return an HTML.Response with the rendered template\nend\n","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In this example, an Otera template is defined with embedded Julia code that calculates the square of a given number. ","category":"page"},{"location":"#Mounting-Static-Files","page":"Overview","title":"Mounting Static Files","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"You can mount static files using this handy function which recursively searches a folder for files and mounts everything. All files are loaded into memory on startup.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# mount all files inside the \"content\" folder under the \"/static\" path\nstaticfiles(\"content\", \"static\")\n\n# start the web server\nserve()","category":"page"},{"location":"#Mounting-Dynamic-Files","page":"Overview","title":"Mounting Dynamic Files","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Similar to staticfiles, this function mounts each path and re-reads the file for each request. This means that any changes to the files after the server has started will be displayed.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# mount all files inside the \"content\" folder under the \"/dynamic\" path\ndynamicfiles(\"content\", \"dynamic\")\n\n# start the web server\nserve()","category":"page"},{"location":"#Performance-Tips","page":"Overview","title":"Performance Tips","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios. Anecdotally, i've seen a 2-3x speedup in serve() and a 4-5x speedup in serveparallel() performance.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# This is how you disable internal logging in both modes\nserve(access_log=nothing)\nserveparallel(access_log=nothing)","category":"page"},{"location":"#Logging","page":"Overview","title":"Logging","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a default logging format but allows you to customize the format using the access_log parameter. This functionality is available in both the serve() and serveparallel() functions.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"You can read more about the logging options here","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# Uses the default logging format\nserve()\n\n# Customize the logging format \nserve(access_log=logfmt\"[$time_iso8601] \\\"$request\\\" $status\")\n\n# Disable internal request logging \nserve(access_log=nothing)","category":"page"},{"location":"#Middleware","page":"Overview","title":"Middleware","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Middleware functions make it easy to create custom workflows to intercept all incoming requests and outgoing responses. They are executed in the same order they are passed in (from left to right).","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"They can be set at the application, router, and route layer with the middleware keyword argument. All middleware is additive and any middleware defined in these layers will be combined and executed.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Middleware will always be executed in the following order:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"application -> router -> route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Now lets see some middleware in action:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\nconst CORS_HEADERS = [\n \"Access-Control-Allow-Origin\" => \"*\",\n \"Access-Control-Allow-Headers\" => \"*\",\n \"Access-Control-Allow-Methods\" => \"POST, GET, OPTIONS\"\n]\n\n# https://juliaweb.github.io/HTTP.jl/stable/examples/#Cors-Server\nfunction CorsMiddleware(handler)\n return function(req::HTTP.Request)\n println(\"CORS middleware\")\n # determine if this is a pre-flight request from the browser\n if HTTP.method(req)==\"OPTIONS\"\n return HTTP.Response(200, CORS_HEADERS) \n else \n return handler(req) # passes the request to the AuthMiddleware\n end\n end\nend\n\nfunction AuthMiddleware(handler)\n return function(req::HTTP.Request)\n println(\"Auth middleware\")\n # ** NOT an actual security check ** #\n if !HTTP.headercontains(req, \"Authorization\", \"true\")\n return HTTP.Response(403)\n else \n return handler(req) # passes the request to your application\n end\n end\nend\n\nfunction middleware1(handle)\n function(req)\n println(\"middleware1\")\n handle(req)\n end\nend\n\nfunction middleware2(handle)\n function(req)\n println(\"middleware2\")\n handle(req)\n end\nend\n\n# set middleware at the router level\nmath = router(\"math\", middleware=[middleware1])\n\n# set middleware at the route level \n@get math(\"/divide/{a}/{b}\", middleware=[middleware2]) function(req, a::Float64, b::Float64)\n return a / b\nend\n\n# set application level middleware\nserve(middleware=[CorsMiddleware, AuthMiddleware])","category":"page"},{"location":"#Custom-Response-Serializers","page":"Overview","title":"Custom Response Serializers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"If you don't want to use Oxygen's default response serializer, you can turn it off and add your own! Just create your own special middleware function to serialize the response and add it at the end of your own middleware chain. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Both serve() and serveparallel() have a serialize keyword argument which can toggle off the default serializer.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\nusing JSON3\n\n@get \"/divide/{a}/{b}\" function(req::HTTP.Request, a::Float64, b::Float64)\n return a / b\nend\n\n# This is just a regular middleware function\nfunction myserializer(handle)\n function(req)\n try\n response = handle(req)\n # convert all responses to JSON\n return HTTP.Response(200, [], body=JSON3.write(response)) \n catch error \n @error \"ERROR: \" exception=(error, catch_backtrace())\n return HTTP.Response(500, \"The Server encountered a problem\")\n end \n end\nend\n\n# make sure 'myserializer' is the last middleware function in this list\nserve(middleware=[myserializer], serialize=false)","category":"page"},{"location":"#Autogenerated-Docs-with-Swagger","page":"Overview","title":"Autogenerated Docs with Swagger","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Swagger documentation is automatically generated for each route you register in your application. Only the route name, parameter types, and 200 & 500 responses are automatically created for you by default. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"You can view your generated documentation at /docs, and the schema can be found under /docs/schema. Both of these values can be changed to whatever you want using the configdocs() function. You can also opt out of autogenerated docs entirely by calling the disabledocs() function before starting your application. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"To add additional details you can either use the built-in mergeschema() or setschema() functions to directly modify the schema yourself or merge the generated schema from the SwaggerMarkdown.jl package (I'd recommend the latter)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to merge the schema generated from the SwaggerMarkdown.jl package.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing SwaggerMarkdown\n\n# Here's an example of how you can merge autogenerated docs from SwaggerMarkdown.jl into your api\n@swagger \"\"\"\n/divide/{a}/{b}:\n get:\n description: Return the result of a / b\n parameters:\n - name: a\n in: path\n required: true\n description: this is the value of the numerator \n schema:\n type : number\n responses:\n '200':\n description: Successfully returned an number.\n\"\"\"\n@get \"/divide/{a}/{b}\" function (req, a::Float64, b::Float64)\n return a / b\nend\n\n# title and version are required\ninfo = Dict(\"title\" => \"My Demo Api\", \"version\" => \"1.0.0\")\nopenApi = OpenAPI(\"3.0\", info)\nswagger_document = build(openApi)\n \n# merge the SwaggerMarkdown schema with the internal schema\nmergeschema(swagger_document)\n\n# start the web server\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to manually modify the schema","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing SwaggerMarkdown\n\n# Only the basic information is parsed from this route when generating docs\n@get \"/multiply/{a}/{b}\" function (req, a::Float64, b::Float64)\n return a * b\nend\n\n# Here's an example of how to update a part of the schema yourself\nmergeschema(\"/multiply/{a}/{b}\", \n Dict(\n \"get\" => Dict(\n \"description\" => \"return the result of a * b\"\n )\n )\n)\n\n# Here's another example of how to update a part of the schema yourself, but this way allows you to modify other properties defined at the root of the schema (title, summary, etc.)\nmergeschema(\n Dict(\n \"paths\" => Dict(\n \"/multiply/{a}/{b}\" => Dict(\n \"get\" => Dict(\n \"description\" => \"return the result of a * b\"\n )\n )\n )\n )\n)","category":"page"},{"location":"#API-Reference-(macros)","page":"Overview","title":"API Reference (macros)","text":"","category":"section"},{"location":"#@get,-@post,-@put,-@patch,-@delete","page":"Overview","title":"@get, @post, @put, @patch, @delete","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" @get(path, func)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\npath string or router() Required. The route to register\nfunc function Required. The request handler for this route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Used to register a function to a specific endpoint to handle that corresponding type of request","category":"page"},{"location":"#@route","page":"Overview","title":"@route","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" @route(methods, path, func)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nmethods array Required. The types of HTTP requests to register to this route\npath string or router() Required. The route to register\nfunc function Required. The request handler for this route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Low-level macro that allows a route to be handle multiple request types","category":"page"},{"location":"#staticfiles","page":"Overview","title":"staticfiles","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" staticfiles(folder, mount)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nfolder string Required. The folder to serve files from\nmountdir string The root endpoint to mount files under (default is \"static\")\nset_headers function Customize the http response headers when returning these files\nloadfile function Customize behavior when loading files","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths.","category":"page"},{"location":"#dynamicfiles","page":"Overview","title":"dynamicfiles","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" dynamicfiles(folder, mount)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nfolder string Required. The folder to serve files from\nmountdir string The root endpoint to mount files under (default is \"static\")\nset_headers function Customize the http response headers when returning these files\nloadfile function Customize behavior when loading files","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths. The file is loaded on each request, potentially picking up any file changes.","category":"page"},{"location":"#Request-helper-functions","page":"Overview","title":"Request helper functions","text":"","category":"section"},{"location":"#html()","page":"Overview","title":"html()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" html(content, status, headers)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\ncontent string Required. The string to be returned as HTML\nstatus integer The HTTP response code (default is 200)\nheaders dict The headers for the HTTP response (default has content-type header set to \"text/html; charset=utf-8\")","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Helper function to designate when content should be returned as HTML","category":"page"},{"location":"#queryparams()","page":"Overview","title":"queryparams()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" queryparams(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the query parameters from a request as a Dict()","category":"page"},{"location":"#Body-Functions","page":"Overview","title":"Body Functions","text":"","category":"section"},{"location":"#text()","page":"Overview","title":"text()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" text(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the body of a request as a string","category":"page"},{"location":"#binary()","page":"Overview","title":"binary()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" binary(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the body of a request as a binary file (returns a vector of UInt8s)","category":"page"},{"location":"#json()","page":"Overview","title":"json()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" json(request, classtype)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object\nclasstype struct A struct to deserialize a JSON object into","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Deserialize the body of a request into a julia struct ","category":"page"},{"location":"tutorial/cron_scheduling/#Cron-Scheduling","page":"Cron Scheduling","title":"Cron Scheduling","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the Spring Cron Expressions page.","category":"page"},{"location":"tutorial/cron_scheduling/#Cron-Expression-Syntax","page":"Cron Scheduling","title":"Cron Expression Syntax","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The string has six single space-separated time and date fields:\n\n ┌───────────── second (0-59)\n │ ┌───────────── minute (0 - 59)\n │ │ ┌───────────── hour (0 - 23)\n │ │ │ ┌───────────── day of the month (1 - 31)\n │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)\n │ │ │ │ │ ┌───────────── day of the week (1 - 7)\n │ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)\n │ │ │ │ │ │\n * * * * * *","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to '*'). ","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"# In this example we see only the `seconds` part of the expression is defined. \n# This means that all following expressions are automatically defaulted to '*' expressions\n@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Scheduling-Endpoints","page":"Cron Scheduling","title":"Scheduling Endpoints","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The router() function has a keyword argument called cron, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"# execute at 8, 9 and 10 o'clock of every day.\n@get router(\"/cron-example\", cron=\"0 0 8-10 * * *\") function(req)\n println(\"here\")\nend\n\n# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)\nevery5 = router(\"/cron\", cron=\"*/5\")\n\n# this endpoint inherits the cron expression\n@get every5(\"/first\") function(req)\n println(\"first\")\nend\n\n# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5\n@get every5(\"/second\", cron=\"*/2\") function(req)\n println(\"second\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Scheduling-Functions","page":"Cron Scheduling","title":"Scheduling Functions","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"In addition to scheduling endpoints, you can also use the new @cron macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend\n\n@cron \"0 0/30 8-10 * * *\" function()\n println(\"runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Starting-and-Stopping-Cron-Jobs","page":"Cron Scheduling","title":"Starting & Stopping Cron Jobs","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.","category":"page"},{"location":"tutorial/first_steps/#First-Steps","page":"First Steps","title":"First Steps","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"In this tutorial, you'll learn about all the core features of Oxygen ia a simple step-by-step approach. This guide will be aimed at beginner/intermediate users and will gradually build upon each other. ","category":"page"},{"location":"tutorial/first_steps/#Setup-your-first-project","page":"First Steps","title":"Setup your first project","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Navigate to your projects folder (If you're using and editor like vscode, just open up your project folder","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"cd /path/to/your/project","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Open open a terminal and start the julia repl with this command","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"julia","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Before we go any further, lets create a new environment for this project. Press the ] key inside the repl to use Pkg (julia's jult in package manager) you should see something similar to (v1.7) pkg> in the repl","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Activate your current environment ","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"pkg> activate .","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Install the latest version of Oxygen and HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"pkg> add Oxygen HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Press the backspace button to exit the package manager and return to the julia repl","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"If everything was done correctly, you should see a Project.toml and Manifest.toml file created in your project folder","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Next lets create our src folder and our main.jl file. Once that's complete, our project should ook something like this.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"project\n├── src\n│ ├── main.jl\n├── Project.toml\n├── Manifest.toml\n","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"For the duration of this guide, we will be working out of the src/main.jl file ","category":"page"},{"location":"tutorial/first_steps/#Creating-your-first-web-server","page":"First Steps","title":"Creating your first web server","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Here's an example of what a simple Oxygen server could look like","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"module Main \nusing Oxygen\nusing HTTP\n\n@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend\n\nserve()\nend","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Start the webserver with:","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"include(\"src/main.jl\")","category":"page"},{"location":"tutorial/first_steps/#Line-by-line","page":"First Steps","title":"Line by line","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"using Oxygen\nusing HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Here we pull in the both libraries our api depends on. The @get macro and serve() function come from Oxygen and the HTTP.Request type comes from the HTTP library.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Next we move into the core snippet where we define a route for our api. This route is made up of several components.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"http method => from @get macro (it's a GET request)\npath => the endpoint that will get added to our api which is \"/greet\"\nrequest handler => The function that accepts a request and returns a response","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Finally at the bottom of our Main module we have this function to start up our brand new webserver. This function can take a number of keyword arguments such as the host & port, which can be helpful if you don't want to use the default values.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"serve()","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"For example, you can start your server on port 8000 instead of 8080 which is used by default","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"serve(port=8000)","category":"page"},{"location":"tutorial/first_steps/#Try-out-your-endpoints","page":"First Steps","title":"Try out your endpoints","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"You should see the server starting up inside the console. You should be able to hit http://127.0.0.1:8080/greet inside your browser and see the following:","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"\"hello world!\"","category":"page"},{"location":"tutorial/first_steps/#Interactive-API-documenation","page":"First Steps","title":"Interactive API documenation","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Open your browser to http://127.0.0.1:8080/docs Here you'll see the auto-generated documentation for your api. This is done internally by generating a JSON object that conforms to the openapi format. Once generated, you can feed this same schema to libraries like swagger which translate this into an interactive api for you to explore.","category":"page"}] +[{"location":"tutorial/bigger_applications/#Bigger-Applications-Multiple-Files","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"If you are building an application or a web API, it's rarely the case that you can put everything on a single file.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"As your application grows you'll need to spread your application's logic across multiple files. Oxygen provides some tools to help you do this while staying organized.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Let's say you have an application that looks something like this:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"app\n├── src\n│ ├── main.jl\n│ └── MathOperations.jl\n│\n├── Project.toml\n└── Manifest.toml","category":"page"},{"location":"tutorial/bigger_applications/#How-to-use-the-router()-function","page":"Bigger Applications - Multiple Files","title":"How to use the router() function","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Let's say you have a file dedicated to handling mathematical operations in the submodule at /src/MathOperations.jl.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You might want the first part of each path to have the same value and just switch out the subpath to keep things organized in your api. You can use the router function to do just that. ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function is an HOF (higher order function) that allows you to reuse the same properties across multiple endpoints.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Because the generated router is just a function, they can be exported and shared across multiple files & modules.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using Oxygen\n\nmath = router(\"/math\", tags=[\"math\"])\n\n@get math(\"/multiply/{a}/{b}\", tags=[\"multiplication\"]) function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get math(\"/divide/{a}/{b}\") function(req, a::Float64, b::Float64)\n return a / b\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/#Tagging-your-routes","page":"Bigger Applications - Multiple Files","title":"Tagging your routes","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"By using the hello router in both endpoints, it passes along all the properties as default values. For example If we look at the routes registered in the application they will look like:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"/math/multiply/{a}/{b}\n/math/divide/{a}/{b}","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Both endpoints in this case will be tagged to the math tag and the /multiply endpoint will have an additional tag appended just to this endpoint called multiplication. These tags are used by Oxygen when auto-generating the documentation to organize it by separating the endpoints into sections based off their tags. ","category":"page"},{"location":"tutorial/bigger_applications/#Middleware-and-router()","page":"Bigger Applications - Multiple Files","title":"Middleware & router()","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function has a middleware parameter which takes a vector of middleware functions which are used to intercept all incoming requests & outgoing responses.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"All middleware is additive and any middleware defined in these layers will be combined and executed.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You can assign middleware at three levels:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application \nrouter \nroute ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Middleware will always get executed in the following order:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application -> router -> route","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"the application layer can only be set from the serve() and serveparallel() functions. While the other two layers can be set using the router() function.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"# Set middleware at the application level\nserve(middleware=[])\n\n# Set middleware at the Router level\nmyrouter = router(\"/router\", middleware=[])\n\n# Set middleware at the Route level\n@get myrouter(\"/example\", middleware=[]) function()\n return \"example\"\nend","category":"page"},{"location":"tutorial/bigger_applications/#Router-Level-Middleware","page":"Bigger Applications - Multiple Files","title":"Router Level Middleware","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"At the router level, any middleware defined here will be reused across all other routes that use this router(). In the example below, both /greet/hello and /greet/bonjour routes will send requests through the same middleware functions before either endpoint is called","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\n# middleware1 is defined at the router level\ngreet = router(\"/greet\", middleware=[middleware1])\n\n@get greet(\"/hello\") function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend","category":"page"},{"location":"tutorial/bigger_applications/#Route-Specific-Middleware","page":"Bigger Applications - Multiple Files","title":"Route Specific Middleware","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"At the route level, you can customize what middleware functions should be applied on a route by route basis. In the example below, the /greet/hello route gets both middleware1 & middleware2 functions applied to it, while the /greet/bonjour route only has middleware1 function which it inherited from the greet router.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\nfunction middleware2(handle)\n function(req)\n println(\"this is the 2nd middleware function\")\n handle(req)\n end\nend\n\n# middleware1 is added at the router level\ngreet = router(\"/greet\", middleware=[middleware1])\n\n# middleware2 is added at the route level\n@get greet(\"/hello\", middleware=[middleware2]) function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/#Skipping-Middleware-layers","page":"Bigger Applications - Multiple Files","title":"Skipping Middleware layers","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"Well, what if we don't want previous layers of middleware to run? By setting middleware=[], it clears all middleware functions at that layer and skips all layers that come before it. These changes are localized and only affect the components where these values are set.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"For example, setting middleware=[] at the:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"application layer -> clears the application layer\nrouter layer -> no application middleware is applied to this router\nroute layer -> no router middleware is applied to this route","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"You can set the router's middleware parameter to an empty vector to bypass any application level middleware. In the example below, all requests to endpoints registered to the greet router() will skip any application level middleware and get executed directly.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"function middleware1(handle)\n function(req)\n println(\"this is the 1st middleware function\")\n handle(req)\n end\nend\n\ngreet = router(\"/greet\", middleware=[])\n\n@get greet(\"/hello\") function()\n println(\"hello\")\nend\n\n@get greet(\"/bonjour\") function()\n println(\"bonjour\")\nend\n\nserve(middleware=[middleware1])","category":"page"},{"location":"tutorial/bigger_applications/#Repeat-Actions","page":"Bigger Applications - Multiple Files","title":"Repeat Actions","text":"","category":"section"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The router() function has an interval parameter which is used to call a request handler on a set interval (in seconds). ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"It's important to note that request handlers that use this property can't define additional function parameters outside of the default HTTP.Request parameter.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"In the example below, the /repeat/hello endpoint is called every 0.5 seconds and \"hello\" is printed to the console each time.","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using Oxygen\n\nrepeat = router(\"/repeat\", interval=0.5, tags=[\"repeat\"])\n\n@get repeat(\"/hello\") function()\n println(\"hello\")\nend\n\n# you can override properties by setting route specific values \n@get repeat(\"/bonjour\", interval=1.5) function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"If you want to call an endpoint with parameters on a set interval, you're better off creating an endpoint to perform the action you want and a second endpoint to call the first on a set interval. ","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"using HTTP\nusing Oxygen\n\nrepeat = router(\"/repeat\", interval=1.5, tags=[\"repeat\"])\n\n@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get repeat(\"/multiply\") function()\n response = internalrequest(HTTP.Request(\"GET\", \"/multiply/3/5\"))\n println(response)\n return response\nend\n\nserve()","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"The example above will print the response from the /multiply endpoint in the console below every 1.5 seconds and should look like this:","category":"page"},{"location":"tutorial/bigger_applications/","page":"Bigger Applications - Multiple Files","title":"Bigger Applications - Multiple Files","text":"\"\"\"\nHTTP/1.1 200 OK\nContent-Type: application/json; charset=utf-8\n\n15.0\"\"\"","category":"page"},{"location":"tutorial/request_types/#Request-Types","page":"Request Types","title":"Request Types","text":"","category":"section"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"When designing an API you need to first think about what type of requests and what routes or paths your api would need to function. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"For example, if we were to design a weather app we'd probably want a way to lookup weather alerts for a particular state","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"http://localhost:8080/weather/alerts/{state}","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This url can be broken down into several parts ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"host → http://localhost\nport → 8080\nroute or path → /weather/alerts/{state}\npath parameter → {state}","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Before we start writing code for we need to answer some questions: ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"What kind of data manipulation is this route going to perform?\nAre we adding/removing/updating data? (This determines our http method)\nWill this endpoint need any inputs?\nIf so, will we need to pass them through the path or inside the body of the http request?","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This is when knowing the different type of http methods comes in handy.","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Common HTTP methods:","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"POST → when you want to create some data\nGET → when you want to get data\nPUT → update some data if it already exists or create it\nPATCH → when you want to update some data\nDELETE → when you want to delete some data","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"(there are more methods that aren't in this list)","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"In the HTTP protocol, you can communicate to each path using one (or more) of these \"methods\".","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"In reality you can use any of these http methods to do any of those operations. But it's heavily recommended to use the appropriate http method so that people & machines can easily understand your web api. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Now back to our web example. Lets answer those questions:","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"This endpoint will return alerts from the US National Weather service api\nThe only input we will need is the state abbreviation","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"Since we will only be fetching data and not creating/updating/deleting anything, that means we will want to setup a GET route for our api to handle this type of action.","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"using Oxygen\nusing HTTP\n\n@get \"/weather/alerts/{state}\" function(req::HTTP.Request, state::String)\n return HTTP.get(\"https://api.weather.gov/alerts/active?area=$state\")\nend\n\nserve() ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"With our code in place, we can run this code and visit the endpoint in our browser to view the alerts. Try it out yourself by clicking on the link below. ","category":"page"},{"location":"tutorial/request_types/","page":"Request Types","title":"Request Types","text":"http://127.0.0.1:8080/weather/alerts/NY","category":"page"},{"location":"api/#Api","page":"Api","title":"Api","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"Documentation for Oxygen.jl","category":"page"},{"location":"api/#Starting-the-webserver","page":"Api","title":"Starting the webserver","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"serve\nserveparallel","category":"page"},{"location":"api/#Oxygen.serve","page":"Api","title":"Oxygen.serve","text":"serve(; middleware::Vector=[], handler=stream_handler, host=\"127.0.0.1\", port=8080, async=false, parallel=false, serialize=true, catch_errors=true, docs=true, metrics=true, show_errors=true, show_banner=true, docs_path=\"/docs\", schema_path=\"/schema\", external_url=nothing, kwargs...)\n\nStart the webserver with your own custom request handler\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.serveparallel","page":"Api","title":"Oxygen.serveparallel","text":"serveparallel(; middleware::Vector=[], handler=stream_handler, host=\"127.0.0.1\", port=8080, serialize=true, async=false, catch_errors=true, docs=true, metrics=true, kwargs...)\n\n\n\n\n\n","category":"function"},{"location":"api/#Routing","page":"Api","title":"Routing","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@get(path, func)\n@post(path, func)\n@put(path, func)\n@patch(path, func)\n@delete(path, func)\n@route(methods, path, func)\n\nget(path, func)\npost(path, func)\nput(path, func)\npatch(path, func)\ndelete(path, func)\nroute(methods, path, func)","category":"page"},{"location":"api/#Oxygen.@get-Tuple{Any, Any}","page":"Api","title":"Oxygen.@get","text":"@get(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle GET requests \n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@post-Tuple{Any, Any}","page":"Api","title":"Oxygen.@post","text":"@post(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle POST requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@put-Tuple{Any, Any}","page":"Api","title":"Oxygen.@put","text":"@put(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle PUT requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@patch-Tuple{Any, Any}","page":"Api","title":"Oxygen.@patch","text":"@patch(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle PATCH requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@delete-Tuple{Any, Any}","page":"Api","title":"Oxygen.@delete","text":"@delete(path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle DELETE requests\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@route-Tuple{Any, Any, Any}","page":"Api","title":"Oxygen.@route","text":"@route(methods::Array{String}, path::String, func::Function)\n\nUsed to register a function to a specific endpoint to handle mulitiple request types\n\n\n\n\n\n","category":"macro"},{"location":"api/#Mounting-Files","page":"Api","title":"Mounting Files","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@staticfiles\n@dynamicfiles\nstaticfiles\ndynamicfiles","category":"page"},{"location":"api/#Oxygen.@staticfiles","page":"Api","title":"Oxygen.@staticfiles","text":"@staticfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])\n\nMount all files inside the /static folder (or user defined mount point)\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.@dynamicfiles","page":"Api","title":"Oxygen.@dynamicfiles","text":"@dynamicfiles(folder::String, mountdir::String, headers::Vector{Pair{String,String}}=[])\n\nMount all files inside the /static folder (or user defined mount point), but files are re-read on each request\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.staticfiles","page":"Api","title":"Oxygen.staticfiles","text":"staticfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)\n\nMount all files inside the /static folder (or user defined mount point). The headers array will get applied to all mounted files\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.dynamicfiles","page":"Api","title":"Oxygen.dynamicfiles","text":"dynamicfiles(folder::String, mountdir::String; headers::Vector{Pair{String,String}}=[], loadfile::Union{Function,Nothing}=nothing)\n\nMount all files inside the /static folder (or user defined mount point), but files are re-read on each request. The headers array will get applied to all mounted files\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Autogenerated-Docs","page":"Api","title":"Autogenerated Docs","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"configdocs\nenabledocs\ndisabledocs\nisdocsenabled\nmergeschema\nsetschema\ngetschema","category":"page"},{"location":"api/#Oxygen.configdocs","page":"Api","title":"Oxygen.configdocs","text":"configdocs(docspath::String = \"/docs\", schemapath::String = \"/schema\")\n\nConfigure the default docs and schema endpoints\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.mergeschema","page":"Api","title":"Oxygen.mergeschema","text":"mergeschema(route::String, customschema::Dict)\n\nMerge the schema of a specific route\n\n\n\n\n\nmergeschema(customschema::Dict)\n\nMerge the top-level autogenerated schema with a custom schema\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.setschema","page":"Api","title":"Oxygen.setschema","text":"setschema(customschema::Dict)\n\nOverwrites the entire internal schema\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.getschema","page":"Api","title":"Oxygen.getschema","text":"getschema()\n\nReturn the current internal schema for this app\n\n\n\n\n\n","category":"function"},{"location":"api/#Helper-functions","page":"Api","title":"Helper functions","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"queryparams\nformdata\nhtml\ntext\nfile\nxml\njs\njson\ncss\nbinary","category":"page"},{"location":"api/#Oxygen.Core.Util.queryparams","page":"Api","title":"Oxygen.Core.Util.queryparams","text":"queryparams(request::HTTP.Request)\n\nParse's the query parameters from the Requests URL and return them as a Dict\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.formdata","page":"Api","title":"Oxygen.Core.Util.formdata","text":"formdata(request::HTTP.Request)\n\nRead the html form data from the body of a HTTP.Request\n\n\n\n\n\nformdata(request::HTTP.Response)\n\nRead the html form data from the body of a HTTP.Response\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.html","page":"Api","title":"Oxygen.Core.Util.html","text":"html(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as HTML\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.text","page":"Api","title":"Oxygen.Core.Util.text","text":"text(request::HTTP.Request)\n\nRead the body of a HTTP.Request as a String\n\n\n\n\n\ntext(response::HTTP.Response)\n\nRead the body of a HTTP.Response as a String\n\n\n\n\n\ntext(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as plain text\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.file","page":"Api","title":"Oxygen.Core.Util.file","text":"file(filepath::String; loadfile=nothing, status = 200, headers = []) :: HTTP.Response\n\nReads a file and returns a HTTP.Response. The file is read as binary. If the file does not exist, an ArgumentError is thrown. The MIME type and the size of the file are added to the headers.\n\nArguments\n\nfilepath: The path to the file to be read.\nloadfile: An optional function to load the file. If not provided, the file is read using the open function.\nstatus: The HTTP status code to be used in the response. Defaults to 200.\nheaders: Any additional headers to be included in the response. Defaults to an empty array.\n\nReturns\n\nA HTTP response.\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.xml","page":"Api","title":"Oxygen.Core.Util.xml","text":"xml(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as XML\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.js","page":"Api","title":"Oxygen.Core.Util.js","text":"js(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as JavaScript\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.json","page":"Api","title":"Oxygen.Core.Util.json","text":"json(request::HTTP.Request; keyword_arguments...)\n\nRead the body of a HTTP.Request as JSON with additional arguments for the read/serializer.\n\n\n\n\n\njson(request::HTTP.Request, classtype; keyword_arguments...)\n\nRead the body of a HTTP.Request as JSON with additional arguments for the read/serializer into a custom struct.\n\n\n\n\n\njson(response::HTTP.Response; keyword_arguments)\n\nRead the body of a HTTP.Response as JSON with additional keyword arguments\n\n\n\n\n\njson(response::HTTP.Response, classtype; keyword_arguments)\n\nRead the body of a HTTP.Response as JSON with additional keyword arguments and serialize it into a custom struct\n\n\n\n\n\njson(content::Any; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as JSON\n\n\n\n\n\njson(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA helper function that can be passed binary data that should be interpreted as JSON. No conversion is done on the content since it's already in binary format.\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.css","page":"Api","title":"Oxygen.Core.Util.css","text":"css(content::String; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a String that should be interpreted as CSS\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.binary","page":"Api","title":"Oxygen.Core.Util.binary","text":"binary(request::HTTP.Request)\n\nRead the body of a HTTP.Request as a Vector{UInt8}\n\n\n\n\n\nbinary(content::Vector{UInt8}; status::Int, headers::Vector{Pair}) :: HTTP.Response\n\nA convenience function to return a Vector of UInt8 that should be interpreted as binary data\n\n\n\n\n\n","category":"function"},{"location":"api/#Repeat-Tasks-and-Cron-Scheduling","page":"Api","title":"Repeat Tasks & Cron Scheduling","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"@cron\nstarttasks\nstoptasks\ncleartasks\nstartcronjobs\nstopcronjobs\nclearcronjobs\nclearcronjobs","category":"page"},{"location":"api/#Oxygen.@cron","page":"Api","title":"Oxygen.@cron","text":"@cron(expression::String, func::Function)\n\nRegisters a function with a cron expression. This will extract either the function name or the random Id julia assigns to each lambda function. \n\n\n\n\n\n@cron(expression::String, name::String, func::Function)\n\nThis variation provides way manually \"name\" a registered function. This information is used by the server on startup to log out all cron jobs.\n\n\n\n\n\n","category":"macro"},{"location":"api/#Oxygen.starttasks","page":"Api","title":"Oxygen.starttasks","text":"starttasks()\n\nStart all background repeat tasks\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.stoptasks","page":"Api","title":"Oxygen.stoptasks","text":"stoptasks()\n\nStop all background repeat tasks\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.cleartasks","page":"Api","title":"Oxygen.cleartasks","text":"cleartasks(ct::Context)\n\nClear any stored repeat task definitions\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.startcronjobs","page":"Api","title":"Oxygen.startcronjobs","text":"startcronjobs()\n\nStart all the cron cronjobs within their own async task. Each individual task will loop conintually and sleep untill the next time it's suppost to \n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.stopcronjobs","page":"Api","title":"Oxygen.stopcronjobs","text":"stopcronjobs()\n\nStop each background task by toggling a global reference that all cron jobs reference\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.clearcronjobs","page":"Api","title":"Oxygen.clearcronjobs","text":"Clears all cron job defintions\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Extra's","page":"Api","title":"Extra's","text":"","category":"section"},{"location":"api/","page":"Api","title":"Api","text":"router\ninternalrequest\nredirect\nterminate\nresetstate","category":"page"},{"location":"api/#Oxygen.router","page":"Api","title":"Oxygen.router","text":"No documentation found.\n\nBinding Oxygen.Core.AutoDoc.router does not exist.\n\n\n\n","category":"function"},{"location":"api/#Oxygen.internalrequest","page":"Api","title":"Oxygen.internalrequest","text":"internalrequest(req::HTTP.Request; middleware::Vector=[], serialize::Bool=true, catch_errors::Bool=true)\n\nDirectly call one of our other endpoints registered with the router, using your own middleware and bypassing any globally defined middleware\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.Core.Util.redirect","page":"Api","title":"Oxygen.Core.Util.redirect","text":"redirect(path::String; code = 308)\n\nreturn a redirect response \n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.terminate","page":"Api","title":"Oxygen.terminate","text":"terminate(ctx)\n\nstops the webserver immediately\n\n\n\n\n\n\n\n","category":"function"},{"location":"api/#Oxygen.resetstate","page":"Api","title":"Oxygen.resetstate","text":"resetstate()\n\nReset all the internal state variables\n\n\n\n\n\n","category":"function"},{"location":"tutorial/oauth2/#OAuth2-with-Umbrella.jl","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Umbrella.jl is a simple Julia authentication plugin, it supports Google and GitHub OAuth2 with more to come. Umbrella integrates with Julia web framework such as Genie.jl, Oxygen.jl or Mux.jl effortlessly.","category":"page"},{"location":"tutorial/oauth2/#Prerequisite","page":"OAuth2 with Umbrella.jl","title":"Prerequisite","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Before using the plugin, you need to obtain OAuth 2 credentials, see Google Identity Step 1, GitHub: Creating an OAuth App for details.","category":"page"},{"location":"tutorial/oauth2/#Installation","page":"OAuth2 with Umbrella.jl","title":"Installation","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"pkg> add Umbrella","category":"page"},{"location":"tutorial/oauth2/#Basic-Usage","page":"OAuth2 with Umbrella.jl","title":"Basic Usage","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Many resources are available describing how OAuth 2 works, please advice OAuth 2.0, Google Identity, or GitHub OAuth 2 for details","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Follow the steps below to enable OAuth 2 in your application. ","category":"page"},{"location":"tutorial/oauth2/#.-Configuration","page":"OAuth2 with Umbrella.jl","title":"1. Configuration","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"OAuth 2 required parameters such as client_id, client_secret and redirect_uri need to be configured through Configuration.Options. ","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"scopes is a list of resources the application will access on user's behalf, it is vary from one provider to another.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"providerOptions configures the additional parameters at the redirection step, it is dependent on the provider.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"const options = Configuration.Options(;\n client_id = \"\", # client id from an OAuth 2 provider\n client_secret = \"\", # secret from an OAuth 2 provider\n redirect_uri = \"http://localhost:3000/oauth2/google/callback\",\n success_redirect = \"/protected\",\n failure_redirect = \"/error\",\n scopes = [\"profile\", \"openid\", \"email\"],\n providerOptions = GoogleOptions(access_type=\"online\")\n)","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"init function takes the provider and options, then returns an OAuth 2 instance. Available provider values are :google, :github and facebook. This list is growing as more providers are supported.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"oauth2_instance = init(:google, options)","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"The examples will use Oxygen.jl as the web framework, but the concept is the same for other web frameworks.","category":"page"},{"location":"tutorial/oauth2/#.-Handle-provider-redirection","page":"OAuth2 with Umbrella.jl","title":"2. Handle provider redirection","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Create two endpoints,","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"/ serve the login page which, in this case, is a Google OAuth 2 link.\n/oauth2/google handles redirections to an OAuth 2 server.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"@get \"/\" function ()\n return \"Authenticate with Google\"\nend\n\n@get \"/oauth2/google\" function ()\n oauth2_instance.redirect()\nend","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"redirect function generates the URL using the parameters in step 1, and redirects users to provider's OAuth 2 server to initiate the authentication and authorization process.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Once the users consent to grant access to one or more scopes requested by the application, OAuth 2 server responds the code for retrieving access token to a callback endpoint.","category":"page"},{"location":"tutorial/oauth2/#.-Retrieves-tokens","page":"OAuth2 with Umbrella.jl","title":"3. Retrieves tokens","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Finally, create the endpoint handling callback from the OAuth 2 server. The path must be identical to the path in redirect_uri from Configuration.Options.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"token_exchange function performs two actions,","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"Use code responded by the OAuth 2 server to exchange an access token.\nGet user profile using the access token.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"A handler is required for access/refresh tokens and user profile handling.","category":"page"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"@get \"/oauth2/google/callback\" function (req)\n query_params = queryparams(req)\n code = query_params[\"code\"]\n\n oauth2_instance.token_exchange(code, function (tokens, user)\n # handle tokens and user profile here\n end\n )\nend","category":"page"},{"location":"tutorial/oauth2/#Full-Example","page":"OAuth2 with Umbrella.jl","title":"Full Example","text":"","category":"section"},{"location":"tutorial/oauth2/","page":"OAuth2 with Umbrella.jl","title":"OAuth2 with Umbrella.jl","text":"using Oxygen\nusing Umbrella\nusing HTTP\n\nconst oauth_path = \"/oauth2/google\"\nconst oauth_callback = \"/oauth2/google/callback\"\n\nconst options = Configuration.Options(;\n client_id=\"\", # client id from Google API Console\n client_secret=\"\", # secret from Google API Console\n redirect_uri=\"http://127.0.0.1:8080$(oauth_callback)\",\n success_redirect=\"/protected\",\n failure_redirect=\"/no\",\n scopes=[\"profile\", \"openid\", \"email\"]\n)\n\nconst google_oauth2 = Umbrella.init(:google, options)\n\n@get \"/\" function()\n return \"Authenticate with Google\"\nend\n\n@get oauth_path function()\n # this handles the Google oauth2 redirect in the background\n google_oauth2.redirect()\nend\n\n@get oauth_callback function(req)\n query_params = queryparams(req)\n code = query_params[\"code\"]\n\n # handle tokens and user details\n google_oauth2.token_exchange(code, \n function (tokens::Google.Tokens, user::Google.User)\n println(tokens.access_token)\n println(tokens.refresh_token)\n println(user.email)\n end\n )\nend\n\n@get \"/protected\" function()\n \"Congrets, You signed in Successfully!\"\nend\n\n# start the web server\nserve()","category":"page"},{"location":"tutorial/query_parameters/#Query-Parameters","page":"Query Parameters","title":"Query Parameters","text":"","category":"section"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as \"query\" parameters.","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"In the example below, we have two query parameters passed to our request handler","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"debug = true \nlimit = 10","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"http://127.0.0.1:8000/echo?debug=true&limit=10","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"To show how this works, lets take a look at this route below:","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"@get \"/echo\" function(req)\n # the queryparams() function will extract all query paramaters from the url \n return queryparams(req)\nend","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"If we hit this route with a url like the one below we should see the query parameters returned as a JSON object ","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"{\n \"debug\": \"true\",\n \"limit\": \"10\"\n}","category":"page"},{"location":"tutorial/query_parameters/","page":"Query Parameters","title":"Query Parameters","text":"The important distinction between query parameters and path parameters is that they are not automatically converted for you. In this example debug & limit are set to a string even though those aren't the \"correct\" data types.","category":"page"},{"location":"tutorial/request_body/#Request-Body","page":"Request Body","title":"Request Body","text":"","category":"section"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"Whenever you need to send data from a client to your API, you send it as a request body.","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"A request body is data sent by the client to your API (usually JSON). A response body is the data your API sends to the client.","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"Request bodies are useful when you need to send more complicated information to an API. Imagine we wanted to request an uber/lyft to come pick us up. The app (a client) will have to send a lot of information to make this happen. It'd need to send information about the user (like location data, membership info) and data about the destination. The api in turn will have to figure out pricing, available drivers and potential routes to take. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"The inputs of this api are pretty complicated which means it's a perfect case where we'd want to use the request body to send this information. You could send this kind of information through the URL, but I'd highly recommend you don't. Request bodies can store data in pretty much any format which is a lot more flexible than what a URL can support.","category":"page"},{"location":"tutorial/request_body/#Example","page":"Request Body","title":"Example","text":"","category":"section"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"The request bodies can be read and converted to a Julia object by using the built-in json() helper function. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"struct Person\n name::String\n age::String\nend\n\n@post \"/create/struct\" function(req)\n # this will convert the request body directly into a Person struct\n person = json(req, Person)\n return \"hello $(person.name)!\"\nend\n\n@post \"/create/dict\" function(req)\n # this will convert the request body into a Julia Dict\n data = json(req)\n return \"\"\"hello $(data[\"name\"])!\"\"\"\nend","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"When converting JSON into struct's Oxygen will throw an error if the request body doesn't match the struct, all properties need to be visible and match the right type. ","category":"page"},{"location":"tutorial/request_body/","page":"Request Body","title":"Request Body","text":"If you don't pass a struct to convert the JSON into, then it will convert the JSON into a Julia Dictionary. This has the benefit of being able to take JSON of any shape which is helpful when your data can change shape or is unknown. ","category":"page"},{"location":"tutorial/path_parameters/#Path-Parameters","page":"Path Parameters","title":"Path Parameters","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"You can declare path \"parameters\" or \"variables\" inside your route with braces and those values are passed directly to your request handler. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"The values of {a} & {b} in the path will get passed to the request handler with the parameter with the same name. ","category":"page"},{"location":"tutorial/path_parameters/#Path-parameters-with-types","page":"Path Parameters","title":"Path parameters with types","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"You can declare the type of a path parameter in the function, using standard Julia type annotations","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Let's take a look back at our first example above we have code to add two numbers.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"In this line we have our request type, route, and function handler defined. Looking closer at our request handler, we can see our variables have type annotations attached to them. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Oxygen will use any type annotations you give it to try to convert the incoming data into that type. Granted, these are completely optional, if you leave out the type annotation then Oxygen will assume it's a string by default. Below is another way to write the same function without type annotations.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"@get \"/multiply/{a}/{b}\" function(req, a, b)\n return parse(Float64, a) * parse(Float64, b)\nend","category":"page"},{"location":"tutorial/path_parameters/#Autogenerated-Docs-and-Path-Types","page":"Path Parameters","title":"Autogenerated Docs & Path Types","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"And when you open your browser at http://127.0.0.1:8080/docs, you will see the autogenerated interactive documentation for your api. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"If type annotations were provided in the request handler, they will be taken into account when generating the openapi spec. This means that the generated documentation will know what the input types will be and will not only show, but enforce those types through the interactive documentation. ","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Practically, this means that your users will know exactly how to call your endpoint and your inputs will always remain up to date with the code. ","category":"page"},{"location":"tutorial/path_parameters/#Additional-Parameter-Type-Support","page":"Path Parameters","title":"Additional Parameter Type Support","text":"","category":"section"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"Oxygen supports a lot of different path parameter types outside of Julia's base primitives. More complex types & structs are automatically parsed and passed to your request handlers.","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"In most cases, Oxygen uses the built-in parse() function to parse incoming parameters. But when the parameter types start getting more complex (eg. Vector{Int64} or a custom struct), then Oxygen assumes the parameter is a JSON string and uses the JSON3 library to serialize the parameter into the corresponding type","category":"page"},{"location":"tutorial/path_parameters/","page":"Path Parameters","title":"Path Parameters","text":"using Dates\nusing Oxygen\nusing StructTypes\n\n@enum Fruit apple=1 orange=2 kiwi=3\n\nstruct Person \n name :: String \n age :: Int8\nend\n\n# Add a supporting struct types\nStructTypes.StructType(::Type{Person}) = StructTypes.Struct()\nStructTypes.StructType(::Type{Complex{Float64}}) = StructTypes.Struct()\n\n@get \"/fruit/{fruit}\" function(req, fruit::Fruit)\n return fruit\nend\n\n@get \"/date/{date}\" function(req, date::Date)\n return date\nend\n\n@get \"/datetime/{datetime}\" function(req, datetime::DateTime)\n return datetime\nend\n\n@get \"/complex/{complex}\" function(req, complex::Complex{Float64})\n return complex\nend\n\n@get \"/list/{list}\" function(req, list::Vector{Float32})\n return list\nend\n\n@get \"/data/{dict}\" function(req, dict::Dict{String, Any})\n return dict\nend\n\n@get \"/tuple/{tuple}\" function(req, tuple::Tuple{String, String})\n return tuple\nend\n\n@get \"/union/{value}\" function(req, value::Union{Bool, String, Float64})\n return value\nend\n\n@get \"/boolean/{bool}\" function(req, bool::Bool)\n return bool\nend\n\n@get \"/struct/{person}\" function(req, person::Person)\n return person\nend\n\n@get \"/float/{float}\" function (req, float::Float32)\n return float\nend\n\nserve()","category":"page"},{"location":"#Oxygen.jl","page":"Overview","title":"Oxygen.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"
      \n
      \n

      \n

      \n A breath of fresh air for programming web apps in Julia.\n

      \n

      \n Version\n documentation stable\n Build Status\n Coverage Status\n \n

      \n
      ","category":"page"},{"location":"#About","page":"Overview","title":"About","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen is a micro-framework built on top of the HTTP.jl library. Breathe easy knowing you can quickly spin up a web server with abstractions you're already familiar with.","category":"page"},{"location":"#Contact","page":"Overview","title":"Contact","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Need Help? Feel free to reach out on our social media channels.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"(Image: Chat on Discord) (Image: Discuss on GitHub)","category":"page"},{"location":"#Features","page":"Overview","title":"Features","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Straightforward routing\nReal-time Metrics Dashboard\nAuto-generated swagger documentation\nOut-of-the-box JSON serialization & deserialization (customizable)\nType definition support for path parameters\nRequest Extractors\nMultiple Instance Support\nMultithreading support\nWebsockets, Streaming, and Server-Sent Events\nCron Scheduling (on endpoints & functions)\nMiddleware chaining (at the application, router, and route levels)\nStatic & Dynamic file hosting\nTemplating Support\nPlotting Support\nProtocol Buffer Support\nRoute tagging\nRepeat tasks","category":"page"},{"location":"#Installation","page":"Overview","title":"Installation","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"pkg> add Oxygen","category":"page"},{"location":"#Minimalistic-Example","page":"Overview","title":"Minimalistic Example","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Create a web-server with very few lines of code","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\n@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Handlers","page":"Overview","title":"Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Handlers are used to connect your code to the server in a clean & straightforward way. They assign a url to a function and invoke the function when an incoming request matches that url.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Handlers can be imported from other modules and distributed across multiple files for better organization and modularity\nAll handlers have equivalent macro & function implementations and support do..end block syntax\nThe type of first argument is used to identify what kind of handler is being registered\nThis package assumes it's a Request handler by default when no type information is provided","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"There are 3 types of supported handlers:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Request Handlers\nStream Handlers\nWebsocket Handlers","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using HTTP\nusing Oxygen\n\n# Request Handler\n@get \"/\" function(req::HTTP.Request)\n ...\nend\n\n# Stream Handler\n@stream \"/stream\" function(stream::HTTP.Stream)\n ...\nend\n\n# Websocket Handler\n@websocket \"/ws\" function(ws::HTTP.WebSocket)\n ...\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"They are just functions which means there are many ways that they can be expressed and defined. Below is an example of several different ways you can express and assign a Request handler.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/greet\" function()\n \"hello world!\"\nend\n\n@get(\"/gruessen\") do \n \"Hallo Welt!\"\nend\n\n@get \"/saluer\" () -> begin\n \"Bonjour le monde!\"\nend\n\n@get \"/saludar\" () -> \"¡Hola Mundo!\"\n@get \"/salutare\" f() = \"ciao mondo!\"\n\n# This function can be declared in another module\nfunction subtract(req, a::Float64, b::Float64)\n return a - b\nend\n\n# register foreign request handlers like this\n@get \"/subtract/{a}/{b}\" subtract","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"
      More Handler Docs","category":"page"},{"location":"#Request-Handlers","page":"Overview","title":"Request Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Request handlers are used to handle HTTP requests. They are defined using macros or their function equivalents, and accept a HTTP.Request object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The default Handler when no type information is provided\nRouting Macros: @get, @post, @put, @patch, @delete, @route\nRouting Functions: get(), post(), put(), patch(), delete(), route()","category":"page"},{"location":"#Stream-Handlers","page":"Overview","title":"Stream Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Stream handlers are used to stream data. They are defined using the @stream macro or the stream() function and accept a HTTP.Stream object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@stream and stream() don't require a type definition on the first argument, they assume it's a stream.\nStream handlers can be assigned with standard routing macros & functions: @get, @post, etc\nYou need to explicitly include the type definition so Oxygen can identify this as a Stream handler","category":"page"},{"location":"#Websocket-Handlers","page":"Overview","title":"Websocket Handlers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Websocket handlers are used to handle websocket connections. They are defined using the @websocket macro or the websocket() function and accept a HTTP.WebSocket object as the first argument. These handlers support both function and do-block syntax.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@websocket and websocket() don't require a type definition on the first argument, they assume it's a websocket.\nWebsocket handlers can also be assigned with the @get macro or get() function, because the websocket protocol requires a GET request to initiate the handshake. \nYou need to explicitly include the type definition so Oxygen can identify this as a Websocket handler","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"
      ","category":"page"},{"location":"#Routing-Macro-and-Function-Syntax","page":"Overview","title":"Routing Macro & Function Syntax","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"There are two primary ways to register your request handlers: the standard routing macros or the routing functions which utilize the do-block syntax. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"For each routing macro, we now have a an equivalent routing function","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get -> get()\n@post -> post()\n@put -> put()\n@patch -> patch()\n@delete -> delete()\n@route -> route()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The only practical difference between the two is that the routing macros are called during the precompilation stage, whereas the routing functions are only called when invoked. (The routing macros call the routing functions under the hood)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# Routing Macro syntax\n@get \"/add/{x}/{y}\" function(request::HTTP.Request, x::Int, y::Int)\n x + y\nend\n\n# Routing Function syntax\nget(\"/add/{x}/{y}\") do request::HTTP.Request, x::Int, y::Int\n x + y\nend","category":"page"},{"location":"#Render-Functions","page":"Overview","title":"Render Functions","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen, by default, automatically identifies the Content-Type of the return value from a request handler when building a Response. This default functionality is quite useful, but it does have an impact on performance. In situations where the return type is known, It's recommended to use one of the pre-existing render functions to speed things up.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Here's a list of the currently supported render functions: html, text, json, file, xml, js, css, binary","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to use these functions:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen \n\nget(\"/html\") do \n html(\"

      Hello World

      \")\nend\n\nget(\"/text\") do \n text(\"Hello World\")\nend\n\nget(\"/json\") do \n json(Dict(\"message\" => \"Hello World\"))\nend\n\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In most cases, these functions accept plain strings as inputs. The only exceptions are the binary function, which accepts a Vector{UInt8}, and the json function which accepts any serializable type. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Each render function accepts a status and custom headers.\nThe Content-Type and Content-Length headers are automatically set by these render functions","category":"page"},{"location":"#Path-parameters","page":"Overview","title":"Path parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Path parameters are declared with braces and are passed directly to your request handler. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# use path params without type definitions (defaults to Strings)\n@get \"/add/{a}/{b}\" function(req, a, b)\n return parse(Float64, a) + parse(Float64, b)\nend\n\n# use path params with type definitions (they are automatically converted)\n@get \"/multiply/{a}/{b}\" function(req, a::Float64, b::Float64)\n return a * b\nend\n\n# The order of the parameters doesn't matter (just the name matters)\n@get \"/subtract/{a}/{b}\" function(req, b::Int64, a::Int64)\n return a - b\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Query-parameters","page":"Overview","title":"Query parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Query parameters can be declared directly inside of your handlers signature. Any parameter that isn't mentioned inside the route path is assumed to be a query parameter.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"If a default value is not provided, it's assumed to be a required parameter","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/query\" function(req::HTTP.Request, a::Int, message::String=\"hello world\")\n return (a, message)\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Alternatively, you can use the queryparams() function to extract the raw values from the url as a dictionary. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@get \"/query\" function(req::HTTP.Request)\n return queryparams(req)\nend","category":"page"},{"location":"#HTML-Forms","page":"Overview","title":"HTML Forms","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Use the formdata() function to extract and parse the form data from the body of a request. This function returns a dictionary of key-value pairs from the form","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# Setup a basic form\n@get \"/\" function()\n html(\"\"\"\n
      \n
      \n
      \n
      \n

      \n \n
      \n \"\"\")\nend\n\n# Parse the form data and return it\n@post \"/form\" function(req)\n data = formdata(req)\n return data\nend\n\nserve()","category":"page"},{"location":"#Return-JSON","page":"Overview","title":"Return JSON","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"All objects are automatically deserialized into JSON using the JSON3 library","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\n@get \"/data\" function(req::HTTP.Request)\n return Dict(\"message\" => \"hello!\", \"value\" => 99.3)\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Deserialize-and-Serialize-custom-structs","page":"Overview","title":"Deserialize & Serialize custom structs","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides some out-of-the-box serialization & deserialization for most objects but requires the use of StructTypes when converting structs","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\nusing StructTypes\n\nstruct Animal\n id::Int\n type::String\n name::String\nend\n\n# Add a supporting struct type definition so JSON3 can serialize & deserialize automatically\nStructTypes.StructType(::Type{Animal}) = StructTypes.Struct()\n\n@get \"/get\" function(req::HTTP.Request)\n # serialize struct into JSON automatically (because we used StructTypes)\n return Animal(1, \"cat\", \"whiskers\")\nend\n\n@post \"/echo\" function(req::HTTP.Request)\n # deserialize JSON from the request body into an Animal struct\n animal = json(req, Animal)\n # serialize struct back into JSON automatically (because we used StructTypes)\n return animal\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Extractors","page":"Overview","title":"Extractors","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen comes with several built-in extractors designed to reduce the amount of boilerplate required to serialize inputs to your handler functions. By simply defining a struct and specifying the data source, these extractors streamline the process of data ingestion & validation through a uniform api.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The serialized data is accessible through the payload property\nCan be used alongside other parameters and extractors\nDefault values can be assigned when defined with the @kwdef macro\nIncludes both global and local validators\nStruct definitions can be deeply nested","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Supported Extractors:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Path - extracts from path parameters\nQuery - extracts from query parameters, \nHeader - extracts from request headers\nForm - extracts form data from the request body\nBody - serializes the entire request body to a given type (String, Float64, etc..)\nProtoBuffer - extracts the ProtoBuf message from the request body (available through a package extension)\nJson - extracts json from the request body\nJsonFragment - extracts a \"fragment\" of the json body using the parameter name to identify and extract the corresponding top-level key","category":"page"},{"location":"#Using-Extractors-and-Parameters","page":"Overview","title":"Using Extractors & Parameters","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In this example we show that the Path extractor can be used alongside regular path parameters. This Also works with regular query parameters and the Query extractor.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"struct Add\n b::Int\n c::Int\nend\n\n@get \"/add/{a}/{b}/{c}\" function(req, a::Int, pathparams::Path{Add})\n add = pathparams.payload # access the serialized payload\n return a + add.b + add.c\nend","category":"page"},{"location":"#Default-Values","page":"Overview","title":"Default Values","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Default values can be setup with structs using the @kwdef macro.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@kwdef struct Pet\n name::String\n age::Int = 10\nend\n\n@post \"/pet\" function(req, params::Json{Pet})\n return params.payload # access the serialized payload\nend","category":"page"},{"location":"#Validation","page":"Overview","title":"Validation","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"On top of serializing incoming data, you can also define your own validation rules by using the validate function. In the example below we show how to use both global and local validators in your code.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Validators are completely optional\nDuring the validation phase, oxygen will call the global validator before running a local validator.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"import Oxygen: validate\n\nstruct Person\n name::String\n age::Int\nend\n\n# Define a global validator \nvalidate(p::Person) = p.age >= 0\n\n# Only the global validator is ran here\n@post \"/person\" function(req, newperson::Json{Person})\n return newperson.payload\nend\n\n# In this case, both global and local validators are ran (this also makes sure the person is age 21+)\n# You can also use this sytnax instead: Json(Person, p -> p.age >= 21)\n@post \"/adult\" function(req, newperson = Json{Person}(p -> p.age >= 21))\n return newperson.payload\nend","category":"page"},{"location":"#Interpolating-variables-into-endpoints","page":"Overview","title":"Interpolating variables into endpoints","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"You can interpolate variables directly into the paths, which makes dynamically registering routes a breeze ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"(Thanks to @anandijain for the idea)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\noperations = Dict(\"add\" => +, \"multiply\" => *)\nfor (pathname, operator) in operations\n @get \"/$pathname/{a}/{b}\" function (req, a::Float64, b::Float64)\n return operator(a, b)\n end\nend\n\n# start the web server\nserve()","category":"page"},{"location":"#Routers","page":"Overview","title":"Routers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The router() function is an HOF (higher order function) that allows you to reuse the same path prefix & properties across multiple endpoints. This is helpful when your api starts to grow and you want to keep your path operations organized.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below are the arguments the router() function can take:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"router(prefix::String; tags::Vector, middleware::Vector, interval::Real, cron::String)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"tags - are used to organize endpoints in the autogenerated docs\nmiddleware - is used to setup router & route-specific middleware\ninterval - is used to support repeat actions (calling a request handler on a set interval in seconds)\ncron - is used to specify a cron expression that determines when to call the request handler.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# Any routes that use this router will be automatically grouped \n# under the 'math' tag in the autogenerated documenation\nmath = router(\"/math\", tags=[\"math\"])\n\n# You can also assign route specific tags\n@get math(\"/multiply/{a}/{b}\", tags=[\"multiplication\"]) function(req, a::Float64, b::Float64)\n return a * b\nend\n\n@get math(\"/divide/{a}/{b}\") function(req, a::Float64, b::Float64)\n return a / b\nend\n\nserve()","category":"page"},{"location":"#Cron-Scheduling","page":"Overview","title":"Cron Scheduling","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the Spring Cron Expressions page.","category":"page"},{"location":"#Cron-Expression-Syntax","page":"Overview","title":"Cron Expression Syntax","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The string has six single space-separated time and date fields:\n\n ┌───────────── second (0-59)\n │ ┌───────────── minute (0 - 59)\n │ │ ┌───────────── hour (0 - 23)\n │ │ │ ┌───────────── day of the month (1 - 31)\n │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)\n │ │ │ │ │ ┌───────────── day of the week (1 - 7)\n │ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)\n │ │ │ │ │ │\n * * * * * *","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to '*'). ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# In this example we see only the `seconds` part of the expression is defined. \n# This means that all following expressions are automatically defaulted to '*' expressions\n@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend","category":"page"},{"location":"#Scheduling-Endpoints","page":"Overview","title":"Scheduling Endpoints","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The router() function has a keyword argument called cron, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# execute at 8, 9 and 10 o'clock of every day.\n@get router(\"/cron-example\", cron=\"0 0 8-10 * * *\") function(req)\n println(\"here\")\nend\n\n# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)\nevery5 = router(\"/cron\", cron=\"*/5\")\n\n# this endpoint inherits the cron expression\n@get every5(\"/first\") function(req)\n println(\"first\")\nend\n\n# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5\n@get every5(\"/second\", cron=\"*/2\") function(req)\n println(\"second\")\nend","category":"page"},{"location":"#Scheduling-Functions","page":"Overview","title":"Scheduling Functions","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In addition to scheduling endpoints, you can also use the new @cron macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend\n\n@cron \"0 0/30 8-10 * * *\" function()\n println(\"runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day\")\nend","category":"page"},{"location":"#Starting-and-Stopping-Cron-Jobs","page":"Overview","title":"Starting & Stopping Cron Jobs","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.","category":"page"},{"location":"#Repeat-Tasks","page":"Overview","title":"Repeat Tasks","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Repeat tasks provide a simple api to run a function on a set interval. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"There are two ways to register repeat tasks: ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Through the interval parameter in a router()\nUsing the @repeat macro","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"It's important to note that request handlers that use this property can't define additional function parameters outside of the default HTTP.Request parameter.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In the example below, the /repeat/hello endpoint is called every 0.5 seconds and \"hello\" is printed to the console each time.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The router() function has an interval parameter which is used to call a request handler on a set interval (in seconds). ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\ntaskrouter = router(\"/repeat\", interval=0.5, tags=[\"repeat\"])\n\n@get taskrouter(\"/hello\") function()\n println(\"hello\")\nend\n\n# you can override properties by setting route specific values \n@get taskrouter(\"/bonjour\", interval=1.5) function()\n println(\"bonjour\")\nend\n\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to register a repeat task outside of the router","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"@repeat 1.5 function()\n println(\"runs every 1.5 seconds\")\nend\n\n# you can also \"name\" a repeat task \n@repeat 5 \"every-five\" function()\n println(\"runs every 5 seconds\")\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"When the server is ran, all tasks are started automatically. But the module also provides utilities to have more fine-grained control over the running tasks using the following functions: starttasks(), stoptasks(), and cleartasks()","category":"page"},{"location":"#Multiple-Instances","page":"Overview","title":"Multiple Instances","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"In some advanced scenarios, you might need to spin up multiple web severs within the same module on different ports. Oxygen provides both a static and dynamic way to create multiple instances of a web server.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"As a general rule of thumb, if you know how many instances you need ahead of time it's best to go with the static approach.","category":"page"},{"location":"#Static:-multiple-instance's-with-@oxidise","page":"Overview","title":"Static: multiple instance's with @oxidise","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a new macro which makes it possible to setup and run multiple instances. It generates methods and binds them to a new internal state for the current module. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In the example below, two simple servers are defined within modules A and B and are started in the parent module. Both modules contain all of the functions exported from Oxygen which can be called directly as shown below.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"module A\n using Oxygen; @oxidise\n\n get(\"/\") do\n text(\"server A\")\n end\nend\n\nmodule B\n using Oxygen; @oxidise\n\n get(\"/\") do\n text(\"server B\")\n end\nend\n\ntry \n # start both instances\n A.serve(port=8001, async=true)\n B.serve(port=8002, async=false)\nfinally\n # shut down if we `Ctrl+C`\n A.terminate()\n B.terminate()\nend","category":"page"},{"location":"#Dynamic:-multiple-instance's-with-instance()","page":"Overview","title":"Dynamic: multiple instance's with instance()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"The instance function helps you create a completely independent instance of an Oxygen web server at runtime. It works by dynamically creating a julia module at runtime and loading the Oxygen code within it.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"All of the same methods from Oxygen are available under the named instance. In the example below we can use the get, and serve by simply using dot syntax on the app1 variable to access the underlying methods.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n######### Setup the first app #########\n\napp1 = instance()\n\napp1.get(\"/\") do\n text(\"server A\")\nend\n\n######### Setup the second app #########\n\napp2 = instance()\n\napp2.get(\"/\") do\n text(\"server B\")\nend\n\n######### Start both instances #########\n\ntry \n # start both servers together\n app1.serve(port=8001, async=true)\n app2.serve(port=8002)\nfinally\n # clean it up\n app1.terminate()\n app2.terminate()\nend","category":"page"},{"location":"#Multithreading-and-Parallelism","page":"Overview","title":"Multithreading & Parallelism","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"For scenarios where you need to handle higher amounts of traffic, you can run Oxygen in a multithreaded mode. In order to utilize this mode, julia must have more than 1 thread to work with. You can start a julia session with 4 threads using the command below","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"julia --threads 4","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"serveparallel() Starts the webserver in streaming mode and handles requests in a cooperative multitasking approach. This function uses Threads.@spawn to schedule a new task on any available thread. Meanwhile, @async is used inside this task when calling each request handler. This allows the task to yield during I/O operations.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing StructTypes\nusing Base.Threads\n\n# Make the Atomic struct serializable\nStructTypes.StructType(::Type{Atomic{Int64}}) = StructTypes.Struct()\n\nx = Atomic{Int64}(0);\n\n@get \"/show\" function()\n return x\nend\n\n@get \"/increment\" function()\n atomic_add!(x, 1)\n return x\nend\n\n# start the web server in parallel mode\nserveparallel()","category":"page"},{"location":"#Protocol-Buffers","page":"Overview","title":"Protocol Buffers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen includes an extension for the ProtoBuf.jl package. This extension provides a protobuf() function, simplifying the process of working with Protocol Buffers in the context of web server. For a better understanding of this package, please refer to its official documentation.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"This function has overloads for the following scenarios:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Decoding a protobuf message from the body of an HTTP request.\nEncoding a protobuf message into the body of an HTTP request.\nEncoding a protobuf message into the body of an HTTP response.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using HTTP\nusing ProtoBuf\nusing Oxygen\n\n# The generated classes need to be created ahead of time (check the protobufs)\ninclude(\"people_pb.jl\");\nusing .people_pb: People, Person\n\n# Decode a Protocol Buffer Message \n@post \"/count\" function(req::HTTP.Request)\n # decode the request body into a People object\n message = protobuf(req, People)\n # count the number of Person objects\n return length(message.people)\nend\n\n# Encode & Return Protocol Buffer message\n@get \"/get\" function()\n message = People([\n Person(\"John Doe\", 20),\n Person(\"Alice\", 30),\n Person(\"Bob\", 35)\n ])\n # seralize the object inside the body of a HTTP.Response\n return protobuf(message)\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"The following is an example of a schema that was used to create the necessary Julia bindings. These bindings allow for the encoding and decoding of messages in the above example.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"syntax = \"proto3\";\nmessage Person {\n string name = 1;\n sint32 age = 2;\n}\nmessage People {\n repeated Person people = 1;\n}","category":"page"},{"location":"#Plotting","page":"Overview","title":"Plotting","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen is equipped with several package extensions that enhance its plotting capabilities. These extensions make it easy to return plots directly from request handlers. All operations are performed in-memory using an IOBuffer and return a HTTP.Response","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Supported Packages and their helper utils:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"CairoMakie.jl: png, svg, pdf, html\nWGLMakie.jl: html\nBonito.jl: html","category":"page"},{"location":"#CairoMakie.jl","page":"Overview","title":"CairoMakie.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using CairoMakie: heatmap\nusing Oxygen\n\n@get \"/cairo\" function()\n fig, ax, pl = heatmap(rand(50, 50))\n png(fig)\nend\n\nserve()","category":"page"},{"location":"#WGLMakie.jl","page":"Overview","title":"WGLMakie.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using Bonito\nusing WGLMakie: heatmap\nusing Oxygen\nusing Oxygen: html # Bonito also exports html\n\n@get \"/wgl\" function()\n fig = heatmap(rand(50, 50))\n html(fig)\nend\n\nserve()","category":"page"},{"location":"#Bonito.jl","page":"Overview","title":"Bonito.jl","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"using Bonito\nusing WGLMakie: heatmap\nusing Oxygen\nusing Oxygen: html # Bonito also exports html\n\n@get \"/bonito\" function()\n app = App() do\n return DOM.div(\n DOM.h1(\"Random 50x50 Heatmap\"), \n DOM.div(heatmap(rand(50, 50)))\n )\n end\n return html(app)\nend\n\nserve()","category":"page"},{"location":"#Templating","page":"Overview","title":"Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Rather than building an internal engine for templating or adding additional dependencies, Oxygen provides two package extensions to support Mustache.jl and OteraEngine.jl templates.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a simple wrapper api around both packages that makes it easy to render templates from strings, templates, and files. This wrapper api returns a render function which accepts a dictionary of inputs to fill out the template.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In all scenarios, the rendered template is returned inside a HTTP.Response object ready to get served by the api. By default, the mime types are auto-detected either by looking at the content of the template or the extension name on the file. If you know the mime type you can pass it directly through the mime_type keyword argument to skip the detection process.","category":"page"},{"location":"#Mustache-Templating","page":"Overview","title":"Mustache Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Please take a look at the Mustache.jl documentation to learn the full capabilities of the package","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 1: Rendering a Mustache Template from a File","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Mustache\nusing Oxygen\n\n# Load the Mustache template from a file and create a render function\nrender = mustache(\"./templates/greeting.txt\", from_file=false)\n\n@get \"/mustache/file\" function()\n data = Dict(\"name\" => \"Chris\")\n return render(data) # This will return an HTML.Response with the rendered template\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 2: Specifying MIME Type for a plain string Mustache Template","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Mustache\nusing Oxygen\n\n# Define a Mustache template (both plain strings and mustache templates are supported)\ntemplate_str = \"Hello, {{name}}!\"\n\n# Create a render function, specifying the MIME type as text/plain\nrender = mustache(template_str, mime_type=\"text/plain\") # mime_type keyword arg is optional \n\n@get \"/plain/text\" function()\n data = Dict(\"name\" => \"Chris\")\n return render(data) # This will return a plain text response with the rendered template\nend","category":"page"},{"location":"#Otera-Templating","page":"Overview","title":"Otera Templating","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Please take a look at the OteraEngine.jl documentation to learn the full capabilities of the package","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 1: Rendering an Otera Template with Logic and Loops","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using OteraEngine\nusing Oxygen\n\n# Define an Otera template\ntemplate_str = \"\"\"\n\n {{ title }}\n \n {% for name in names %}\n Hello {{ name }}
      \n {% end %}\n \n\n\"\"\"\n\n# Create a render function for the Otera template\nrender = otera(template_str)\n\n@get \"/otera/loop\" function()\n data = Dict(\"title\" => \"Greetings\", \"names\" => [\"Alice\", \"Bob\", \"Chris\"])\n return render(data) # This will return an HTML.Response with the rendered template\nend","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In this example, an Otera template is defined with a for-loop that iterates over a list of names, greeting each name.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Example 2: Running Julia Code in Otera Template","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using OteraEngine\nusing Oxygen\n\n# Define an Otera template with embedded Julia code\ntemplate_str = \"\"\"\nThe square of {{ number }} is {< number^2 >}.\n\"\"\"\n\n# Create a render function for the Otera template\nrender = otera(template_str)\n\n@get \"/otera/square\" function()\n data = Dict(\"number\" => 5)\n return render(data) # This will return an HTML.Response with the rendered template\nend\n","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"In this example, an Otera template is defined with embedded Julia code that calculates the square of a given number. ","category":"page"},{"location":"#Mounting-Static-Files","page":"Overview","title":"Mounting Static Files","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"You can mount static files using this handy function which recursively searches a folder for files and mounts everything. All files are loaded into memory on startup.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# mount all files inside the \"content\" folder under the \"/static\" path\nstaticfiles(\"content\", \"static\")\n\n# start the web server\nserve()","category":"page"},{"location":"#Mounting-Dynamic-Files","page":"Overview","title":"Mounting Dynamic Files","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Similar to staticfiles, this function mounts each path and re-reads the file for each request. This means that any changes to the files after the server has started will be displayed.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\n\n# mount all files inside the \"content\" folder under the \"/dynamic\" path\ndynamicfiles(\"content\", \"dynamic\")\n\n# start the web server\nserve()","category":"page"},{"location":"#Performance-Tips","page":"Overview","title":"Performance Tips","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios. Anecdotally, i've seen a 2-3x speedup in serve() and a 4-5x speedup in serveparallel() performance.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# This is how you disable internal logging in both modes\nserve(access_log=nothing)\nserveparallel(access_log=nothing)","category":"page"},{"location":"#Logging","page":"Overview","title":"Logging","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Oxygen provides a default logging format but allows you to customize the format using the access_log parameter. This functionality is available in both the serve() and serveparallel() functions.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"You can read more about the logging options here","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"# Uses the default logging format\nserve()\n\n# Customize the logging format \nserve(access_log=logfmt\"[$time_iso8601] \\\"$request\\\" $status\")\n\n# Disable internal request logging \nserve(access_log=nothing)","category":"page"},{"location":"#Middleware","page":"Overview","title":"Middleware","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Middleware functions make it easy to create custom workflows to intercept all incoming requests and outgoing responses. They are executed in the same order they are passed in (from left to right).","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"They can be set at the application, router, and route layer with the middleware keyword argument. All middleware is additive and any middleware defined in these layers will be combined and executed.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Middleware will always be executed in the following order:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"application -> router -> route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Now lets see some middleware in action:","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\n\nconst CORS_HEADERS = [\n \"Access-Control-Allow-Origin\" => \"*\",\n \"Access-Control-Allow-Headers\" => \"*\",\n \"Access-Control-Allow-Methods\" => \"POST, GET, OPTIONS\"\n]\n\n# https://juliaweb.github.io/HTTP.jl/stable/examples/#Cors-Server\nfunction CorsMiddleware(handler)\n return function(req::HTTP.Request)\n println(\"CORS middleware\")\n # determine if this is a pre-flight request from the browser\n if HTTP.method(req)==\"OPTIONS\"\n return HTTP.Response(200, CORS_HEADERS) \n else \n return handler(req) # passes the request to the AuthMiddleware\n end\n end\nend\n\nfunction AuthMiddleware(handler)\n return function(req::HTTP.Request)\n println(\"Auth middleware\")\n # ** NOT an actual security check ** #\n if !HTTP.headercontains(req, \"Authorization\", \"true\")\n return HTTP.Response(403)\n else \n return handler(req) # passes the request to your application\n end\n end\nend\n\nfunction middleware1(handle)\n function(req)\n println(\"middleware1\")\n handle(req)\n end\nend\n\nfunction middleware2(handle)\n function(req)\n println(\"middleware2\")\n handle(req)\n end\nend\n\n# set middleware at the router level\nmath = router(\"math\", middleware=[middleware1])\n\n# set middleware at the route level \n@get math(\"/divide/{a}/{b}\", middleware=[middleware2]) function(req, a::Float64, b::Float64)\n return a / b\nend\n\n# set application level middleware\nserve(middleware=[CorsMiddleware, AuthMiddleware])","category":"page"},{"location":"#Custom-Response-Serializers","page":"Overview","title":"Custom Response Serializers","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"If you don't want to use Oxygen's default response serializer, you can turn it off and add your own! Just create your own special middleware function to serialize the response and add it at the end of your own middleware chain. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Both serve() and serveparallel() have a serialize keyword argument which can toggle off the default serializer.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing HTTP\nusing JSON3\n\n@get \"/divide/{a}/{b}\" function(req::HTTP.Request, a::Float64, b::Float64)\n return a / b\nend\n\n# This is just a regular middleware function\nfunction myserializer(handle)\n function(req)\n try\n response = handle(req)\n # convert all responses to JSON\n return HTTP.Response(200, [], body=JSON3.write(response)) \n catch error \n @error \"ERROR: \" exception=(error, catch_backtrace())\n return HTTP.Response(500, \"The Server encountered a problem\")\n end \n end\nend\n\n# make sure 'myserializer' is the last middleware function in this list\nserve(middleware=[myserializer], serialize=false)","category":"page"},{"location":"#Autogenerated-Docs-with-Swagger","page":"Overview","title":"Autogenerated Docs with Swagger","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":"Swagger documentation is automatically generated for each route you register in your application. Only the route name, parameter types, and 200 & 500 responses are automatically created for you by default. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"You can view your generated documentation at /docs, and the schema can be found under /docs/schema. Both of these values can be changed to whatever you want using the configdocs() function. You can also opt out of autogenerated docs entirely by calling the disabledocs() function before starting your application. ","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"To add additional details you can either use the built-in mergeschema() or setschema() functions to directly modify the schema yourself or merge the generated schema from the SwaggerMarkdown.jl package (I'd recommend the latter)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to merge the schema generated from the SwaggerMarkdown.jl package.","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing SwaggerMarkdown\n\n# Here's an example of how you can merge autogenerated docs from SwaggerMarkdown.jl into your api\n@swagger \"\"\"\n/divide/{a}/{b}:\n get:\n description: Return the result of a / b\n parameters:\n - name: a\n in: path\n required: true\n description: this is the value of the numerator \n schema:\n type : number\n responses:\n '200':\n description: Successfully returned an number.\n\"\"\"\n@get \"/divide/{a}/{b}\" function (req, a::Float64, b::Float64)\n return a / b\nend\n\n# title and version are required\ninfo = Dict(\"title\" => \"My Demo Api\", \"version\" => \"1.0.0\")\nopenApi = OpenAPI(\"3.0\", info)\nswagger_document = build(openApi)\n \n# merge the SwaggerMarkdown schema with the internal schema\nmergeschema(swagger_document)\n\n# start the web server\nserve()","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Below is an example of how to manually modify the schema","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"using Oxygen\nusing SwaggerMarkdown\n\n# Only the basic information is parsed from this route when generating docs\n@get \"/multiply/{a}/{b}\" function (req, a::Float64, b::Float64)\n return a * b\nend\n\n# Here's an example of how to update a part of the schema yourself\nmergeschema(\"/multiply/{a}/{b}\", \n Dict(\n \"get\" => Dict(\n \"description\" => \"return the result of a * b\"\n )\n )\n)\n\n# Here's another example of how to update a part of the schema yourself, but this way allows you to modify other properties defined at the root of the schema (title, summary, etc.)\nmergeschema(\n Dict(\n \"paths\" => Dict(\n \"/multiply/{a}/{b}\" => Dict(\n \"get\" => Dict(\n \"description\" => \"return the result of a * b\"\n )\n )\n )\n )\n)","category":"page"},{"location":"#API-Reference-(macros)","page":"Overview","title":"API Reference (macros)","text":"","category":"section"},{"location":"#@get,-@post,-@put,-@patch,-@delete","page":"Overview","title":"@get, @post, @put, @patch, @delete","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" @get(path, func)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\npath string or router() Required. The route to register\nfunc function Required. The request handler for this route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Used to register a function to a specific endpoint to handle that corresponding type of request","category":"page"},{"location":"#@route","page":"Overview","title":"@route","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" @route(methods, path, func)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nmethods array Required. The types of HTTP requests to register to this route\npath string or router() Required. The route to register\nfunc function Required. The request handler for this route","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Low-level macro that allows a route to be handle multiple request types","category":"page"},{"location":"#staticfiles","page":"Overview","title":"staticfiles","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" staticfiles(folder, mount)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nfolder string Required. The folder to serve files from\nmountdir string The root endpoint to mount files under (default is \"static\")\nset_headers function Customize the http response headers when returning these files\nloadfile function Customize behavior when loading files","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths.","category":"page"},{"location":"#dynamicfiles","page":"Overview","title":"dynamicfiles","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" dynamicfiles(folder, mount)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nfolder string Required. The folder to serve files from\nmountdir string The root endpoint to mount files under (default is \"static\")\nset_headers function Customize the http response headers when returning these files\nloadfile function Customize behavior when loading files","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Serve all static files within a folder. This function recursively searches a directory and mounts all files under the mount directory using their relative paths. The file is loaded on each request, potentially picking up any file changes.","category":"page"},{"location":"#Request-helper-functions","page":"Overview","title":"Request helper functions","text":"","category":"section"},{"location":"#html()","page":"Overview","title":"html()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" html(content, status, headers)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\ncontent string Required. The string to be returned as HTML\nstatus integer The HTTP response code (default is 200)\nheaders dict The headers for the HTTP response (default has content-type header set to \"text/html; charset=utf-8\")","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Helper function to designate when content should be returned as HTML","category":"page"},{"location":"#queryparams()","page":"Overview","title":"queryparams()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" queryparams(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the query parameters from a request as a Dict()","category":"page"},{"location":"#Body-Functions","page":"Overview","title":"Body Functions","text":"","category":"section"},{"location":"#text()","page":"Overview","title":"text()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" text(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the body of a request as a string","category":"page"},{"location":"#binary()","page":"Overview","title":"binary()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" binary(request)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Returns the body of a request as a binary file (returns a vector of UInt8s)","category":"page"},{"location":"#json()","page":"Overview","title":"json()","text":"","category":"section"},{"location":"","page":"Overview","title":"Overview","text":" json(request, classtype)","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Parameter Type Description\nreq HTTP.Request Required. The HTTP request object\nclasstype struct A struct to deserialize a JSON object into","category":"page"},{"location":"","page":"Overview","title":"Overview","text":"Deserialize the body of a request into a julia struct ","category":"page"},{"location":"tutorial/cron_scheduling/#Cron-Scheduling","page":"Cron Scheduling","title":"Cron Scheduling","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the Spring Cron Expressions page.","category":"page"},{"location":"tutorial/cron_scheduling/#Cron-Expression-Syntax","page":"Cron Scheduling","title":"Cron Expression Syntax","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The string has six single space-separated time and date fields:\n\n ┌───────────── second (0-59)\n │ ┌───────────── minute (0 - 59)\n │ │ ┌───────────── hour (0 - 23)\n │ │ │ ┌───────────── day of the month (1 - 31)\n │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)\n │ │ │ │ │ ┌───────────── day of the week (1 - 7)\n │ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)\n │ │ │ │ │ │\n * * * * * *","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to '*'). ","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"# In this example we see only the `seconds` part of the expression is defined. \n# This means that all following expressions are automatically defaulted to '*' expressions\n@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Scheduling-Endpoints","page":"Cron Scheduling","title":"Scheduling Endpoints","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"The router() function has a keyword argument called cron, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"# execute at 8, 9 and 10 o'clock of every day.\n@get router(\"/cron-example\", cron=\"0 0 8-10 * * *\") function(req)\n println(\"here\")\nend\n\n# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)\nevery5 = router(\"/cron\", cron=\"*/5\")\n\n# this endpoint inherits the cron expression\n@get every5(\"/first\") function(req)\n println(\"first\")\nend\n\n# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5\n@get every5(\"/second\", cron=\"*/2\") function(req)\n println(\"second\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Scheduling-Functions","page":"Cron Scheduling","title":"Scheduling Functions","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"In addition to scheduling endpoints, you can also use the new @cron macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"@cron \"*/2\" function()\n println(\"runs every 2 seconds\")\nend\n\n@cron \"0 0/30 8-10 * * *\" function()\n println(\"runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day\")\nend","category":"page"},{"location":"tutorial/cron_scheduling/#Starting-and-Stopping-Cron-Jobs","page":"Cron Scheduling","title":"Starting & Stopping Cron Jobs","text":"","category":"section"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.","category":"page"},{"location":"tutorial/cron_scheduling/","page":"Cron Scheduling","title":"Cron Scheduling","text":"In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.","category":"page"},{"location":"tutorial/first_steps/#First-Steps","page":"First Steps","title":"First Steps","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"In this tutorial, you'll learn about all the core features of Oxygen ia a simple step-by-step approach. This guide will be aimed at beginner/intermediate users and will gradually build upon each other. ","category":"page"},{"location":"tutorial/first_steps/#Setup-your-first-project","page":"First Steps","title":"Setup your first project","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Navigate to your projects folder (If you're using and editor like vscode, just open up your project folder","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"cd /path/to/your/project","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Open open a terminal and start the julia repl with this command","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"julia","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Before we go any further, lets create a new environment for this project. Press the ] key inside the repl to use Pkg (julia's jult in package manager) you should see something similar to (v1.7) pkg> in the repl","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Activate your current environment ","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"pkg> activate .","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Install the latest version of Oxygen and HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"pkg> add Oxygen HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Press the backspace button to exit the package manager and return to the julia repl","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"If everything was done correctly, you should see a Project.toml and Manifest.toml file created in your project folder","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Next lets create our src folder and our main.jl file. Once that's complete, our project should ook something like this.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"project\n├── src\n│ ├── main.jl\n├── Project.toml\n├── Manifest.toml\n","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"For the duration of this guide, we will be working out of the src/main.jl file ","category":"page"},{"location":"tutorial/first_steps/#Creating-your-first-web-server","page":"First Steps","title":"Creating your first web server","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Here's an example of what a simple Oxygen server could look like","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"module Main \nusing Oxygen\nusing HTTP\n\n@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend\n\nserve()\nend","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Start the webserver with:","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"include(\"src/main.jl\")","category":"page"},{"location":"tutorial/first_steps/#Line-by-line","page":"First Steps","title":"Line by line","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"using Oxygen\nusing HTTP","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Here we pull in the both libraries our api depends on. The @get macro and serve() function come from Oxygen and the HTTP.Request type comes from the HTTP library.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Next we move into the core snippet where we define a route for our api. This route is made up of several components.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"http method => from @get macro (it's a GET request)\npath => the endpoint that will get added to our api which is \"/greet\"\nrequest handler => The function that accepts a request and returns a response","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"@get \"/greet\" function(req::HTTP.Request)\n return \"hello world!\"\nend","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Finally at the bottom of our Main module we have this function to start up our brand new webserver. This function can take a number of keyword arguments such as the host & port, which can be helpful if you don't want to use the default values.","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"serve()","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"For example, you can start your server on port 8000 instead of 8080 which is used by default","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"serve(port=8000)","category":"page"},{"location":"tutorial/first_steps/#Try-out-your-endpoints","page":"First Steps","title":"Try out your endpoints","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"You should see the server starting up inside the console. You should be able to hit http://127.0.0.1:8080/greet inside your browser and see the following:","category":"page"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"\"hello world!\"","category":"page"},{"location":"tutorial/first_steps/#Interactive-API-documenation","page":"First Steps","title":"Interactive API documenation","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"First Steps","title":"First Steps","text":"Open your browser to http://127.0.0.1:8080/docs Here you'll see the auto-generated documentation for your api. This is done internally by generating a JSON object that conforms to the openapi format. Once generated, you can feed this same schema to libraries like swagger which translate this into an interactive api for you to explore.","category":"page"}] } diff --git a/dev/tutorial/bigger_applications/index.html b/dev/tutorial/bigger_applications/index.html index 59fd6fb3..5d066a85 100644 --- a/dev/tutorial/bigger_applications/index.html +++ b/dev/tutorial/bigger_applications/index.html @@ -118,4 +118,4 @@ HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 -15.0""" +15.0""" diff --git a/dev/tutorial/cron_scheduling/index.html b/dev/tutorial/cron_scheduling/index.html index 78552baa..c1792df8 100644 --- a/dev/tutorial/cron_scheduling/index.html +++ b/dev/tutorial/cron_scheduling/index.html @@ -35,4 +35,4 @@ @cron "0 0/30 8-10 * * *" function() println("runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day") -end

      Starting & Stopping Cron Jobs

      When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.

      In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.

      +end

      Starting & Stopping Cron Jobs

      When you run serve() or serveparallel(), all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the terminate() function or manually killing the server with ctrl+C.

      In addition, Oxygen provides utility functions to manually start and stop cron jobs: startcronjobs() and stopcronjobs(). These functions can be used outside of a web server as well.

      diff --git a/dev/tutorial/first_steps/index.html b/dev/tutorial/first_steps/index.html index c155d838..60ea8cc0 100644 --- a/dev/tutorial/first_steps/index.html +++ b/dev/tutorial/first_steps/index.html @@ -16,4 +16,4 @@ end

      Start the webserver with:

      include("src/main.jl")

      Line by line

      using Oxygen
       using HTTP

      Here we pull in the both libraries our api depends on. The @get macro and serve() function come from Oxygen and the HTTP.Request type comes from the HTTP library.

      Next we move into the core snippet where we define a route for our api. This route is made up of several components.

      @get "/greet" function(req::HTTP.Request)
           return "hello world!"
      -end

      Finally at the bottom of our Main module we have this function to start up our brand new webserver. This function can take a number of keyword arguments such as the host & port, which can be helpful if you don't want to use the default values.

      serve()

      For example, you can start your server on port 8000 instead of 8080 which is used by default

      serve(port=8000)

      Try out your endpoints

      You should see the server starting up inside the console. You should be able to hit http://127.0.0.1:8080/greet inside your browser and see the following:

      "hello world!"

      Interactive API documenation

      Open your browser to http://127.0.0.1:8080/docs Here you'll see the auto-generated documentation for your api. This is done internally by generating a JSON object that conforms to the openapi format. Once generated, you can feed this same schema to libraries like swagger which translate this into an interactive api for you to explore.

      +end

      Finally at the bottom of our Main module we have this function to start up our brand new webserver. This function can take a number of keyword arguments such as the host & port, which can be helpful if you don't want to use the default values.

      serve()

      For example, you can start your server on port 8000 instead of 8080 which is used by default

      serve(port=8000)

      Try out your endpoints

      You should see the server starting up inside the console. You should be able to hit http://127.0.0.1:8080/greet inside your browser and see the following:

      "hello world!"

      Interactive API documenation

      Open your browser to http://127.0.0.1:8080/docs Here you'll see the auto-generated documentation for your api. This is done internally by generating a JSON object that conforms to the openapi format. Once generated, you can feed this same schema to libraries like swagger which translate this into an interactive api for you to explore.

      diff --git a/dev/tutorial/oauth2/index.html b/dev/tutorial/oauth2/index.html index d7cdda6a..b3623e1d 100644 --- a/dev/tutorial/oauth2/index.html +++ b/dev/tutorial/oauth2/index.html @@ -67,4 +67,4 @@ end # start the web server -serve() +serve() diff --git a/dev/tutorial/path_parameters/index.html b/dev/tutorial/path_parameters/index.html index bad12c97..07d055a7 100644 --- a/dev/tutorial/path_parameters/index.html +++ b/dev/tutorial/path_parameters/index.html @@ -62,4 +62,4 @@ return float end -serve() +serve() diff --git a/dev/tutorial/query_parameters/index.html b/dev/tutorial/query_parameters/index.html index 3b52566c..6b0beafb 100644 --- a/dev/tutorial/query_parameters/index.html +++ b/dev/tutorial/query_parameters/index.html @@ -5,4 +5,4 @@ end

      If we hit this route with a url like the one below we should see the query parameters returned as a JSON object

      {
           "debug": "true",
           "limit": "10"
      -}

      The important distinction between query parameters and path parameters is that they are not automatically converted for you. In this example debug & limit are set to a string even though those aren't the "correct" data types.

      +}

      The important distinction between query parameters and path parameters is that they are not automatically converted for you. In this example debug & limit are set to a string even though those aren't the "correct" data types.

      diff --git a/dev/tutorial/request_body/index.html b/dev/tutorial/request_body/index.html index dcaf84e7..06b02750 100644 --- a/dev/tutorial/request_body/index.html +++ b/dev/tutorial/request_body/index.html @@ -14,4 +14,4 @@ # this will convert the request body into a Julia Dict data = json(req) return """hello $(data["name"])!""" -end

      When converting JSON into struct's Oxygen will throw an error if the request body doesn't match the struct, all properties need to be visible and match the right type.

      If you don't pass a struct to convert the JSON into, then it will convert the JSON into a Julia Dictionary. This has the benefit of being able to take JSON of any shape which is helpful when your data can change shape or is unknown.

      +end

      When converting JSON into struct's Oxygen will throw an error if the request body doesn't match the struct, all properties need to be visible and match the right type.

      If you don't pass a struct to convert the JSON into, then it will convert the JSON into a Julia Dictionary. This has the benefit of being able to take JSON of any shape which is helpful when your data can change shape or is unknown.

      diff --git a/dev/tutorial/request_types/index.html b/dev/tutorial/request_types/index.html index 1d4ea86d..7fae3452 100644 --- a/dev/tutorial/request_types/index.html +++ b/dev/tutorial/request_types/index.html @@ -6,4 +6,4 @@ return HTTP.get("https://api.weather.gov/alerts/active?area=$state") end -serve()

      With our code in place, we can run this code and visit the endpoint in our browser to view the alerts. Try it out yourself by clicking on the link below.

      http://127.0.0.1:8080/weather/alerts/NY

      +serve()

      With our code in place, we can run this code and visit the endpoint in our browser to view the alerts. Try it out yourself by clicking on the link below.

      http://127.0.0.1:8080/weather/alerts/NY