L'oxygène est un micro-travail construit au-dessus de la bibliothèque http.jl. Respirez facile de savoir que vous pouvez rapidement tourner un serveur Web avec des abstractions que vous connaissez déjà.
Besoin d'aide? N'hésitez pas à tendre la main sur nos réseaux sociaux.
pkg > add OxygenCréer un serveur Web avec très peu de lignes de code
using Oxygen
using HTTP
@get " /greet " function (req :: HTTP.Request )
return " hello world! "
end
# start the web server
serve ()Les gestionnaires sont utilisés pour connecter votre code au serveur de manière propre et simple. Ils affectent une URL à une fonction et invoquent la fonction lorsqu'une demande entrante correspond à cette URL.
do..end Block SyntaxeRequest par défaut lorsqu'aucune information de type n'est fournieIl existe 3 types de gestionnaires pris en charge:
Request des gestionnairesStreamWebsocket 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 Ce ne sont que des fonctions, ce qui signifie qu'il existe de nombreuses façons dont ils peuvent être exprimés et définis. Vous trouverez ci-dessous un exemple de plusieurs manières différentes que vous pouvez exprimer et affecter un gestionnaire 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 Les gestionnaires de demandes sont utilisés pour gérer les demandes HTTP. Ils sont définis à l'aide de macros ou de leurs équivalents de fonction et acceptent un objet HTTP.Request comme premier argument. Ces gestionnaires prennent en charge à la fois la fonction et la syntaxe DO-bloc.
@get , @post , @put , @patch , @delete , @routeget() , post() , put() , patch() , delete() , route() Les gestionnaires de flux sont utilisés pour diffuser des données. Ils sont définis à l'aide de la macro @stream ou de la fonction stream() et acceptent un objet HTTP.Stream comme premier argument. Ces gestionnaires prennent en charge à la fois la fonction et la syntaxe DO-bloc.
@stream et stream() ne nécessitent pas de définition de type sur le premier argument, ils supposent que c'est un flux.Stream peuvent être attribués avec des macros de routage standard et des fonctions: @get , @post , etc.Stream Les gestionnaires WebSocket sont utilisés pour gérer les connexions WebSocket. Ils sont définis à l'aide de la macro @websocket ou de la fonction websocket() et acceptent un objet HTTP.WebSocket comme premier argument. Ces gestionnaires prennent en charge à la fois la fonction et la syntaxe DO-bloc.
@websocket et websocket() ne nécessitent pas de définition de type sur le premier argument, ils supposent que c'est un WebSocket.Websocket peuvent également être attribués avec la fonction @get macro ou get() , car le protocole WebSocket nécessite une demande GET pour lancer la poignée de main.Websocket Il existe deux façons principales d'enregistrer vos gestionnaires de demande: les macros de routage standard ou les fonctions de routage qui utilisent la syntaxe DO-Block.
Pour chaque macro de routage, nous avons maintenant une fonction de routage équivalente
@get -> get ()
@post -> post ()
@put -> put ()
@patch -> patch ()
@delete -> delete ()
@route -> route ()La seule différence pratique entre les deux est que les macros de routage sont appelés pendant le stade de précompilation, tandis que les fonctions de routage ne sont appelées que lorsqu'elles sont invoquées. (Les macros de routage appellent les fonctions de routage sous le capot)
# 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 L'oxygène, par défaut, identifie automatiquement le type de contenu de la valeur de retour d'un gestionnaire de demande lors de la création d'une réponse. Cette fonctionnalité par défaut est très utile, mais elle a un impact sur les performances. Dans les situations où le type de retour est connu, il est recommandé d'utiliser l'une des fonctions de rendu préexistantes pour accélérer les choses.
Voici une liste des fonctions de rendu actuellement prises en charge: html , text , json , file , xml , js , css , binary
Vous trouverez ci-dessous un exemple de la façon d'utiliser ces fonctions:
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 () Dans la plupart des cas, ces fonctions acceptent les chaînes simples comme entrées. Les seules exceptions sont la fonction binary , qui accepte un Vector{UInt8} , et la fonction json qui accepte tout type sérialisable.
Les paramètres de chemin sont déclarés avec des accolades et sont transmis directement à votre gestionnaire de demande.
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 ()Les paramètres de requête peuvent être déclarés directement à l'intérieur de la signature de vos gestionnaires. Tout paramètre qui n'est pas mentionné dans le chemin de route est supposé être un paramètre de requête.
@get " /query " function (req :: HTTP.Request , a :: Int , message :: String = " hello world " )
return (a, message)
end Alternativement, vous pouvez utiliser la fonction queryparams() pour extraire les valeurs brutes de l'URL en tant que dictionnaire.
@get " /query " function (req :: HTTP.Request )
return queryparams (req)
end Utilisez la fonction formdata() pour extraire et analyser les données de forme du corps d'une demande. Cette fonction renvoie un dictionnaire de paires de valeurs clés de la forme
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 ()Tous les objets sont automatiquement désérialisés en JSON à l'aide de la bibliothèque JSON3
using Oxygen
using HTTP
@get " /data " function (req :: HTTP.Request )
return Dict ( " message " => " hello! " , " value " => 99.3 )
end
# start the web server
serve ()L'oxygène fournit une sérialisation et une désérialisation prêtes à l'emploi pour la plupart des objets mais nécessite l'utilisation de structure
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 ()L'oxygène est livré avec plusieurs extracteurs intégrés conçus pour réduire la quantité de chauffeur requis pour sérialiser les entrées à vos fonctions de gestionnaire. En définissant simplement une structure et en spécifiant la source de données, ces extracteurs rationalisent le processus d'ingestion et de validation des données via une API uniforme.
payload@kwdefExtracteurs pris en charge:
Path - Extraits des paramètres de cheminQuery - extraits des paramètres de requête,Header - Extraits des en-têtes de demandeForm - extrait les données du formulaire du corps de la demandeBody - sérialise l'ensemble du corps de la demande à un type donné (chaîne, float64, etc.)ProtoBuffer - extrait le message ProtoBuf du corps de la demande (disponible via une extension de package)Json - extrait JSON du corps de la demandeJsonFragment - extrait un "fragment" du corps JSON en utilisant le nom du paramètre pour identifier et extraire la clé de niveau supérieur correspondant Dans cet exemple, nous montrons que l'extracteur Path peut être utilisé aux côtés des paramètres de chemin réguliers. Cela fonctionne également avec les paramètres de requête réguliers et l'extracteur 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 Les valeurs par défaut peuvent être configurées avec des structures à l'aide de la 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 En plus de sérialiser les données entrantes, vous pouvez également définir vos propres règles de validation en utilisant la fonction validate . Dans l'exemple ci-dessous, nous montrons comment utiliser les validateurs global et local dans votre code.
global avant d'exécuter un validateur 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 Vous pouvez interpoler les variables directement dans les chemins, ce qui fait de l'enregistrement dynamique des routes un jeu d'enfant
(Merci à @anandijain pour l'idée)
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 fonction router() est une HOF (fonction d'ordre supérieur) qui vous permet de réutiliser le même préfixe et propriétés de chemin sur plusieurs points d'extrémité. Ceci est utile lorsque votre API commence à croître et que vous souhaitez garder vos opérations de chemin organisées.
Vous trouverez ci-dessous les arguments que la fonction router() peut prendre:
router (prefix :: String ; tags :: Vector , middleware :: Vector , interval :: Real , cron :: String )tags - sont utilisés pour organiser les points d'extrémité dans les documents autoogénésmiddleware - est utilisé pour configurer le routeur et le middleware spécifique à l'itinéraireinterval - est utilisé pour prendre en charge les actions de répétition ( appelant un gestionnaire de demandes sur un intervalle défini en secondes )cron - est utilisé pour spécifier une expression CRON qui détermine quand appeler le gestionnaire de demande. 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 ()L'oxygène est livré avec un système de planification CRON intégré qui vous permet d'appeler automatiquement les points de terminaison et les fonctions lorsque l'expression de Cron correspond à l'heure actuelle.
Lorsqu'un travail est planifié, une nouvelle tâche est créée et s'exécute en arrière-plan. Chaque tâche utilise son expression CRON donnée et l'heure actuelle pour déterminer combien de temps il doit dormir avant de pouvoir s'exécuter.
L'analyseur cron dans l'oxygène est basé sur les mêmes spécifications que celle utilisée au printemps. Vous pouvez trouver plus d'informations à ce sujet sur la page Spring Cron Expressions.
Ce qui suit est une ventilation de ce que représente chaque paramètre de notre expression CRON. Bien que notre spécification ressemble étroitement à celle définie par le printemps, ce n'est pas une correspondance exacte de 1 à 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)
│ │ │ │ │ │
* * * * * *
Les expressions partielles sont également prises en charge, ce qui signifie que les expressions ultérieures peuvent être laissées de côté (elles sont par défaut '*' ).
# 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 fonction router() a un argument de mot-clé appelé cron , qui accepte une expression Cron qui détermine quand un point final est appelé. Tout comme les autres arguments de mots clés, il peut être réutilisé par des points de terminaison qui partagent des routeurs ou être remplacés par des points de terminaison hérités.
# 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 En plus de planifier des points de terminaison, vous pouvez également utiliser la nouvelle macro @cron pour planifier des fonctions. Ceci est utile si vous souhaitez exécuter du code à des moments spécifiques sans le rendre visible ou appelable dans l'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 Lorsque vous exécutez serve() ou serveparallel() , tous les travaux CRON enregistrés sont automatiquement démarrés. Si le serveur est arrêté ou tué, tous les travaux d'exécution seront également résiliés. Vous pouvez arrêter le serveur et toutes les tâches de répétition et les travaux CRON en appelant la fonction terminate() ou en tuant manuellement le serveur avec ctrl+C
De plus, l'oxygène fournit des fonctions utilitaires pour démarrer et arrêter manuellement les travaux CRON: startcronjobs() et stopcronjobs() . Ces fonctions peuvent également être utilisées en dehors d'un serveur Web.
Les tâches de répétition fournissent une API simple pour exécuter une fonction sur un intervalle défini.
Il existe deux façons d'enregistrer des tâches répétées:
interval dans un router()@repeat Il est important de noter que les gestionnaires de demande qui utilisent cette propriété ne peuvent pas définir des paramètres de fonction supplémentaires en dehors du paramètre HTTP.Request par défaut.
Dans l'exemple ci-dessous, le point de terminaison /repeat/hello est appelé toutes les 0,5 secondes et "hello" est imprimé à la console à chaque fois.
La fonction router() a un paramètre interval qui est utilisé pour appeler un gestionnaire de demande sur un intervalle défini (en secondes).
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 ()Vous trouverez ci-dessous un exemple de la façon d'enregistrer une tâche répétée en dehors du routeur
@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 Lorsque le serveur est exécuté, toutes les tâches sont démarrées automatiquement. Mais le module fournit également aux utilitaires d'avoir un contrôle plus fin sur les tâches en cours d'exécution en utilisant les fonctions suivantes: starttasks() , stoptasks() et cleartasks()
L'oxygène peut s'intégrer à la révision pour fournir des recharges chaudes, accélérant le développement. Étant donné que Revise recommande de garder tout le code à réviser dans un package, vous devez d'abord passer à ce type de mise en page.
Assurez-vous d'abord que votre Project.toml dispose des champs requis tels que name pour travailler sur un package plutôt qu'un projet.
Ensuite, écrivez le code principal pour vous routes dans un module src/MyModule.jl :
module MyModule
using Oxygen; @oxidise
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end
Ensuite, vous pouvez faire un script de point d'entrée debug.jl :
using Revise
using Oxygen
using MyModule
MyModule.serve(revise=:eager)
L'option revise peut également être définie sur :lazy , auquel cas les révisions seront toujours laissées juste avant qu'une demande ne soit signifiée, plutôt que d'être tentée avec impatience lorsque les fichiers source changent sur le disque.
Notez que vous devez exécuter un autre script d'entrée sans réviser en production.
Dans certains scénarios avancés, vous devrez peut-être faire tourner plusieurs Severs Web dans le même module sur différents ports. Oxygen fournit à la fois un moyen statique et dynamique de créer plusieurs instances d'un serveur Web.
En règle générale, si vous savez combien d'instances vous avez besoin à l'avance, il est préférable de suivre l'approche statique.
@oxidiseL'oxygène fournit une nouvelle macro qui permet de configurer et d'exécuter plusieurs instances. Il génère des méthodes et les lie à un nouvel état interne pour le module actuel.
Dans l'exemple ci-dessous, deux serveurs simples sont définis dans les modules A et B et sont démarrés dans le module parent. Les deux modules contiennent toutes les fonctions exportées de l'oxygène qui peuvent être appelées directement comme indiqué ci-dessous.
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 fonction instance vous aide à créer une instance complètement indépendante d'un serveur Web d'oxygène à l'exécution. Il fonctionne en créant dynamiquement un module Julia à l'exécution et en chargeant le code d'oxygène à l'intérieur.
Toutes les mêmes méthodes d'oxygène sont disponibles sous l'instance nommée. Dans l'exemple ci-dessous, nous pouvons utiliser le get et serve en utilisant simplement la syntaxe DOT sur la variable app1 pour accéder aux méthodes sous-jacentes.
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 Pour les scénarios où vous devez gérer des quantités plus élevées de trafic, vous pouvez exécuter de l'oxygène en mode multithread. Afin d'utiliser ce mode, Julia doit avoir plus d'un fil avec lequel travailler. Vous pouvez démarrer une session Julia avec 4 threads en utilisant la commande ci-dessous
julia --threads 4 serveparallel() démarre le serveur Web en mode streaming et gère les demandes dans une approche multitâche coopérative. Cette fonction utilise Threads.@spawn pour planifier une nouvelle tâche sur n'importe quel thread disponible. Pendant ce temps, @async est utilisé à l'intérieur de cette tâche lors de l'appel de chaque gestionnaire de demandes. Cela permet à la tâche de céder pendant les opérations d'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 () L'oxygène comprend une extension pour le package Protobuf.jl. Cette extension fournit une fonction protobuf() , simplifiant le processus de travail avec des tampons de protocole dans le contexte du serveur Web. Pour une meilleure compréhension de ce package, veuillez vous référer à sa documentation officielle.
Cette fonction a des surcharges pour les scénarios suivants:
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)
endCe qui suit est un exemple de schéma qui a été utilisé pour créer les liaisons Julia nécessaires. Ces liaisons permettent le codage et le décodage des messages dans l'exemple ci-dessus.
syntax = "proto3" ;
message Person {
string name = 1 ;
sint32 age = 2 ;
}
message People {
repeated Person people = 1 ;
} L'oxygène est équipé de plusieurs extensions de paquets qui améliorent ses capacités de traçage. Ces extensions facilitent le retour des tracés directement des gestionnaires de demande. Toutes les opérations sont effectuées en mémoire à l'aide d'un iobuffer et renvoient une réponse HTTP.Response
Packages pris en charge et leurs utiles d'aide:
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 () Plutôt que de construire un moteur interne pour les modèles ou l'ajout de dépendances supplémentaires, l'oxygène fournit deux extensions de package pour prendre en charge les modèles Mustache.jl et OteraEngine.jl .
L'oxygène fournit une simple API en wrapper autour des deux packages qui facilite le rendu des modèles à partir de chaînes, de modèles et de fichiers. Cette API en wrapper renvoie une fonction render qui accepte un dictionnaire des entrées pour remplir le modèle.
Dans tous les scénarios, le modèle rendu est retourné dans un objet HTTP.Response prêt à être servi par l'API. Par défaut, les types MIME sont automatiquement détectés en regardant le contenu du modèle ou le nom d'extension du fichier. Si vous connaissez le type MIME, vous pouvez le passer directement via l'argument de mot-clé mime_type pour sauter le processus de détection.
Veuillez jeter un œil à la documentation Mustache.jl pour apprendre les capacités complètes du package
Exemple 1: Rendre un modèle de moustache à partir d'un fichier
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
endExemple 2: Spécification du type MIME pour un modèle de moustache à chaîne 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
endVeuillez consulter la documentation OteraEngine.jl pour apprendre les capacités complètes du package
Exemple 1: Rendre un modèle Otera avec logique et boucles
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
endDans cet exemple, un modèle OTERA est défini avec une boucle pour itérate sur une liste de noms, saluant chaque nom.
Exemple 2: Exécution du code Julia dans le modèle 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
Dans cet exemple, un modèle OTERA est défini avec un code Julia intégré qui calcule le carré d'un nombre donné.
Vous pouvez monter des fichiers statiques en utilisant cette fonction pratique qui recherche récursivement un dossier pour les fichiers et monte tout. Tous les fichiers sont chargés en mémoire au démarrage.
using Oxygen
# mount all files inside the "content" folder under the "/static" path
staticfiles ( " content " , " static " )
# start the web server
serve ()Semblable à StaticFiles, cette fonction monte chaque chemin et reble le fichier pour chaque demande. Cela signifie que toutes les modifications apportées aux fichiers après le démarrage du serveur seront affichées.
using Oxygen
# mount all files inside the "content" folder under the "/dynamic" path
dynamicfiles ( " content " , " dynamic " )
# start the web server
serve () La désactivation de l'enregistreur interne peut fournir des gains de performances massifs, ce qui peut être utile dans certains scénarios. Anecdotique, j'ai vu une accélération de 2-3x dans serve() et une accélération de 4-5x dans les performances serveparallel() .
# This is how you disable internal logging in both modes
serve (access_log = nothing )
serveparallel (access_log = nothing ) Oxygen fournit un format de journalisation par défaut mais vous permet de personnaliser le format à l'aide du paramètre access_log . Cette fonctionnalité est disponible dans les fonctions serve() et serveparallel() .
Vous pouvez en savoir plus sur les options de journalisation ici
# 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 )Les fonctions middleware facilitent la création de workflows personnalisés pour intercepter toutes les demandes entrantes et les réponses sortantes. Ils sont exécutés dans le même ordre qu'ils sont passés (de gauche à droite).
Ils peuvent être définis sur l'application, le routeur et la couche d'itinéraire avec l'argument du mot-clé middleware . Tous les middleware sont additifs et tout middleware défini dans ces couches sera combiné et exécuté.
Le middleware sera toujours exécuté dans l'ordre suivant:
application -> router -> route
Voyons maintenant un middleware en action:
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 vous ne souhaitez pas utiliser le sérialiseur de réponse par défaut d'Oxygen, vous pouvez l'éteindre et ajouter le vôtre! Créez simplement votre propre fonction middleware spéciale pour sérialiser la réponse et ajoutez-la à la fin de votre propre chaîne de middleware.
Les deux serve() et serveparallel() ont un argument de mot-clé serialize qui peut activer le sérialiseur par défaut.
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 documentation de Swagger est automatiquement générée pour chaque itinéraire que vous vous inscrivez dans votre application. Seules le nom de route, les types de paramètres et les réponses 200 et 500 sont automatiquement créés pour vous par défaut.
Vous pouvez afficher votre documentation générée sur /docs , et le schéma peut être trouvé sous /docs/schema . Ces deux valeurs peuvent être modifiées en ce que vous voulez en utilisant la fonction configdocs() . Vous pouvez également vous retirer entièrement des documents autogenits en appelant la fonction disabledocs() avant de démarrer votre application.
Pour ajouter des détails supplémentaires, vous pouvez utiliser les fonctions mergeschema() ou setschema() intégrées pour modifier directement le schéma vous-même ou fusionner le schéma généré à partir du package SwaggerMarkdown.jl (je recommanderais ce dernier)
Vous trouverez ci-dessous un exemple de la façon de fusionner le schéma généré à partir du package 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 ()Vous trouverez ci-dessous un exemple de comment modifier manuellement le schéma
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)| Paramètre | Taper | Description |
|---|---|---|
path | string ou router() | Requis . L'itinéraire pour s'inscrire |
func | function | Requis . Le gestionnaire de demandes de cette route |
Utilisé pour enregistrer une fonction à un point de terminaison spécifique pour gérer ce type de demande correspondant
@route (methods, path, func)| Paramètre | Taper | Description |
|---|---|---|
methods | array | Requis . Les types de demandes HTTP pour s'inscrire sur cette route |
path | string ou router() | Requis . L'itinéraire pour s'inscrire |
func | function | Requis . Le gestionnaire de demandes de cette route |
Macro de bas niveau qui permet à un itinéraire de gérer plusieurs types de demandes
staticfiles (folder, mount)| Paramètre | Taper | Description |
|---|---|---|
folder | string | Requis . Le dossier pour servir les fichiers à partir de |
mountdir | string | Le point de terminaison racine pour monter les fichiers sous (par défaut est "statique") |
set_headers | function | Personnalisez les en-têtes de réponse HTTP lors du retour de ces fichiers |
loadfile | function | Personnaliser le comportement lors du chargement des fichiers |
Servir tous les fichiers statiques dans un dossier. Cette fonction recherche récursivement un répertoire et monte tous les fichiers sous le répertoire Mount en utilisant leurs chemins relatifs.
dynamicfiles (folder, mount)| Paramètre | Taper | Description |
|---|---|---|
folder | string | Requis . Le dossier pour servir les fichiers à partir de |
mountdir | string | Le point de terminaison racine pour monter les fichiers sous (par défaut est "statique") |
set_headers | function | Personnalisez les en-têtes de réponse HTTP lors du retour de ces fichiers |
loadfile | function | Personnaliser le comportement lors du chargement des fichiers |
Servir tous les fichiers statiques dans un dossier. Cette fonction recherche récursivement un répertoire et monte tous les fichiers sous le répertoire Mount en utilisant leurs chemins relatifs. Le fichier est chargé à chaque demande, potentiellement en train de prendre des modifications de fichier.
html (content, status, headers)| Paramètre | Taper | Description |
|---|---|---|
content | string | Requis . La chaîne à retourner sous forme de html |
status | integer | Le code de réponse HTTP (par défaut est 200) |
headers | dict | Les en-têtes de la réponse HTTP (par défaut a un en-tête de type contenu défini sur "Text / Html; charSet = UTF-8") |
Fonction d'assistance à désigner lorsque le contenu doit être renvoyé en tant que HTML
queryparams (request)| Paramètre | Taper | Description |
|---|---|---|
req | HTTP.Request | Requis . L'objet de demande HTTP |
Renvoie les paramètres de requête d'une demande en tant que dict ()
text (request)| Paramètre | Taper | Description |
|---|---|---|
req | HTTP.Request | Requis . L'objet de demande HTTP |
Renvoie le corps d'une demande en tant que chaîne
binary (request)| Paramètre | Taper | Description |
|---|---|---|
req | HTTP.Request | Requis . L'objet de demande HTTP |
Renvoie le corps d'une demande en tant que fichier binaire (renvoie un vecteur d' UInt8 s)
json (request, classtype)| Paramètre | Taper | Description |
|---|---|---|
req | HTTP.Request | Requis . L'objet de demande HTTP |
classtype | struct | Une structure pour désérialiser un objet JSON dans |
Désérialiser le corps d'une demande dans une structure Julia