Oxygen

所属分类:Linux/Unix编程
开发工具:Julia
文件大小:0KB
下载次数:0
上传日期:2024-02-02 02:31:25
上 传 者sh-1993
说明:  Julia编程web应用程序的新鲜空气
(A breath of fresh air for programming web apps in Julia)

文件列表:
data/
demo/
docs/
src/
test/
.travis.yml
LICENSE.md
Project.toml
oxygen.png

# Oxygen.jl

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

Version documentation stable Build Status Coverage Status

## About 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. ## Features - Straightforward routing - Real-time Metrics Dashboard - Auto-generated swagger documentation - Out-of-the-box JSON serialization & deserialization (customizable) - Type definition support for path parameters - Multithreading support - Cron Scheduling (on endpoints & functions) - Middleware chaining (at the application, router, and route levels) - Static & Dynamic file hosting - Templating Support - Route tagging - Repeat tasks ## Installation ```julia pkg> add Oxygen ``` ## Minimalistic Example Create a web-server with very few lines of code ```julia using Oxygen using HTTP @get "/greet" function(req::HTTP.Request) return "hello world!" end # start the web server serve() ``` ## Request handlers Request handlers are just functions, which means there are many valid ways to express them - Request handlers don't have to be defined where the routes are. They can be imported from other modules and spread across multiple files - Just like the request handlers, routes can be declared across multiple modules and files ```julia using Oxygen @get "/greet" function() "hello world!" end @get "/saluer" () -> begin "Bonjour le monde!" end @get "/saludar" () -> "Hola Mundo!" @get "/salutare" f() = "ciao mondo!" # This function can be declared in another module function subtract(req, a::Float64, b::Float64) return a - b end # register foreign request handlers like this @get "/subtract/{a}/{b}" subtract # start the web server serve() ``` ## Routing Macro & Function Syntax There are two primary ways to register your request handlers: the standard routing macros or the routing functions which utilize the do-block syntax. For each routing macro, we now have a an equivalent routing function ```julia @get -> get() @post -> post() @put -> put() @patch -> patch() @delete -> delete() @route -> route() ``` 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) ```julia # Routing Macro syntax @get "/add/{x}/{y}" function(request::HTTP.Request, x::Int, y::Int) x + y end # Routing Function syntax get("/add/{x}/{y}") do request::HTTP.Request, x::Int, y::Int x + y end ``` ## Render Functions 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. Here's a list of the currently supported render functions: `html`, `text`, `json`, `file`, `xml`, `js`, `css`, `binary` Below is an example of how to use these functions: ```julia using Oxygen get("/html") do html("

Hello World

