Oxygen es un micro-marco construido sobre la biblioteca http.jl. Respira tranquila sabiendo que puede girar rápidamente un servidor web con abstracciones con las que ya está familiarizado.
¿Necesitar ayuda? Siéntase libre de comunicarse con nuestros canales de redes sociales.
pkg > add OxygenCree un servidor web con muy pocas líneas de código
using Oxygen
using HTTP
@get " /greet " function (req :: HTTP.Request )
return " hello world! "
end
# start the web server
serve ()Los manejadores se utilizan para conectar su código al servidor de manera limpia y directa. Asignan una URL a una función e invocan la función cuando una solicitud entrante coincide con esa URL.
do..end End Bloque de sintaxisRequest de forma predeterminada cuando no se proporciona información de tipoHay 3 tipos de manejadores compatibles:
Request manejadoresStreamWebsocket using HTTP
using Oxygen
# Request Handler
@get " / " function (req :: HTTP.Request )
...
end
# Stream Handler
@stream " /stream " function (stream :: HTTP.Stream )
...
end
# Websocket Handler
@websocket " /ws " function (ws :: HTTP.WebSocket )
...
end Son solo funciones, lo que significa que hay muchas maneras en que se pueden expresar y definir. A continuación se muestra un ejemplo de varias formas diferentes en que puede expresar y asignar un controlador Request .
@get " /greet " function ()
" hello world! "
end
@get ( " /gruessen " ) do
" Hallo Welt! "
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 Los manejadores de solicitud se utilizan para manejar las solicitudes HTTP. Se definen usando macros o sus equivalentes de función, y aceptan un objeto HTTP.Request como el primer argumento. Estos manejadores admiten tanto la función como la sintaxis de bloqueo.
@get , @post , @put , @patch , @delete , @routeget() , post() , put() , patch() , delete() , route() Los manejadores de transmisión se utilizan para transmitir datos. Se definen usando la función @stream macro o la función stream() y aceptan un objeto HTTP.Stream como el primer argumento. Estos manejadores admiten tanto la función como la sintaxis de bloqueo.
@stream y stream() no requieren una definición de tipo en el primer argumento, suponen que es una secuencia.Stream se pueden asignar con macros y funciones de enrutamiento estándar: @get , @post , etc.Stream Los manejadores de WebSocket se utilizan para manejar las conexiones WebSocket. Se definen usando la función @websocket Macro o la función websocket() y aceptan un objeto HTTP.WebSocket como el primer argumento. Estos manejadores admiten tanto la función como la sintaxis de bloqueo.
@websocket y websocket() no requieren una definición de tipo en el primer argumento, suponen que es un WebSocket.Websocket también se pueden asignar con la función @get macro u get() , porque el protocolo WebSocket requiere una solicitud GET para iniciar el apretón de manos.Websocket Hay dos formas principales de registrar sus manejadores de solicitud: las macros de enrutamiento estándar o las funciones de enrutamiento que utilizan la sintaxis Do-Block.
Para cada macro de enrutamiento, ahora tenemos una función de enrutamiento equivalente
@get -> get ()
@post -> post ()
@put -> put ()
@patch -> patch ()
@delete -> delete ()
@route -> route ()La única diferencia práctica entre los dos es que las macros de enrutamiento se llaman durante la etapa de precompilación, mientras que las funciones de enrutamiento solo se llaman cuando se invoca. (Las macros de enrutamiento llaman a las funciones de enrutamiento debajo del capó)
# 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 El oxígeno, por defecto, identifica automáticamente el tipo de contenido del valor de retorno de un controlador de solicitud al construir una respuesta. Esta funcionalidad predeterminada es bastante útil, pero tiene un impacto en el rendimiento. En situaciones en las que se conoce el tipo de retorno, se recomienda utilizar una de las funciones de renderización preexistentes para acelerar las cosas.
Aquí hay una lista de las funciones de renderización actualmente compatibles: html , text , json , file , xml , js , css , binary
A continuación se muestra un ejemplo de cómo usar estas funciones:
using Oxygen
get ( " /html " ) do
html ( " <h1>Hello World</h1> " )
end
get ( " /text " ) do
text ( " Hello World " )
end
get ( " /json " ) do
json ( Dict ( " message " => " Hello World " ))
end
serve () En la mayoría de los casos, estas funciones aceptan cadenas simples como entradas. Las únicas excepciones son la función binary , que acepta un Vector{UInt8} , y la función json que acepta cualquier tipo serializable.
Los parámetros de la ruta se declaran con aparatos ortopédicos y se pasan directamente a su controlador de solicitud.
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 ()Los parámetros de consulta se pueden declarar directamente dentro de su firma de manejadores. Se supone que cualquier parámetro que no se menciona dentro de la ruta de ruta es un parámetro de consulta.
@get " /query " function (req :: HTTP.Request , a :: Int , message :: String = " hello world " )
return (a, message)
end Alternativamente, puede usar la función queryparams() para extraer los valores sin procesar de la URL como un diccionario.
@get " /query " function (req :: HTTP.Request )
return queryparams (req)
end Use la función formdata() para extraer y analizar los datos de formulario del cuerpo de una solicitud. Esta función devuelve un diccionario de pares de valor clave del formulario
using Oxygen
# Setup a basic form
@get " / " function ()
html ( """
<form action="/form" method="post">
<label for="firstname">First name:</label><br>
<input type="text" id="firstname" name="firstname"><br>
<label for="lastname">Last name:</label><br>
<input type="text" id="lastname" name="lastname"><br><br>
<input type="submit" value="Submit">
</form>
""" )
end
# Parse the form data and return it
@post " /form " function (req)
data = formdata (req)
return data
end
serve ()Todos los objetos se deserializan automáticamente en JSON usando la biblioteca JSON3
using Oxygen
using HTTP
@get " /data " function (req :: HTTP.Request )
return Dict ( " message " => " hello! " , " value " => 99.3 )
end
# start the web server
serve ()El oxígeno proporciona algunas serialización y deserialización de listones para usar para la mayoría de los objetos, pero requiere el uso de estructuras al convertir estructuras
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 ()El oxígeno viene con varios extractores incorporados diseñados para reducir la cantidad de calderas requeridas para serializar las entradas a las funciones de su controlador. Simplemente definiendo una estructura y especificando la fuente de datos, estos extractores agilizan el proceso de ingestión y validación de datos a través de una API uniforme.
payload@kwdefExtractores compatibles:
Path : extractos de los parámetros de rutaQuery : extractos de los parámetros de consulta,Header : extractos de encabezados de solicitudForm : extrae datos de formulario del cuerpo de solicitudBody : serializa todo el cuerpo de solicitud a un tipo determinado (String, Float64, etc.)ProtoBuffer : extrae el mensaje ProtoBuf del cuerpo de solicitud (disponible a través de una extensión de paquete)Json - Extrae a JSON del cuerpo de solicitudJsonFragment : extrae un "fragmento" del cuerpo JSON usando el nombre del parámetro para identificar y extraer la clave de nivel superior correspondiente En este ejemplo, mostramos que el extractor Path se puede usar junto con los parámetros de ruta regulares. Esto también funciona con parámetros de consulta regulares y el extractor Query .
struct Add
b :: Int
c :: Int
end
@get " /add/{a}/{b}/{c} " function (req, a :: Int , pathparams :: Path{Add} )
add = pathparams . payload # access the serialized payload
return a + add . b + add . c
end Los valores predeterminados se pueden configurar con estructuras utilizando el macro @kwdef .
@kwdef struct Pet
name :: String
age :: Int = 10
end
@post " /pet " function (req, params :: Json{Pet} )
return params . payload # access the serialized payload
end Además de la serialización de datos entrantes, también puede definir sus propias reglas de validación utilizando la función validate . En el siguiente ejemplo, mostramos cómo usar validadores global y local en su código.
global antes de ejecutar un validador local . import Oxygen : validate
struct Person
name :: String
age :: Int
end
# Define a global validator
validate (p :: Person ) = p . age >= 0
# Only the global validator is ran here
@post " /person " function (req, newperson :: Json{Person} )
return newperson . payload
end
# In this case, both global and local validators are ran (this also makes sure the person is age 21+)
# You can also use this sytnax instead: Json(Person, p -> p.age >= 21)
@post " /adult " function (req, newperson = Json {Person} (p -> p . age >= 21 ))
return newperson . payload
end Puede interpolar variables directamente en las rutas, lo que hace que las rutas de registro dinámicamente sean muy fáciles
(Gracias a @anandijain por la idea)
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 () La función router() es un HOF (función de orden superior) que le permite reutilizar el mismo prefijo de ruta y propiedades en múltiples puntos finales. Esto es útil cuando su API comienza a crecer y desea mantener organizadas sus operaciones de camino.
A continuación se presentan los argumentos que puede tomar la función router() :
router (prefix :: String ; tags :: Vector , middleware :: Vector , interval :: Real , cron :: String )tags : se utilizan para organizar puntos finales en los documentos autogenadosmiddleware : se utiliza para configurar el enrutador y el middleware específico de la rutainterval : se usa para respaldar acciones repetidas ( llamando a un controlador de solicitud en un intervalo establecido en segundos )cron : se usa para especificar una expresión de Cron que determina cuándo llamar al controlador de solicitudes. 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 ()Oxygen viene con un sistema de programación CRON incorporado que le permite llamar a puntos finales y funciones automáticamente cuando la expresión de Cron coincide con la hora actual.
Cuando se programa un trabajo, se crea y se ejecuta una nueva tarea en segundo plano. Cada tarea usa su expresión cron dada y el tiempo actual para determinar cuánto tiempo necesita dormir antes de poder ejecutarse.
El analizador cron en oxígeno se basa en las mismas especificaciones que la utilizada en la primavera. Puede encontrar más información sobre esto en la página Spring Cron Expressions.
El siguiente es un desglose de lo que representa cada parámetro en nuestra expresión cron. Si bien nuestra especificación se parece mucho a la definida por la primavera, no es un partido exacto de 1 a 1.
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)
│ │ │ │ │ │
* * * * * *
También se admiten expresiones parciales, lo que significa que las expresiones posteriores pueden dejarse fuera (están incumplidas en '*' ).
# 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 La función router() tiene un argumento de palabra clave llamado cron , que acepta una expresión de cron que determina cuándo se llama a un punto final. Al igual que los otros argumentos de palabras clave, los puntos finales pueden reutilizar que compartan enrutadores o sean anulados por puntos finales heredados.
# 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 Además de programar puntos finales, también puede usar la nueva macro @cron para programar funciones. Esto es útil si desea ejecutar código en momentos específicos sin hacerlo visible o llamable en la API.
@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 Cuando ejecuta serve() o serveparallel() , todos los trabajos cron registrados se inician automáticamente. Si el servidor se detiene o mata, todos los trabajos en ejecución también serán cancelados. Puede detener el servidor y todas las tareas de repetición y trabajos cron llamando a la función terminate() o matando manualmente el servidor con ctrl+C .
Además, Oxygen proporciona funciones de utilidad para comenzar y detener manualmente los trabajos cron: startcronjobs() y stopcronjobs() . Estas funciones también se pueden usar fuera de un servidor web.
Las tareas repetidas proporcionan una API simple para ejecutar una función en un intervalo establecido.
Hay dos formas de registrar tareas repetidas:
interval en un router()@repeat Es importante tener en cuenta que los controladores de solicitud que usan esta propiedad no pueden definir parámetros de función adicionales fuera del parámetro HTTP.Request predeterminado.
En el siguiente ejemplo, el punto final /repeat/hello se llama cada 0.5 segundos y "hello" se imprime en la consola cada vez.
La función router() tiene un parámetro interval que se utiliza para llamar a un controlador de solicitud en un intervalo establecido (en segundos).
using Oxygen
taskrouter = router ( " /repeat " , interval = 0.5 , tags = [ " repeat " ])
@get taskrouter ( " /hello " ) function ()
println ( " hello " )
end
# you can override properties by setting route specific values
@get taskrouter ( " /bonjour " , interval = 1.5 ) function ()
println ( " bonjour " )
end
serve ()A continuación se muestra un ejemplo de cómo registrar una tarea repetida fuera del enrutador
@repeat 1.5 function ()
println ( " runs every 1.5 seconds " )
end
# you can also "name" a repeat task
@repeat 5 " every-five " function ()
println ( " runs every 5 seconds " )
end Cuando se ejecuta el servidor, todas las tareas se inician automáticamente. Pero el módulo también proporciona utilidades para tener más control de grano fino sobre las tareas de ejecución utilizando las siguientes funciones: starttasks() , stoptasks() y cleartasks()
El oxígeno puede integrarse con la revisión para proporcionar recargas en caliente, acelerando el desarrollo. Dado que Revise recomienda que se revise todo el código en un paquete, primero debe pasar a este tipo de diseño.
Primero asegúrese de que su Project.toml tenga los campos requeridos, como name para trabajar en un paquete en lugar de un proyecto.
A continuación, escriba el código principal para sus rutas en un módulo src/MyModule.jl :
module MyModule
using Oxygen; @oxidise
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end
Entonces puede hacer un script de punto de entrada debug.jl :
using Revise
using Oxygen
using MyModule
MyModule.serve(revise=:eager)
La opción revise también se puede configurar en :lazy , en cuyo caso las revisiones siempre se dejarán justo antes de que se atiende una solicitud, en lugar de intentarse ansiosamente cuando los archivos de origen cambien en el disco.
Tenga en cuenta que debe ejecutar otro script de entrada sin revisar en la producción.
En algunos escenarios avanzados, es posible que deba girar múltiples severs web dentro del mismo módulo en diferentes puertos. Oxygen proporciona una forma estática y dinámica de crear múltiples instancias de un servidor web.
Como regla general, si sabe cuántas instancias necesita con anticipación, es mejor ir con el enfoque estático.
@oxidiseOxygen proporciona una nueva macro que permite configurar y ejecutar múltiples instancias. Genera métodos y los une a un nuevo estado interno para el módulo actual.
En el ejemplo a continuación, dos servidores simples se definen dentro de los módulos A y B y se inician en el módulo principal. Ambos módulos contienen todas las funciones exportadas desde oxígeno que pueden llamarse directamente como se muestra a continuación.
module A
using Oxygen; @oxidise
get ( " / " ) do
text ( " server A " )
end
end
module B
using Oxygen; @oxidise
get ( " / " ) do
text ( " server B " )
end
end
try
# start both instances
A . serve (port = 8001 , async = true )
B . serve (port = 8002 , async = false )
finally
# shut down if we `Ctrl+C`
A . terminate ()
B . terminate ()
endinstance() La función instance le ayuda a crear una instancia completamente independiente de un servidor web de oxígeno en tiempo de ejecución. Funciona creando dinámicamente un módulo Julia en tiempo de ejecución y cargando el código de oxígeno dentro de él.
Todos los mismos métodos de oxígeno están disponibles bajo la instancia nombrada. En el siguiente ejemplo, podemos usar el get y serve simplemente usando la sintaxis DOT en la variable app1 para acceder a los métodos subyacentes.
using Oxygen
# ######## Setup the first app #########
app1 = instance ()
app1 . get ( " / " ) do
text ( " server A " )
end
# ######## Setup the second app #########
app2 = instance ()
app2 . get ( " / " ) do
text ( " server B " )
end
# ######## Start both instances #########
try
# start both servers together
app1 . serve (port = 8001 , async = true )
app2 . serve (port = 8002 )
finally
# clean it up
app1 . terminate ()
app2 . terminate ()
end Para escenarios en los que necesita manejar mayores cantidades de tráfico, puede ejecutar oxígeno en modo multiproceso. Para utilizar este modo, Julia debe tener más de 1 hilo para trabajar. Puede iniciar una sesión de Julia con 4 hilos usando el comando a continuación
julia --threads 4 serveparallel() inicia el servidor web en modo de transmisión y maneja las solicitudes en un enfoque de multitarea cooperativa. Esta función usa Threads.@spawn para programar una nueva tarea en cualquier hilo disponible. Mientras tanto, @async se usa dentro de esta tarea al llamar a cada controlador de solicitud. Esto permite que la tarea cediera durante las operaciones de E/S.
using Oxygen
using StructTypes
using Base . Threads
# Make the Atomic struct serializable
StructTypes . StructType ( :: Type{Atomic{Int64}} ) = StructTypes . Struct ()
x = Atomic {Int64} ( 0 );
@get " /show " function ()
return x
end
@get " /increment " function ()
atomic_add! (x, 1 )
return x
end
# start the web server in parallel mode
serveparallel () El oxígeno incluye una extensión para el paquete ProtoBuf.jl. Esta extensión proporciona una función protobuf() , que simplifica el proceso de trabajo con búferes de protocolos en el contexto del servidor web. Para una mejor comprensión de este paquete, consulte su documentación oficial.
Esta función tiene sobrecargas para los siguientes escenarios:
using HTTP
using ProtoBuf
using Oxygen
# The generated classes need to be created ahead of time (check the protobufs)
include ( " people_pb.jl " );
using . people_pb : People, Person
# Decode a Protocol Buffer Message
@post " /count " function (req :: HTTP.Request )
# decode the request body into a People object
message = protobuf (req, People)
# count the number of Person objects
return length (message . people)
end
# Encode & Return Protocol Buffer message
@get " /get " function ()
message = People ([
Person ( " John Doe " , 20 ),
Person ( " Alice " , 30 ),
Person ( " Bob " , 35 )
])
# seralize the object inside the body of a HTTP.Response
return protobuf (message)
endEl siguiente es un ejemplo de un esquema que se utilizó para crear las fijaciones de Julia necesarias. Estos enlaces permiten la codificación y decodificación de mensajes en el ejemplo anterior.
syntax = "proto3" ;
message Person {
string name = 1 ;
sint32 age = 2 ;
}
message People {
repeated Person people = 1 ;
} El oxígeno está equipado con varias extensiones de paquetes que mejoran sus capacidades de trazado. Estas extensiones facilitan la devolución de las parcelas directamente de los manejadores de solicitud. Todas las operaciones se realizan en memoria utilizando un iobuffer y devuelven una HTTP.Response
Paquetes compatibles y sus Utilper:
png , svg , pdf , htmlhtmlhtml using CairoMakie : heatmap
using Oxygen
@get " /cairo " function ()
fig, ax, pl = heatmap ( rand ( 50 , 50 ))
png (fig)
end
serve () using Bonito
using WGLMakie : heatmap
using Oxygen
using Oxygen : html # Bonito also exports html
@get " /wgl " function ()
fig = heatmap ( rand ( 50 , 50 ))
html (fig)
end
serve () using Bonito
using WGLMakie : heatmap
using Oxygen
using Oxygen : html # Bonito also exports html
@get " /bonito " function ()
app = App () do
return DOM . div (
DOM . h1 ( " Random 50x50 Heatmap " ),
DOM . div ( heatmap ( rand ( 50 , 50 )))
)
end
return html (app)
end
serve () En lugar de construir un motor interno para plantillas o agregar dependencias adicionales, Oxygen proporciona dos extensiones de paquetes para admitir Mustache.jl y OteraEngine.jl plantillas.
Oxygen proporciona una API de envoltura simple alrededor de ambos paquetes que facilita que las plantillas de las cuerdas, las plantillas y los archivos sea fácil. Esta API de envoltura devuelve una función render que acepta un diccionario de entradas para completar la plantilla.
En todos los escenarios, la plantilla renderizada se devuelve dentro de un objeto HTTP.Sponse listo para ser atendido por la API. Por defecto, los tipos de MIME se detectan automáticamente, ya sea observando el contenido de la plantilla o el nombre de la extensión en el archivo. Si conoce el tipo MIME, puede pasarlo directamente a través del argumento de palabras clave mime_type para omitir el proceso de detección.
Eche un vistazo a la documentación de bigote.jl para aprender las capacidades completas del paquete
Ejemplo 1: Renderizar una plantilla de bigote desde un archivo
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
endEjemplo 2: Especificar el tipo de MIME para una plantilla de bigote de cadena simple
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
endEche un vistazo a la documentación OTERAEngine.jl para aprender las capacidades completas del paquete
Ejemplo 1: Renderizar una plantilla de Otera con lógica y bucles
using OteraEngine
using Oxygen
# Define an Otera template
template_str = """
<html>
<head><title>{{ title }}</title></head>
<body>
{% for name in names %}
Hello {{ name }}<br>
{% end %}
</body>
</html>
"""
# 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
endEn este ejemplo, una plantilla de OTERA se define con un bucle for-bucle que itera sobre una lista de nombres, saludando cada nombre.
Ejemplo 2: Ejecutar el código Julia en la plantilla OTERA
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
En este ejemplo, una plantilla OTERA se define con el código Julia incrustado que calcula el cuadrado de un número dado.
Puede montar archivos estáticos utilizando esta práctica función que busca recursivamente una carpeta para obtener archivos y monta todo. Todos los archivos se cargan en la memoria al inicio.
using Oxygen
# mount all files inside the "content" folder under the "/static" path
staticfiles ( " content " , " static " )
# start the web server
serve ()Similar a StaticFiles, esta función monta cada ruta y vuelve a leer el archivo para cada solicitud. Esto significa que se mostrarán cualquier cambio en los archivos después de que el servidor haya comenzado.
using Oxygen
# mount all files inside the "content" folder under the "/dynamic" path
dynamicfiles ( " content " , " dynamic " )
# start the web server
serve () Deshabilitar el registrador interno puede proporcionar algunas ganancias de rendimiento masivas, lo que puede ser útil en algunos escenarios. Anecdóticamente, he visto una aceleración de 2-3x en serve() y una aceleración de 4-5x en el rendimiento serveparallel() .
# This is how you disable internal logging in both modes
serve (access_log = nothing )
serveparallel (access_log = nothing ) Oxygen proporciona un formato de registro predeterminado, pero le permite personalizar el formato utilizando el parámetro access_log . Esta funcionalidad está disponible en las funciones serve() y serveparallel() .
Puede leer más sobre las opciones de registro aquí
# 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 )Las funciones de middleware facilitan crear flujos de trabajo personalizados para interceptar todas las solicitudes entrantes y respuestas salientes. Se ejecutan en el mismo orden en que se pasan (de izquierda a derecha).
Se pueden configurar en la aplicación, el enrutador y la capa de ruta con el argumento de palabras clave middleware . Todo el middleware es aditivo y cualquier middleware definido en estas capas se combinará y ejecutará.
El middleware siempre se ejecutará en el siguiente orden:
application -> router -> route
Ahora veamos algún middleware en acción:
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 ( " middleware1 " )
handle (req)
end
end
function middleware2 (handle)
function (req)
println ( " middleware2 " )
handle (req)
end
end
# set middleware at the router level
math = router ( " math " , middleware = [middleware1])
# set middleware at the route level
@get math ( " /divide/{a}/{b} " , middleware = [middleware2]) function (req, a :: Float64 , b :: Float64 )
return a / b
end
# set application level middleware
serve (middleware = [CorsMiddleware, AuthMiddleware])Si no desea usar el serializador de respuesta predeterminado de Oxygen, ¡puede apagarlo y agregar el suyo! Simplemente cree su propia función especial de middleware para serializar la respuesta y agréguela al final de su propia cadena de middleware.
Tanto serve() como serveparallel() tienen un argumento de palabras clave serialize que puede alternar el serializador predeterminado.
using Oxygen
using HTTP
using JSON3
@get " /divide/{a}/{b} " function (req :: HTTP.Request , a :: Float64 , b :: Float64 )
return a / b
end
# This is just a regular middleware function
function myserializer (handle)
function (req)
try
response = handle (req)
# convert all responses to JSON
return HTTP . Response ( 200 , [], body = JSON3 . write (response))
catch error
@error " ERROR: " exception = (error, catch_backtrace ())
return HTTP . Response ( 500 , " The Server encountered a problem " )
end
end
end
# make sure 'myserializer' is the last middleware function in this list
serve (middleware = [myserializer], serialize = false )La documentación de Swagger se genera automáticamente para cada ruta que se registre en su aplicación. Solo el nombre de la ruta, los tipos de parámetros y las respuestas 200 y 500 se crean automáticamente para usted de forma predeterminada.
Puede ver su documentación generada en /docs , y el esquema se puede encontrar en /docs/schema . Ambos valores se pueden cambiar a lo que desee utilizando la función configdocs() . También puede optar por no recibir documentos autogenerados por completo llamando a la función disabledocs() antes de comenzar su aplicación.
Para agregar detalles adicionales, puede usar las funciones incorporadas mergeschema() o setschema() para modificar directamente el esquema usted mismo o fusionar el esquema generado desde el paquete SwaggerMarkdown.jl (recomendaría este último)
A continuación se muestra un ejemplo de cómo fusionar el esquema generado desde el paquete SwaggerMarkdown.jl .
using Oxygen
using SwaggerMarkdown
# Here's an example of how you can merge autogenerated docs from SwaggerMarkdown.jl into your api
@swagger """
/divide/{a}/{b}:
get:
description: Return the result of a / b
parameters:
- name: a
in: path
required: true
description: this is the value of the numerator
schema:
type : number
responses:
'200':
description: Successfully returned an number.
"""
@get " /divide/{a}/{b} " function (req, a :: Float64 , b :: Float64 )
return a / b
end
# title and version are required
info = Dict ( " title " => " My Demo Api " , " version " => " 1.0.0 " )
openApi = OpenAPI ( " 3.0 " , info)
swagger_document = build (openApi)
# merge the SwaggerMarkdown schema with the internal schema
mergeschema (swagger_document)
# start the web server
serve ()A continuación se muestra un ejemplo de cómo modificar manualmente el esquema
using Oxygen
using SwaggerMarkdown
# Only the basic information is parsed from this route when generating docs
@get " /multiply/{a}/{b} " function (req, a :: Float64 , b :: Float64 )
return a * b
end
# Here's an example of how to update a part of the schema yourself
mergeschema ( " /multiply/{a}/{b} " ,
Dict (
" get " => Dict (
" description " => " return the result of a * b "
)
)
)
# 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.)
mergeschema (
Dict (
" paths " => Dict (
" /multiply/{a}/{b} " => Dict (
" get " => Dict (
" description " => " return the result of a * b "
)
)
)
)
) @get (path, func)| Parámetro | Tipo | Descripción |
|---|---|---|
path | string o router() | Requerido . La ruta para registrarse |
func | function | Requerido . El controlador de solicitud para esta ruta |
Se utiliza para registrar una función en un punto final específico para manejar ese tipo de solicitud correspondiente
@route (methods, path, func)| Parámetro | Tipo | Descripción |
|---|---|---|
methods | array | Requerido . Los tipos de solicitudes HTTP para registrarse en esta ruta |
path | string o router() | Requerido . La ruta para registrarse |
func | function | Requerido . El controlador de solicitud para esta ruta |
Macro de bajo nivel que permite que una ruta se maneje múltiples tipos de solicitudes
staticfiles (folder, mount)| Parámetro | Tipo | Descripción |
|---|---|---|
folder | string | Requerido . La carpeta para servir archivos desde |
mountdir | string | El punto final raíz para montar archivos en (predeterminado es "estático") |
set_headers | function | Personalice los encabezados de respuesta HTTP al devolver estos archivos |
loadfile | function | Personalizar el comportamiento al cargar archivos |
Sirva todos los archivos estáticos dentro de una carpeta. Esta función busca recursivamente en un directorio y monta todos los archivos en el directorio de montaje utilizando sus rutas relativas.
dynamicfiles (folder, mount)| Parámetro | Tipo | Descripción |
|---|---|---|
folder | string | Requerido . La carpeta para servir archivos desde |
mountdir | string | El punto final raíz para montar archivos en (predeterminado es "estático") |
set_headers | function | Personalice los encabezados de respuesta HTTP al devolver estos archivos |
loadfile | function | Personalizar el comportamiento al cargar archivos |
Sirva todos los archivos estáticos dentro de una carpeta. Esta función busca recursivamente en un directorio y monta todos los archivos en el directorio de montaje utilizando sus rutas relativas. El archivo se carga en cada solicitud, potencialmente recogiendo cualquier cambio de archivo.
html (content, status, headers)| Parámetro | Tipo | Descripción |
|---|---|---|
content | string | Requerido . La cadena se devolverá como html |
status | integer | El código de respuesta HTTP (el valor predeterminado es 200) |
headers | dict | Los encabezados para la respuesta HTTP (el valor predeterminado tiene encabezado de tipo de contenido establecido en "Text/HTML; Charset = UTF-8") |
La función auxiliar para designar cuando el contenido debe devolverse como HTML
queryparams (request)| Parámetro | Tipo | Descripción |
|---|---|---|
req | HTTP.Request | Requerido . El objeto de solicitud http |
Devuelve los parámetros de consulta de una solicitud como un dict ()
text (request)| Parámetro | Tipo | Descripción |
|---|---|---|
req | HTTP.Request | Requerido . El objeto de solicitud http |
Devuelve el cuerpo de una solicitud como cadena
binary (request)| Parámetro | Tipo | Descripción |
|---|---|---|
req | HTTP.Request | Requerido . El objeto de solicitud http |
Devuelve el cuerpo de una solicitud como un archivo binario (devuelve un vector de UInt8 s)
json (request, classtype)| Parámetro | Tipo | Descripción |
|---|---|---|
req | HTTP.Request | Requerido . El objeto de solicitud http |
classtype | struct | Una estructura para deserializar un objeto json en |
Deserializar el cuerpo de una solicitud en una estructura de Julia