Oxygen ist ein Mikro-Framework, das auf der Http.jl-Bibliothek basiert. Atme einfach zu wissen, dass du einen Webserver mit Abstraktionen, mit denen du bereits vertraut bist, schnell verdrehen kannst.
Benötigen Sie Hilfe? Fühlen Sie sich frei, sich auf unseren Social -Media -Kanälen zu wenden.
pkg > add OxygenErstellen Sie einen Webserver mit sehr wenigen Codezeilen
using Oxygen
using HTTP
@get " /greet " function (req :: HTTP.Request )
return " hello world! "
end
# start the web server
serve ()Handler werden verwendet, um Ihren Code auf saubere und einfache Weise mit dem Server zu verbinden. Sie weisen einer Funktion eine URL zu und rufen die Funktion auf, wenn eine eingehende Anforderung mit dieser URL übereinstimmt.
do..end BlocksyntaxRequest ausEs gibt 3 Arten von unterstützten Handlern:
Request HandlernStream -HandlerWebsocket -Handler 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 Sie sind nur Funktionen, was bedeutet, dass es viele Möglichkeiten gibt, wie sie ausgedrückt und definiert werden können. Im Folgenden finden Sie ein Beispiel für verschiedene Möglichkeiten, wie Sie einen Request ausdrücken und zuweisen können.
@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 Anforderungshandler werden verwendet, um HTTP -Anforderungen zu verarbeiten. Sie werden unter Verwendung von Makros oder ihren Funktionsäquivalenten definiert und akzeptieren ein HTTP.Request -Objekt als erstes Argument. Diese Handler unterstützen sowohl Funktion als auch Do-Block-Syntax.
@get , @post , @put , @patch , @delete , @routeget() , post() , put() , patch() , delete() , route() Stream -Handler werden verwendet, um Daten zu streamen. Sie werden mit der Funktion @stream oder der Funktion stream() definiert und akzeptieren ein HTTP.Stream -Objekt als erstes Argument. Diese Handler unterstützen sowohl Funktion als auch Do-Block-Syntax.
@stream und stream() erfordern keine Typdefinition für das erste Argument, sie gehen davon aus, dass es sich um einen Stream handelt.Stream -Handler können mit Standard -Routing -Makros & Funktionen zugeordnet werden: @get , @post usw.Stream -Handler identifizieren kann WebSocket -Handler werden verwendet, um WebSocket -Verbindungen zu verarbeiten. Sie werden mit dem @websocket -Makro oder der Funktion websocket() definiert und akzeptieren ein HTTP.WebSocket -Objekt als erstes Argument. Diese Handler unterstützen sowohl Funktion als auch Do-Block-Syntax.
@websocket und websocket() erfordern keine Typdefinition im ersten Argument, sie gehen davon aus, dass es sich um ein WebSocket handelt.Websocket -Handler können auch mit der Funktion @get macro oder get() zugewiesen werden, da das WebSocket -Protokoll eine GET -Anforderung erfordert, um den Handshake zu initiieren.Websocket -Handler identifizieren kann Es gibt zwei primäre Möglichkeiten, Ihre Anforderungshandler zu registrieren: die Standard-Routing-Makros oder die Routing-Funktionen, die die Do-Block-Syntax verwenden.
Für jedes Routing -Makro haben wir jetzt eine äquivalente Routing -Funktion
@get -> get ()
@post -> post ()
@put -> put ()
@patch -> patch ()
@delete -> delete ()
@route -> route ()Der einzige praktische Unterschied zwischen den beiden besteht darin, dass die Routing -Makros während der Vorkompilierungsphase aufgerufen werden, während die Routing -Funktionen nur beim Aufrufen aufgerufen werden. (Die Routing -Makros nennen die Routing -Funktionen unter der Haube)
# 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 Sauerstoff identifiziert standardmäßig den Inhalt des Rückgabewerts automatisch von einem Anforderungshandler beim Erstellen einer Antwort. Diese Standardfunktionalität ist sehr nützlich, wirkt sich jedoch auf die Leistung aus. In Situationen, in denen der Rückgabetyp bekannt ist, wird empfohlen, eine der bereits bestehenden Renderfunktionen zu verwenden, um die Dinge zu beschleunigen.
Hier ist eine Liste der derzeit unterstützten Renderfunktionen: html , text , json , file , xml , js , css , binary
Nachfolgend finden Sie ein Beispiel für die Verwendung dieser Funktionen:
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 () In den meisten Fällen akzeptieren diese Funktionen einfache Zeichenfolgen als Eingaben. Die einzigen Ausnahmen sind die binary Funktion, die einen Vector{UInt8} und die json -Funktion akzeptiert, die einen serialisierbaren Typ akzeptiert.
Die Pfadparameter werden mit Zahnspangen deklariert und direkt an Ihren Anfrage -Handler übergeben.
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 ()Abfrageparameter können direkt in die Signatur Ihrer Handler deklariert werden. Jeder Parameter, der nicht im Routenpfad erwähnt wird, wird als Abfrageparameter angenommen.
@get " /query " function (req :: HTTP.Request , a :: Int , message :: String = " hello world " )
return (a, message)
end Alternativ können Sie die Funktion queryparams() verwenden, um die Rohwerte aus der URL als Wörterbuch zu extrahieren.
@get " /query " function (req :: HTTP.Request )
return queryparams (req)
end Verwenden Sie die formdata() -Funktion, um die Formdaten aus dem Körper einer Anforderung zu extrahieren und zu analysieren. Diese Funktion gibt ein Wörterbuch von Schlüsselwertpaaren aus der Form zurück
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 ()Alle Objekte werden automatisch mit der JSON3 -Bibliothek in JSON deserialisiert
using Oxygen
using HTTP
@get " /data " function (req :: HTTP.Request )
return Dict ( " message " => " hello! " , " value " => 99.3 )
end
# start the web server
serve ()Sauerstoff liefert für die meisten Objekte einige Serialisierung und Deserialisierung des Boxs und erfordert jedoch die Verwendung von Strukturstruktur
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 ()Sauerstoff wird mit mehreren eingebauten Extraktoren geliefert, die die Menge an Kesselplatten reduzieren, die zur Serialisierung der Eingänge auf Ihre Handlerfunktionen erforderlich ist. Durch einfaches Definieren einer Struktur und die Angabe der Datenquelle rationalisieren diese Extraktoren den Prozess der Datenverschlusses und -validierung durch eine einheitliche API.
payload -Immobilie zugänglich@kwdef -Makro definiert sindUnterstützte Extraktoren:
Path - extrahiert aus PfadparameternQuery - Extrahiert aus Abfrageparametern,Header - Auszüge aus AnforderungsüberschriftenForm - Extrahiert Formular Daten aus der AnforderungsbehördeBody - serialisiert den gesamten Anforderungskörper zu einem bestimmten Typ (String, Float64 usw.)ProtoBuffer - Extrahiert die ProtoBuf -Nachricht aus der Anforderungsorganisation (verfügbar über eine Paketerweiterung)Json - Extrahiert JSON aus der AnfragekörperJsonFragment - Extrahiert ein "Fragment" des JSON -Körper In diesem Beispiel zeigen wir, dass der Path neben den regulären Pfadparametern verwendet werden kann. Dies funktioniert auch mit regulären Abfrageparametern und dem 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 Standardwerte können mit Strukturen mit dem @kwdef -Makro eingerichtet werden.
@kwdef struct Pet
name :: String
age :: Int = 10
end
@post " /pet " function (req, params :: Json{Pet} )
return params . payload # access the serialized payload
end Zusätzlich zur Serialisierung der eingehenden Daten können Sie Ihre eigenen Validierungsregeln mit der validate definieren. Im folgenden Beispiel zeigen wir, wie Sie sowohl global als auch local Validatoren in Ihrem Code verwenden.
global Validator auf, bevor er einen local Validator ausführt. 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 Sie können Variablen direkt in die Pfade interpolieren, wodurch dynamisch Registrierung von Routen eine Kinderspiel
(Danke an @anandijain für die Idee)
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 () Die Funktion router() ist eine HOF -Funktion (höherer Ordnung), mit der Sie das gleiche Pfadpräfix und die gleichen Eigenschaften über mehrere Endpunkte hinweg wiederverwenden können. Dies ist hilfreich, wenn Ihre API zu wachsen beginnt und Sie Ihre Pfadoperationen organisieren möchten.
Im Folgenden finden Sie die Argumente, die die Funktion router() erfolgen kann:
router (prefix :: String ; tags :: Vector , middleware :: Vector , interval :: Real , cron :: String )tags - werden verwendet, um Endpunkte in den autogenerierten Dokumenten zu organisierenmiddleware - wird verwendet, um Router und Routen -spezifische Middleware einzurichteninterval - wird verwendet, um Wiederholungsaktionen zu unterstützen ( auf ein festgelegtes Intervall in Sekunden eingerichtet zu werden )cron - wird verwendet, um einen Cron -Ausdruck anzugeben, der feststellt, wann der Anforderungsbehandler aufgerufen werden soll. 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 verfügt über ein integriertes Cron-Planungssystem, mit dem Sie Endpunkte und Funktionen automatisch aufrufen können, wenn der Cron-Ausdruck mit der aktuellen Zeit übereinstimmt.
Wenn ein Job geplant ist, wird eine neue Aufgabe erstellt und im Hintergrund ausgeführt. Jede Aufgabe verwendet ihren gegebenen Cron -Ausdruck und die aktuelle Zeit, um festzustellen, wie lange sie schlafen muss, bevor sie ausgeführt werden kann.
Der Cron -Parser im Sauerstoff basiert auf den gleichen Spezifikationen wie im Frühjahr. Weitere Informationen dazu finden Sie auf der Seite Spring Cron Expressions.
Das Folgende ist eine Aufschlüsselung dessen, was jeder Parameter in unserem Cron -Ausdruck darstellt. Während unsere Spezifikation der durch den Frühling definierten Spezifikationen stark ähnelt, ist sie kein genaues 1-zu-1-Übereinstimmung.
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)
│ │ │ │ │ │
* * * * * *
Es werden ebenfalls teilweise Ausdrücke unterstützt, was bedeutet, dass nachfolgende Ausdrücke ausgelassen werden können (sie werden standardmäßig mit '*' ).
# 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 Die Funktion router() hat ein Schlüsselwortargument namens cron , das einen Cron -Ausdruck akzeptiert, der feststellt, wann ein Endpunkt aufgerufen wird. Genau wie bei den anderen Schlüsselwortargumenten kann es von Endpunkten wiederverwendet werden, die Router teilen oder von ererbten Endpunkten überschrieben werden.
# 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 Zusätzlich zur Planung von Endpunkten können Sie das neue @cron -Makro verwenden, um Funktionen zu planen. Dies ist nützlich, wenn Sie Code zu bestimmten Zeiten ausführen möchten, ohne dass er in der API sichtbar oder aufrufbar macht.
@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 Wenn Sie serve() oder serveparallel() ausführen, werden alle registrierten Cron -Jobs automatisch gestartet. Wenn der Server gestoppt oder getötet wird, werden auch alle laufenden Jobs gekündigt. Sie können den Server und alle Wiederholungsaufgaben und Cron -Jobs stoppen, indem Sie die Funktion terminate() aufrufen oder den Server manuell mit ctrl+C töten.
Darüber hinaus bietet Oxygen Nutzfunktionen, um Cron -Jobs manuell zu starten und zu stoppen: startcronjobs() und stopcronjobs() . Diese Funktionen können auch außerhalb eines Webservers verwendet werden.
Wiederholungsaufgaben liefern eine einfache API, um eine Funktion auf einem festgelegten Intervall auszuführen.
Es gibt zwei Möglichkeiten, Wiederholungsaufgaben zu registrieren:
interval in einem router()@repeat -Makros Es ist wichtig zu beachten, dass Anforderungshandler, die diese Eigenschaft verwenden, nicht zusätzliche Funktionsparameter außerhalb des Standardparameters HTTP.Request definieren können.
Im folgenden Beispiel wird der Endpunkt /repeat/hello alle 0,5 Sekunden aufgerufen und "hello" jedes Mal in die Konsole gedruckt.
Die Funktion router() hat einen interval , mit dem ein Anforderungshandler in einem festgelegten Intervall (in Sekunden) aufgerufen wird.
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 ()Nachfolgend finden Sie ein Beispiel dafür, wie Sie eine Wiederholungsaufgabe außerhalb des Routers registrieren können
@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 Wenn der Server ausgeführt wird, werden alle Aufgaben automatisch gestartet. Das Modul bietet auch Versorgungsunternehmen, die mithilfe der folgenden Funktionen eine feinkörnigere Kontrolle über die laufenden Aufgaben haben: starttasks() , stoptasks() und cleartasks()
Sauerstoff kann sich in Revision integrieren, um heiße Nachladevorgänge bereitzustellen und die Entwicklung zu beschleunigen. Da Revise empfiehlt, alle Code in einem Paket zu überarbeiten, müssen Sie zunächst zu diesem Typ eines Layouts übergehen.
Stellen Sie zunächst sicher, dass Ihr Project.toml über die erforderlichen Felder wie name an einem Paket und nicht an einem Projekt verfügt.
Schreiben Sie als nächstes den Hauptcode für Sie Routen in einem Modul src/MyModule.jl :
module MyModule
using Oxygen; @oxidise
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end
Dann können Sie ein debug.jl -Einstiegspunkt -Skript erstellen:
using Revise
using Oxygen
using MyModule
MyModule.serve(revise=:eager)
Die revise kann auch festgelegt werden :lazy . In diesem Fall wird die Überarbeitung immer kurz vor einer Anfrage überlassen, anstatt eifrig versucht zu werden, wenn sich Quelldateien auf der Festplatte ändern.
Beachten Sie, dass Sie ein weiteres Einstiegspunkt -Skript ohne Überarbeitung in der Produktion ausführen sollten.
In einigen erweiterten Szenarien müssen Sie möglicherweise mehrere Web -Severs innerhalb desselben Moduls auf verschiedenen Ports aufspannen. Oxygen bietet sowohl eine statische als auch dynamische Möglichkeit, mehrere Instanzen eines Webservers zu erstellen.
Wenn Sie als allgemeine Faustregelung wissen, wie viele Fälle Sie im Voraus benötigen, ist es am besten, den statischen Ansatz zu erreichen.
@oxidiseOxygen bietet ein neues Makro, mit dem mehrere Instanzen eingerichtet und ausgeführt werden können. Es erzeugt Methoden und bindet sie an einen neuen internen Zustand für das aktuelle Modul.
Im folgenden Beispiel werden zwei einfache Server in den Modulen A und B definiert und im übergeordneten Modul gestartet. Beide Module enthalten alle aus Sauerstoff exportierten Funktionen, die wie unten gezeigt aufgerufen werden können.
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() Mit der instance können Sie zur Laufzeit eine völlig unabhängige Instanz eines Sauerstoff -Webservers erstellen. Es erstellt dynamisch ein Julia -Modul zur Laufzeit und lädt den Sauerstoffcode darin.
Alle die gleichen Methoden aus Sauerstoff sind unter der benannten Instanz erhältlich. Im folgenden Beispiel können wir das get verwenden und serve , indem wir die DOT -Syntax in der app1 -Variablen einfach verwenden, um auf die zugrunde liegenden Methoden zuzugreifen.
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 Für Szenarien, in denen Sie höhere Verkehrsmengen bewältigen müssen, können Sie Sauerstoff in einem Multithread -Modus ausführen. Um diesen Modus zu nutzen, muss Julia mehr als einen Thread zum Arbeiten haben. Sie können eine Julia -Sitzung mit 4 Threads mit dem folgenden Befehl starten
julia --threads 4 serveparallel() startet den Webserver im Streaming -Modus und behandelt Anfragen in einem kooperativen Multitasking -Ansatz. Diese Funktion verwendet Threads.@spawn , um eine neue Aufgabe in jedem verfügbaren Thread zu planen. In der Zwischenzeit wird @Async in dieser Aufgabe verwendet, wenn Sie jeden Anforderungshandler aufrufen. Dies ermöglicht es der Aufgabe, während der E/A -Operationen nachzugeben.
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 () Sauerstoff enthält eine Erweiterung für das Protobuf.jl -Paket. Diese Erweiterung bietet eine protobuf() -Funktion, die den Prozess der Arbeit mit Protokollpuffern im Kontext des Webservers vereinfacht. Für ein besseres Verständnis dieses Pakets finden Sie in der offiziellen Dokumentation.
Diese Funktion enthält Überladungen für die folgenden Szenarien:
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)
endDas Folgende ist ein Beispiel für ein Schema, mit dem die erforderlichen Julia -Bindungen erstellt wurden. Diese Bindungen ermöglichen die Codierung und Dekodierung von Nachrichten im obigen Beispiel.
syntax = "proto3" ;
message Person {
string name = 1 ;
sint32 age = 2 ;
}
message People {
repeated Person people = 1 ;
} Sauerstoff ist mit mehreren Verpackungsverlängerungen ausgestattet, die seine Darstellungsfähigkeiten verbessern. Diese Erweiterungen machen es einfach, Diagramme direkt von Anforderungshandlern zurückzugeben. Alle Operationen werden in Memory mit einem Iobuffer durchgeführt und geben eine HTTP.Response zurück
Unterstützte Pakete und ihre Helfer -Utils:
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 () Anstatt einen internen Motor zum Vorlagen oder Hinzufügen zusätzlicher Abhängigkeiten zu erstellen, bietet Sauerstoff zwei Paketverlängerungen zur Unterstützung von Mustache.jl und OteraEngine.jl -Vorlagen.
Oxygen bietet eine einfache Wrapper -API um beide Pakete, mit der Vorlagen von Zeichenfolgen, Vorlagen und Dateien gerendert werden können. Diese Wrapper -API gibt eine render zurück, die ein Wörterbuch mit Eingängen akzeptiert, um die Vorlage auszufüllen.
In allen Szenarien wird die gerenderte Vorlage in ein HTTP.Response -Objekt zurückgegeben, das von der API bedient wird. Standardmäßig werden die MIME-Typen automatisch zu erkennen, indem sie den Inhalt der Vorlage oder den Erweiterungsnamen in der Datei betrachten. Wenn Sie den MIME -Typ kennen, können Sie ihn direkt über das Keyword -Argument mime_type übergeben, um den Erkennungsprozess zu überspringen.
Bitte werfen Sie einen Blick auf die Dokumentation von Schnurrbartschache.jl, um die vollständigen Funktionen des Pakets zu erlernen
Beispiel 1: Rendern einer Schnurrbartvorlage aus einer Datei
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
endBeispiel 2: MIME -Typ für eine Schnurrbartvorlage für Zeichenfolge angeben
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
endBitte werfen Sie einen Blick auf die Dokumentation von oteraereneNengine.jl, um die vollständigen Funktionen des Pakets zu erfahren
Beispiel 1: Rendern einer Otera -Vorlage mit Logik und Schleifen
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
endIn diesem Beispiel wird eine Otera-Vorlage mit einem For-Schleife definiert, der eine Liste von Namen iteriert und jeden Namen begrüßt.
Beispiel 2: Ausführen von Julia Code in der Otera -Vorlage
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 diesem Beispiel wird eine Otera -Vorlage mit dem eingebetteten Julia -Code definiert, der das Quadrat einer bestimmten Zahl berechnet.
Sie können statische Dateien mit dieser praktischen Funktion montieren, die rekursiv einen Ordner nach Dateien durchsucht und alles montiert. Alle Dateien werden beim Start in den Speicher geladen.
using Oxygen
# mount all files inside the "content" folder under the "/static" path
staticfiles ( " content " , " static " )
# start the web server
serve ()Ähnlich wie bei staticFiles montiert diese Funktion jeden Pfad und liest die Datei für jede Anforderung erneut. Dies bedeutet, dass Änderungen an den Dateien nach Beginn des Servers angezeigt werden.
using Oxygen
# mount all files inside the "content" folder under the "/dynamic" path
dynamicfiles ( " content " , " dynamic " )
# start the web server
serve () Durch Deaktivieren des internen Loggers können einige massive Leistungssteigerungen erzielt werden, was in einigen Szenarien hilfreich sein kann. Anekdotisch habe ich in serve() und einer 4-5-fachen Beschleunigung in serveparallel() Leistung eine 2-3-fache beschleunigte Leistung gesehen.
# This is how you disable internal logging in both modes
serve (access_log = nothing )
serveparallel (access_log = nothing ) Oxygen bietet ein Standard -Protokollierungsformat, ermöglicht Ihnen jedoch, das Format mit dem Parameter access_log anzupassen. Diese Funktionalität ist sowohl in den Funktionen serve() als auch serveparallel() verfügbar.
Weitere Informationen zu den Protokollierungsoptionen finden Sie hier
# 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 )Mit Middleware -Funktionen können benutzerdefinierte Workflows einfach erstellt werden, um alle eingehenden Anforderungen und ausgehenden Antworten abzufangen. Sie werden in der gleichen Reihenfolge ausgeführt, in der sie übergeben werden (von links nach rechts).
Sie können mit dem middleware -Keyword -Argument auf die Anwendung, den Router und die Routenebene eingestellt werden. Alle Middleware sind additiv und alle in diesen Ebenen definierten Middleware werden kombiniert und ausgeführt.
Middleware wird immer in der folgenden Reihenfolge ausgeführt:
application -> router -> route
Lassen Sie uns nun einige Middleware in Aktion sehen:
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])Wenn Sie den Standard -Antwort -Serializer von Oxygen nicht verwenden möchten, können Sie ihn ausschalten und Ihre eigenen hinzufügen! Erstellen Sie einfach Ihre eigene Special Middleware -Funktion, um die Antwort zu serialisieren und am Ende Ihrer eigenen Middleware -Kette hinzuzufügen.
Sowohl serve() als auch serveparallel() haben ein serialize -Schlüsselwortargument, das den Standard -Serializer abschaltet.
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 )Die Swagger -Dokumentation wird für jede Route, die Sie in Ihrer Anwendung registrieren, automatisch generiert. Nur der Routenname, Parametertypen und 200 und 500 Antworten werden standardmäßig automatisch für Sie erstellt.
Sie können Ihre generierte Dokumentation AT /docs anzeigen, und das Schema finden Sie unter /docs/schema . Beide Werte können in alles geändert werden, was Sie mit der Funktion configdocs() verwenden möchten. Sie können auch autogenerierte Dokumente vollständig abmelden, indem Sie die Funktion " disabledocs() vor dem Starten Ihrer Anwendung aufrufen.
Um zusätzliche Details hinzuzufügen, können Sie entweder die integrierten mergeschema() - oder setschema() -Funktionen verwenden, um das Schema selbst direkt zu ändern oder das generierte Schema aus dem Paket SwaggerMarkdown.jl zu verschmelzen (ich würde das letztere empfehlen)
Im Folgenden finden Sie ein Beispiel dafür, wie das Schema aus dem Paket SwaggerMarkdown.jl zusammengeführt wird.
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 ()Unten finden Sie ein Beispiel dafür, wie das Schema manuell modifiziert werden kann
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)| Parameter | Typ | Beschreibung |
|---|---|---|
path | string oder router() | Erforderlich . Die Route zur Registrierung |
func | function | Erforderlich . Der Anfragehandler für diese Route |
Wird verwendet, um eine Funktion an einem bestimmten Endpunkt zu registrieren, um diesen entsprechenden Anforderungsart zu verarbeiten
@route (methods, path, func)| Parameter | Typ | Beschreibung |
|---|---|---|
methods | array | Erforderlich . Die Arten von HTTP -Anfragen, um sich auf dieser Route zu registrieren |
path | string oder router() | Erforderlich . Die Route zur Registrierung |
func | function | Erforderlich . Der Anfragehandler für diese Route |
Makro mit niedrigem Niveau, mit dem eine Route mehrere Anforderungstypen behandelt werden kann
staticfiles (folder, mount)| Parameter | Typ | Beschreibung |
|---|---|---|
folder | string | Erforderlich . Der Ordner zum Servieren von Dateien von |
mountdir | string | Der Root -Endpunkt für die Montierung von Dateien unter (Standardeinstellung ist "statisch") |
set_headers | function | Passen Sie die HTTP -Antwortheader an, wenn Sie diese Dateien zurückgeben |
loadfile | function | Passen Sie das Verhalten beim Laden von Dateien an |
Servieren Sie alle statischen Dateien in einem Ordner. Diese Funktion durchsucht rekursiv ein Verzeichnis und montiert alle Dateien unter dem Mount -Verzeichnis mit ihren relativen Pfaden.
dynamicfiles (folder, mount)| Parameter | Typ | Beschreibung |
|---|---|---|
folder | string | Erforderlich . Der Ordner zum Servieren von Dateien von |
mountdir | string | Der Root -Endpunkt für die Montierung von Dateien unter (Standardeinstellung ist "statisch") |
set_headers | function | Passen Sie die HTTP -Antwortheader an, wenn Sie diese Dateien zurückgeben |
loadfile | function | Passen Sie das Verhalten beim Laden von Dateien an |
Servieren Sie alle statischen Dateien in einem Ordner. Diese Funktion durchsucht rekursiv ein Verzeichnis und montiert alle Dateien unter dem Mount -Verzeichnis mit ihren relativen Pfaden. Die Datei wird für jede Anforderung geladen, wodurch möglicherweise Änderungen der Datei abgeholt werden.
html (content, status, headers)| Parameter | Typ | Beschreibung |
|---|---|---|
content | string | Erforderlich . Die Zeichenfolge, die als HTML zurückgegeben wird |
status | integer | Der HTTP -Antwortcode (Standard ist 200) |
headers | dict | Die Header für die HTTP-Antwort (Standard-Header vom Typ Inhaltstyp ist auf "text/html; charSet = utf-8" festgelegt) |
Helferfunktion zu bestimmen, wann Inhalte als HTML zurückgegeben werden sollten
queryparams (request)| Parameter | Typ | Beschreibung |
|---|---|---|
req | HTTP.Request | Erforderlich . Das HTTP -Anforderungsobjekt |
Gibt die Abfrageparameter aus einer Anforderung als Diktat zurück ()
text (request)| Parameter | Typ | Beschreibung |
|---|---|---|
req | HTTP.Request | Erforderlich . Das HTTP -Anforderungsobjekt |
Gibt den Körper einer Anfrage als Zeichenfolge zurück
binary (request)| Parameter | Typ | Beschreibung |
|---|---|---|
req | HTTP.Request | Erforderlich . Das HTTP -Anforderungsobjekt |
Gibt den Körper einer Anfrage als Binärdatei zurück (gibt einen Vektor von UInt8 s zurück)
json (request, classtype)| Parameter | Typ | Beschreibung |
|---|---|---|
req | HTTP.Request | Erforderlich . Das HTTP -Anforderungsobjekt |
classtype | struct | Eine Struktur, um ein JSON -Objekt in zu deserialisieren in |
Deserialisieren Sie den Körper einer Anfrage in eine Julia -Struktur