") end get("/text") do text("Hello World") end get("/json") do json(Dict("message" => "Hello World")) end serve() ``` 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. - Each render function accepts a status and custom headers. - The Content-Type and Content-Length headers are automatically set by these render functions ## Path parameters Path parameters are declared with braces and are passed directly to your request handler. ```julia using Oxygen # use path params without type definitions (defaults to Strings) @get "/add/{a}/{b}" function(req, a, b) return parse(Float64, a) + parse(Float64, b) end # use path params with type definitions (they are automatically converted) @get "/multiply/{a}/{b}" function(req, a::Float64, b::Float64) return a * b end # The order of the parameters doesn't matter (just the name matters) @get "/subtract/{a}/{b}" function(req, b::Int64, a::Int64) return a - b end # start the web server serve() ``` ## Query parameters Use the `queryparams()` function to extract and parse parameters from the url ```julia using Oxygen using HTTP @get "/query" function(req::HTTP.Request) # extract & return the query params from the request object return queryparams(req) end # start the web server serve() ``` ## Interpolating variables into endpoints You can interpolate variables directly into the paths, which makes dynamically registering routes a breeze (Thanks to @anandijain for the idea) ```julia using Oxygen operations = Dict("add" => +, "multiply" => *) for (pathname, operator) in operations @get "/$pathname/{a}/{b}" function (req, a::Float64, b::Float64) return operator(a, b) end end # start the web server serve() ``` ## Return JSON All objects are automatically deserialized into JSON using the JSON3 library ```julia using Oxygen using HTTP @get "/data" function(req::HTTP.Request) return Dict("message" => "hello!", "value" => 99.3) end # start the web server serve() ``` ## Deserialize & Serialize custom structs Oxygen provides some out-of-the-box serialization & deserialization for most objects but requires the use of StructTypes when converting structs ```julia using Oxygen using HTTP using StructTypes struct Animal id::Int type::String name::String end # Add a supporting struct type definition so JSON3 can serialize & deserialize automatically StructTypes.StructType(::Type{Animal}) = StructTypes.Struct() @get "/get" function(req::HTTP.Request) # serialize struct into JSON automatically (because we used StructTypes) return Animal(1, "cat", "whiskers") end @post "/echo" function(req::HTTP.Request) # deserialize JSON from the request body into an Animal struct animal = json(req, Animal) # serialize struct back into JSON automatically (because we used StructTypes) return animal end # start the web server serve() ``` ## Routers 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. Below are the arguments the `router()` function can take: ```julia router(prefix::String; tags::Vector, middleware::Vector, interval::Real, cron::String) ``` - `tags` - are used to organize endpoints in the autogenerated docs - `middleware` - is used to setup router & route-specific middleware - `interval` - is used to support repeat actions (*calling a request handler on a set interval in seconds*) - `cron` - is used to specify a cron expression that determines when to call the request handler. ```julia using Oxygen # Any routes that use this router will be automatically grouped # under the 'math' tag in the autogenerated documenation math = router("/math", tags=["math"]) # You can also assign route specific tags @get math("/multiply/{a}/{b}", tags=["multiplication"]) function(req, a::Float64, b::Float64) return a * b end @get math("/divide/{a}/{b}") function(req, a::Float64, b::Float64) return a / b end serve() ``` ## Cron Scheduling 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. 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. 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](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html) page. ### Cron Expression Syntax 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. ``` The string has six single space-separated time and date fields: ┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (1 - 7) │ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7) │ │ │ │ │ │ * * * * * * ``` Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to `'*'`). ```julia # In this example we see only the `seconds` part of the expression is defined. # This means that all following expressions are automatically defaulted to '*' expressions @cron "*/2" function() println("runs every 2 seconds") end ``` ### Scheduling Endpoints 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. ```julia # execute at 8, 9 and 10 o'clock of every day. @get router("/cron-example", cron="0 0 8-10 * * *") function(req) println("here") end # execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0) every5 = router("/cron", cron="*/5") # this endpoint inherits the cron expression @get every5("/first") function(req) println("first") end # Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5 @get every5("/second", cron="*/2") function(req) println("second") end ``` ### Scheduling Functions 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. ```julia @cron "*/2" function() println("runs every 2 seconds") end @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. ## Repeat Tasks The `router()` function has an `interval` parameter which is used to call a request handler on a set interval (in seconds). **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.** In the example below, the `/repeat/hello` endpoint is called every 0.5 seconds and `"hello"` is printed to the console each time. ```julia using Oxygen repeat = router("/repeat", interval=0.5, tags=["repeat"]) @get repeat("/hello") function() println("hello") end # you can override properties by setting route specific values @get repeat("/bonjour", interval=1.5) function() println("bonjour") end serve() ``` ## Templating 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. 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. 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. ### Mustache Templating Please take a look at the [Mustache.jl](https://jverzani.github.io/Mustache.jl/dev/) documentation to learn the full capabilities of the package Example 1: Rendering a Mustache Template from a File ```julia using Mustache using Oxygen # Load the Mustache template from a file and create a render function render = mustache("./templates/greeting.txt", from_file=false) @get "/mustache/file" function() data = Dict("name" => "Chris") return render(data) # This will return an HTML.Response with the rendered template end ``` Example 2: Specifying MIME Type for a plain string Mustache Template ```julia using Mustache using Oxygen # Define a Mustache template (both plain strings and mustache templates are supported) template_str = "Hello, {{name}}!" # Create a render function, specifying the MIME type as text/plain render = mustache(template_str, mime_type="text/plain") # mime_type keyword arg is optional @get "/plain/text" function() data = Dict("name" => "Chris") return render(data) # This will return a plain text response with the rendered template end ``` ### Otera Templating Please take a look at the [OteraEngine.jl](https://mommawatasu.github.io/OteraEngine.jl/dev/tutorial/#API) documentation to learn the full capabilities of the package Example 1: Rendering an Otera Template with Logic and Loops ```julia using OteraEngine using Oxygen # Define an Otera template template_str = """ {{ title }} {% for name in names %} Hello {{ name }}
{% end %} """ # Create a render function for the Otera template render = otera(template_str) @get "/otera/loop" function() data = Dict("title" => "Greetings", "names" => ["Alice", "Bob", "Chris"]) return render(data) # This will return an HTML.Response with the rendered template end ``` In this example, an Otera template is defined with a for-loop that iterates over a list of names, greeting each name. Example 2: Running Julia Code in Otera Template ```julia using OteraEngine using Oxygen # Define an Otera template with embedded Julia code template_str = """ The square of {{ number }} is {< number^2 >}. """ # Create a render function for the Otera template render = otera(template_str) @get "/otera/square" function() data = Dict("number" => 5) return render(data) # This will return an HTML.Response with the rendered template end ``` In this example, an Otera template is defined with embedded Julia code that calculates the square of a given number. ## Mounting Static Files 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. ```julia using Oxygen # mount all files inside the "content" folder under the "/static" path staticfiles("content", "static") # start the web server serve() ``` ## Mounting Dynamic Files 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. ```julia using Oxygen # mount all files inside the "content" folder under the "/dynamic" path dynamicfiles("content", "dynamic") # start the web server serve() ``` ## Performance Tips 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. ```julia # This is how you disable internal logging in both modes serve(access_log=nothing) serveparallel(access_log=nothing) ``` ## Logging 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. You can read more about the logging options [here](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.@logfmt_str) ```julia # Uses the default logging format serve() # Customize the logging format serve(access_log=logfmt"[$time_iso8601] \"$request\" $status") # Disable internal request logging serve(access_log=nothing) ``` ## Middleware 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). 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. Middleware will always be executed in the following order: ``` application -> router -> route ``` Now lets see some middleware in action: ```julia using Oxygen using HTTP const CORS_HEADERS = [ "Access-Control-Allow-Origin" => "*", "Access-Control-Allow-Headers" => "*", "Access-Control-Allow-Methods" => "POST, GET, OPTIONS" ] # https://juliaweb.github.io/HTTP.jl/stable/examples/#Cors-Server function CorsMiddleware(handler) return function(req::HTTP.Request) println("CORS middleware") # determine if this is a pre-flight request from the browser if HTTP.method(req)=="OPTIONS" return HTTP.Response(200, CORS_HEADERS) else return handler(req) # passes the request to the AuthMiddleware end end end function AuthMiddleware(handler) return function(req::HTTP.Request) println("Auth middleware") # ** NOT an actual security check ** # if !HTTP.headercontains(req, "Authorization", "true") return HTTP.Response(403) else return handler(req) # passes the request to your application end end end function middleware1(handle) function(req) println("midd ... ...

近期下载者

相关文件


收藏者