酸素は、http.jlライブラリの上に構築されたマイクロフレームワークです。既によく知っている抽象化を使用して、Webサーバーをすばやくスピンアップできることを簡単に呼吸できます。
助けが必要ですか?ソーシャルメディアチャネルに自由にご連絡ください。
pkg > add Oxygenコードの行が非常に少ないWebサーバーを作成します
using Oxygen
using HTTP
@get " /greet " function (req :: HTTP.Request )
return " hello world! "
end
# start the web server
serve ()ハンドラーは、クリーンで簡単な方法でコードをサーバーに接続するために使用されます。彼らは機能にURLを割り当て、着信要求がそのURLと一致するときに関数を呼び出します。
do..endRequestハンドラーであると仮定しますサポートされているハンドラーには3種類あります。
RequestStreamハンドラーWebsocketハンドラー 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それらは単なる機能であり、表現して定義できる多くの方法があります。以下は、 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リクエストハンドラーは、HTTPリクエストを処理するために使用されます。それらは、マクロまたはその関数の等価物を使用して定義され、 HTTP.Requestオブジェクトを最初の引数として受け入れます。これらのハンドラーは、機能とdo-blockの両方の構文をサポートしています。
@get 、 @post 、 @put 、 @patch 、 @delete 、 @routeget() 、 post() 、 put() 、 patch() 、 delete() 、 route()ストリームハンドラーは、データのストリーミングに使用されます。 @stream Macroまたはstream()関数を使用して定義され、最初の引数としてHTTP.Streamオブジェクトを受け入れます。これらのハンドラーは、機能とdo-blockの両方の構文をサポートしています。
@streamとstream() 、最初の引数でタイプ定義を必要としません。彼らはそれがストリームであると仮定します。Streamハンドラーは、標準のルーティングマクロと機能で割り当てることができます: @get 、 @postなどStreamハンドラーとして識別できるように、タイプ定義を明示的に含める必要がありますWebSocketハンドラーは、WebSocket接続を処理するために使用されます。 @websocket Macroまたはwebsocket()機能を使用して定義され、最初の引数としてHTTP.WebSocketオブジェクトを受け入れます。これらのハンドラーは、機能とdo-blockの両方の構文をサポートしています。
@websocketとwebsocket() 、最初の引数でタイプ定義を必要としません。彼らはそれがWebSocketであると仮定します。GETリクエストが必要なため、 @get Macroまたはget()関数でWebsocketハンドラーを割り当てることもできます。Websocketハンドラーとして識別できるように、タイプ定義を明示的に含める必要がありますリクエストハンドラーを登録するには、標準のルーティングマクロまたはdo-block構文を利用するルーティング関数の2つの主要な方法があります。
ルーティングマクロごとに、同等のルーティング関数が得られました
@get -> get ()
@post -> post ()
@put -> put ()
@patch -> patch ()
@delete -> delete ()
@route -> route ()2つの実用的な違いは、ルーティングマクロがプリコンパイル段階で呼び出されるのに対し、ルーティング関数は呼び出されたときにのみ呼び出されることです。 (ルーティングマクロは、フードの下のルーティング関数を呼び出します)
# 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 酸素は、デフォルトでは、応答を構築するときにリクエストハンドラーからの返品値のコンテンツタイプを自動的に識別します。このデフォルトの機能は非常に便利ですが、パフォーマンスに影響を与えます。リターンタイプがわかっている状況では、既存のレンダリング関数の1つを使用して物事を高速化することをお勧めします。
現在サポートされているレンダリング関数のリストを示します: html 、 text 、 json 、 file 、 xml 、 js 、 css 、 binary
以下は、これらの機能を使用する方法の例です。
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 ()ほとんどの場合、これらの関数は入力としてプレーン文字列を受け入れます。唯一の例外は、 Vector{UInt8}を受け入れるbinary関数と、シリアル化可能なタイプを受け入れるjson関数です。
パスパラメーターはブレースで宣言され、リクエストハンドラーに直接渡されます。
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 ()クエリパラメーターは、ハンドラーの署名の内側に直接宣言できます。ルートパス内で言及されていないパラメーターは、クエリパラメーターであると想定されます。
@get " /query " function (req :: HTTP.Request , a :: Int , message :: String = " hello world " )
return (a, message)
endまたは、 queryparams()関数を使用して、辞書としてURLから生値を抽出できます。
@get " /query " function (req :: HTTP.Request )
return queryparams (req)
end formdata()関数を使用して、リクエストの本体からフォームデータを抽出して解析します。この関数は、フォームからキー価値ペアの辞書を返します
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 ()すべてのオブジェクトは、JSON3ライブラリを使用してJSONに自動的に偏ります
using Oxygen
using HTTP
@get " /data " function (req :: HTTP.Request )
return Dict ( " message " => " hello! " , " value " => 99.3 )
end
# start the web server
serve ()酸素は、ほとんどのオブジェクトにすぐに使用できるシリアル化と降下を提供しますが、構造体を変換するときに構造タイプの使用が必要です
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 ()酸素には、ハンドラー機能への入力をシリアル化するために必要なボイラープレートの量を減らすために設計されたいくつかの組み込み抽出器が付属しています。構造を定義し、データソースを指定するだけで、これらの抽出者は、均一なAPIを介してデータの摂取と検証のプロセスを合理化します。
payloadプロパティからアクセスできます@kwdefマクロで定義すると、デフォルト値を割り当てることができますサポートされている抽出器:
Path - パスパラメーターから抽出しますQuery - クエリパラメーターからの抽出、Header - リクエストヘッダーからの抽出Form - リクエスト本体からフォームデータを抽出しますBody - リクエストボディ全体を特定のタイプにシリアル化します(文字列、float64など..)ProtoBufferリクエスト本体からProtoBufメッセージを抽出します(パッケージ拡張機能を介して利用可能)Jsonリクエスト本体からJSONを抽出しますJsonFragmentパラメーター名を使用してJSONボディの「フラグメント」を抽出して、対応するトップレベルキーを識別および抽出しますこの例では、通常のパスパラメーターとともにPath抽出器を使用できることを示します。これは、通常のクエリパラメーターと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 デフォルト値は、 @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 着信データのシリアル化に加えて、 validate関数を使用して独自の検証ルールを定義することもできます。以下の例では、コードでglobalデーターとlocalバリエーターの両方を使用する方法を示します。
localバリーターを実行する前にglobalバリエーターを呼び出します。 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 変数をパスに直接補間することができます。これにより、ルートを動的に登録することができます。
(アイデアをありがとう@Anandijainに感謝します)
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 ()router()関数は、複数のエンドポイントで同じパスプレフィックスとプロパティを再利用できるHOF(高次関数)です。これは、APIが成長し始め、パス操作を整理し続けたい場合に役立ちます。
以下は、 router()関数が取ることができる引数です。
router (prefix :: String ; tags :: Vector , middleware :: Vector , interval :: Real , cron :: String )tags - 自動生成ドキュメントでエンドポイントを整理するために使用されますmiddleware - ルーターとルート固有のミドルウェアのセットアップに使用されますinterval - 繰り返しアクションをサポートするために使用されます(秒単位で設定されたインターバルでリクエストハンドラーを呼び出す)cronは、リクエストハンドラーをいつ呼び出すかを決定するCron式を指定するために使用されます。 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には、Cronの式が現在と一致するときにエンドポイントと機能を自動的に呼び出すことができる組み込みのCronスケジューリングシステムが付属しています。
ジョブがスケジュールされると、新しいタスクが作成され、バックグラウンドで実行されます。各タスクは、指定されたクロン式と現在の時刻を使用して、実行する前に睡眠が必要な時間を判断します。
酸素のクロンパーサーは、春に使用されたものと同じ仕様に基づいています。これについては、Spring Cron Expressionsページで詳細を確認できます。
以下は、Cron式の各パラメーターが表すものの内訳です。私たちの仕様は春に定義されたものに非常に似ていますが、正確な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)
│ │ │ │ │ │
* * * * * *
部分的な表現もサポートされています。つまり、その後の式は除外される可能性があります(デフォルトは'*'にデフォルトです)。
# 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 " )
endrouter()関数には、 cronと呼ばれるキーワード引数があります。これは、エンドポイントがいつ呼び出されるかを決定するCron式を受け入れます。他のキーワード引数と同様に、ルーターを共有するエンドポイントで再利用するか、継承されたエンドポイントによってオーバーライドされる可能性があります。
# 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エンドポイントのスケジューリングに加えて、新しい@cronマクロを使用して機能をスケジュールすることもできます。これは、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 " )
endserve()またはserveparallel()を実行すると、登録されたすべてのCRONジョブが自動的に開始されます。サーバーが停止または殺された場合、すべての実行中のジョブも終了します。 terminate()関数を呼び出すか、 ctrl+Cでサーバーを手動で殺害することにより、サーバーとすべてのタスクとCronのジョブを繰り返すことができます。
さらに、酸素は、CRON startcronjobs()とstopcronjobs()を手動で開始および停止するためのユーティリティ関数を提供します。これらの機能は、Webサーバーの外でも使用できます。
繰り返しタスクは、設定された間隔で関数を実行するための単純なAPIを提供します。
繰り返しタスクを登録するには2つの方法があります。
router()のintervalパラメーターを介して@repeatマクロを使用しますこのプロパティを使用する要求ハンドラーは、デフォルトのHTTP.Requestパラメーターの外側に追加の関数パラメーターを定義できないことに注意することが重要です。
以下の例では、 /repeat/helloエンドポイントは0.5秒ごとに呼び出され、 "hello"が毎回コンソールに印刷されます。
router()関数には、設定されたインターバル(秒)でリクエストハンドラーを呼び出すために使用されるintervalパラメーターがあります。
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 ()以下は、ルーターの外で繰り返しタスクを登録する方法の例です
@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サーバーが実行されると、すべてのタスクが自動的に開始されます。ただし、このモジュールは、次の機能を使用して、実行中のタスクをより細かく制御するためのユーティリティも提供します: starttasks() 、 stoptasks() 、およびcleartasks()
酸素は修正と統合してホットリロードを提供し、開発を高速化できます。 Reviseはすべてのコードをパッケージで修正することを推奨するため、最初にこのタイプのレイアウトに移動する必要があります。
まず、 Project.tomlには、プロジェクトではなくパッケージで作業するためにnameなどの必要なフィールドがあることを確認してください。
次に、モジュールsrc/MyModule.jlにルートのメインコードを書きます:
module MyModule
using Oxygen; @oxidise
@get "/greet" function(req::HTTP.Request)
return "hello world!"
end
end
その後、 debug.jl entrypointスクリプトを作成できます。
using Revise
using Oxygen
using MyModule
MyModule.serve(revise=:eager)
reviseオプションは、 :lazyこの場合、ソースファイルがディスク上で変更されたときに熱心に試みられるのではなく、リクエストが提供される直前に、常に改訂版を任せます。
制作の修正なしで別のエントリポイントスクリプトを実行する必要があることに注意してください。
いくつかの高度なシナリオでは、異なるポート上の同じモジュール内の複数のWebセバーをスピンアップする必要がある場合があります。酸素は、Webサーバーの複数のインスタンスを作成するための静的および動的な方法の両方を提供します。
一般的な経験則として、あなたが事前に必要なインスタンスの数を知っているなら、静的アプローチに行くのが最善です。
@oxidiseを使用した複数のインスタンス酸素は新しいマクロを提供し、複数のインスタンスをセットアップおよび実行できるようにします。メソッドを生成し、現在のモジュールの新しい内部状態に結合します。
以下の例では、2つの簡単なサーバーがモジュールAとB内で定義され、親モジュールで開始されます。両方のモジュールには、酸素からエクスポートされたすべての関数が含まれており、以下に示すように直接呼び出すことができます。
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()を使用した複数のインスタンスinstance関数は、実行時に酸素Webサーバーの完全に独立したインスタンスを作成するのに役立ちます。実行時にジュリアモジュールを動的に作成し、その中の酸素コードをロードすることで機能します。
酸素からの同じ方法はすべて、指定されたインスタンスで利用できます。以下の例では、 getを使用して、 app1変数にdot構文を使用して基礎となる方法にアクセスするだけでserveできます。
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 より多くのトラフィックを処理する必要があるシナリオの場合、マルチスレッドモードで酸素を実行できます。このモードを利用するには、ジュリアは作業するために複数のスレッドを持っている必要があります。以下のコマンドを使用して、4つのスレッドでジュリアセッションを開始できます
julia --threads 4 serveparallel() 、ストリーミングモードでWebサーバーを起動し、協力的なマルチタスクアプローチでリクエストを処理します。この関数はThreads.@spawn利用可能なスレッドで新しいタスクをスケジュールします。一方、各リクエストハンドラーを呼び出すとき、 @asyncはこのタスク内で使用されます。これにより、タスクはI/O操作中に生じることができます。
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 ()酸素には、protobuf.jlパッケージの拡張機能が含まれています。この拡張機能はprotobuf()関数を提供し、Webサーバーのコンテキストでプロトコルバッファーを操作するプロセスを簡素化します。このパッケージをよりよく理解するには、その公式文書を参照してください。
この関数には、次のシナリオに過負荷があります。
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)
end以下は、必要なジュリアバインディングを作成するために使用されたスキーマの例です。これらのバインディングにより、上記の例でメッセージのエンコードとデコードが可能になります。
syntax = "proto3" ;
message Person {
string name = 1 ;
sint32 age = 2 ;
}
message People {
repeated Person people = 1 ;
}酸素には、プロット機能を強化するいくつかのパッケージ拡張機能が装備されています。これらの拡張機能により、リクエストハンドラーから直接プロットを簡単に返すことができます。すべての操作はiobufferを使用してメモリ内で実行され、 HTTP.Responseを返します
サポートされているパッケージとそのヘルパーユーティル:
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 ()テンプレートまたは追加の依存関係を追加するための内部エンジンを構築するのではなく、OxygenはMustache.jlとOteraEngine.jlテンプレートをサポートするための2つのパッケージ拡張機能を提供します。
Oxygenは、両方のパッケージの周りにシンプルなラッパーAPIを提供し、文字列、テンプレート、ファイルからテンプレートを簡単にレンダリングできるようにします。このラッパーAPIは、テンプレートに記入するために入力の辞書を受け入れるrender関数を返します。
すべてのシナリオで、レンダリングされたテンプレートは、APIが提供する準備ができているhttp.responseオブジェクト内で返されます。デフォルトでは、MIMEタイプは、テンプレートのコンテンツまたはファイルの拡張名を調べることにより、自動検出されます。 MIMEタイプがわかっている場合は、 mime_typeキーワード引数を介して直接渡すことができ、検出プロセスをスキップできます。
Mustache.jlドキュメントを見て、パッケージの完全な機能を学習してください
例1:ファイルから口ひげテンプレートをレンダリングします
using Mustache
using Oxygen
# Load the Mustache template from a file and create a render function
render = mustache ( " ./templates/greeting.txt " , from_file = false )
@get " /mustache/file " function ()
data = Dict ( " name " => " Chris " )
return render (data) # This will return an HTML.Response with the rendered template
end例2:プレーンストリングの口ひげテンプレートのMIMEタイプの指定
using Mustache
using Oxygen
# Define a Mustache template (both plain strings and mustache templates are supported)
template_str = " Hello, {{name}}! "
# Create a render function, specifying the MIME type as text/plain
render = mustache (template_str, mime_type = " text/plain " ) # mime_type keyword arg is optional
@get " /plain/text " function ()
data = Dict ( " name " => " Chris " )
return render (data) # This will return a plain text response with the rendered template
endパッケージの完全な機能を学ぶために、oteraengine.jlのドキュメントを見てください
例1:ロジックとループでOteraテンプレートをレンダリングする
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
endこの例では、Oteraテンプレートは、名前のリストを反復するループで定義され、各名前に挨拶します。
例2: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
この例では、Oteraテンプレートは、特定の数値の正方形を計算する埋め込まれたJuliaコードで定義されています。
この便利な機能を使用して静的ファイルをマウントすることができます。この機能により、ファイルがフォルダーを再帰的に検索し、すべてをマウントできます。すべてのファイルは、起動時にメモリにロードされます。
using Oxygen
# mount all files inside the "content" folder under the "/static" path
staticfiles ( " content " , " static " )
# start the web server
serve ()staticFilesと同様に、この関数は各パスをマウントし、各リクエストのファイルを読み直します。これは、サーバーが開始された後のファイルの変更が表示されることを意味します。
using Oxygen
# mount all files inside the "content" folder under the "/dynamic" path
dynamicfiles ( " content " , " dynamic " )
# start the web server
serve ()内部ロガーを無効にすると、いくつかの大規模なパフォーマンスの向上を提供できます。これは、いくつかのシナリオで役立ちます。逸話的には、 serve()で2〜3倍のスピードアップと、 serveparallel()パフォーマンスで4-5xスピードアップが見られました。
# This is how you disable internal logging in both modes
serve (access_log = nothing )
serveparallel (access_log = nothing )Oxygenはデフォルトのロギング形式を提供しますが、 access_logパラメーターを使用して形式をカスタマイズできます。この機能はserve()とserveparallel()関数の両方で使用できます。
ロギングオプションの詳細については、こちらをご覧ください
# Uses the default logging format
serve ()
# Customize the logging format
serve (access_log = logfmt " [$time_iso8601] " $request " $status " )
# Disable internal request logging
serve (access_log = nothing )ミドルウェア関数により、カスタムワークフローを簡単に作成して、すべての着信要求と発信応答をインターセプトできます。それらは(左から右に)渡されるのと同じ順序で実行されます。
middlewareキーワード引数を使用して、アプリケーション、ルーター、およびルートレイヤーに設定できます。すべてのミドルウェアは添加剤であり、これらのレイヤーで定義されているミドルウェアはすべて組み合わされて実行されます。
ミドルウェアは常に次の順序で実行されます。
application -> router -> route
ここで、いくつかのミドルウェアが動作しているのを見てみましょう。
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])Oxygenのデフォルト応答シリアイザーを使用したくない場合は、オフにして独自のレスポを追加できます。独自の特別なミドルウェア関数を作成して、応答をシリアル化し、独自のミドルウェアチェーンの最後に追加してください。
serve()とserveparallel()の両方に、デフォルトのシリアイザーを切り替えることができるserializeキーワード引数があります。
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 )Swaggerドキュメントは、アプリケーションに登録するルートごとに自動的に生成されます。デフォルトでは、ルート名、パラメータータイプ、200&500の応答のみが自動的に作成されます。
生成されたドキュメントを/docsで表示できます。スキーマは/docs/schemaの下で見つけることができます。これらの値は両方ともconfigdocs()関数を使用して必要なものに変更できます。また、アプリケーションを開始する前に、 disabledocs()関数を呼び出すことにより、自動生成ドキュメントを完全にオプトアウトすることもできます。
詳細を追加するには、組み込みのmergeschema()またはsetschema()関数を使用してスキーマを直接変更するか、生成されたスキーマをSwaggerMarkdown.jlパッケージからマージできます(後者をお勧めします)
以下は、 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 ()以下は、スキーマを手動で変更する方法の例です
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)| パラメーター | タイプ | 説明 |
|---|---|---|
path | stringまたはrouter() | 必須。登録するルート |
func | function | 必須。このルートのリクエストハンドラー |
特定のエンドポイントに関数を登録して、対応するタイプのリクエストを処理するために使用されます
@route (methods, path, func)| パラメーター | タイプ | 説明 |
|---|---|---|
methods | array | 必須。このルートに登録するためのHTTP要求の種類 |
path | stringまたはrouter() | 必須。登録するルート |
func | function | 必須。このルートのリクエストハンドラー |
ルートを複数の要求タイプを処理できるようにする低レベルのマクロ
staticfiles (folder, mount)| パラメーター | タイプ | 説明 |
|---|---|---|
folder | string | 必須。ファイルを提供するフォルダー |
mountdir | string | 下にファイルをマウントするルートエンドポイント(デフォルトは「静的」です) |
set_headers | function | これらのファイルを返すときにHTTP応答ヘッダーをカスタマイズします |
loadfile | function | ファイルをロードするときに動作をカスタマイズします |
フォルダー内のすべての静的ファイルを提供します。この関数は、ディレクトリを再帰的に検索し、相対パスを使用してマウントディレクトリの下にすべてのファイルをマウントします。
dynamicfiles (folder, mount)| パラメーター | タイプ | 説明 |
|---|---|---|
folder | string | 必須。ファイルを提供するフォルダー |
mountdir | string | 下にファイルをマウントするルートエンドポイント(デフォルトは「静的」です) |
set_headers | function | これらのファイルを返すときにHTTP応答ヘッダーをカスタマイズします |
loadfile | function | ファイルをロードするときに動作をカスタマイズします |
フォルダー内のすべての静的ファイルを提供します。この関数は、ディレクトリを再帰的に検索し、相対パスを使用してマウントディレクトリの下にすべてのファイルをマウントします。ファイルは各リクエストで読み込まれ、ファイルの変更を選択する可能性があります。
html (content, status, headers)| パラメーター | タイプ | 説明 |
|---|---|---|
content | string | 必須。 HTMLとして返される文字列 |
status | integer | HTTP応答コード(デフォルトは200です) |
headers | dict | HTTP応答のヘッダー(デフォルトには、「Text/HTML; charset = utf-8」に設定されたコンテンツタイプのヘッダーがあります) |
HTMLとしてコンテンツを返す必要があるときに指定するヘルパー機能
queryparams (request)| パラメーター | タイプ | 説明 |
|---|---|---|
req | HTTP.Request | 必須。 HTTP要求オブジェクト |
dict()としてリクエストからクエリパラメーターを返します
text (request)| パラメーター | タイプ | 説明 |
|---|---|---|
req | HTTP.Request | 必須。 HTTP要求オブジェクト |
リクエストの本体を文字列として返します
binary (request)| パラメーター | タイプ | 説明 |
|---|---|---|
req | HTTP.Request | 必須。 HTTP要求オブジェクト |
バイナリファイルとしてリクエストの本文を返します( UInt8 sのベクトルを返します)
json (request, classtype)| パラメーター | タイプ | 説明 |
|---|---|---|
req | HTTP.Request | 必須。 HTTP要求オブジェクト |
classtype | struct | JSONオブジェクトをゆるくする構造体 |
リクエストの体をジュリアの構造に脱皮化する