ออกซิเจนเป็นรูปทรงไมโครที่สร้างขึ้นบนสุดของห้องสมุด http.jl หายใจง่ายเมื่อรู้ว่าคุณสามารถหมุนเว็บเซิร์ฟเวอร์ได้อย่างรวดเร็วด้วย abstractions ที่คุณคุ้นเคยอยู่แล้ว
ต้องการความช่วยเหลือ? อย่าลังเลที่จะเข้าถึงช่องทางโซเชียลมีเดียของเรา
pkg > add Oxygenสร้างเว็บเซิร์ฟเวอร์ที่มีรหัสน้อยมาก
using Oxygen
using HTTP
@get " /greet " function (req :: HTTP.Request )
return " hello world! "
end
# start the web server
serve ()ตัวจัดการใช้เพื่อเชื่อมต่อรหัสของคุณเข้ากับเซิร์ฟเวอร์ด้วยวิธีที่สะอาดและตรงไปตรงมา พวกเขากำหนด URL ให้กับฟังก์ชั่นและเรียกใช้ฟังก์ชันเมื่อคำขอเข้ามาตรงกับ URL นั้น
do..end ที่เทียบเท่าRequest โดยค่าเริ่มต้นเมื่อไม่มีข้อมูลประเภทตัวจัดการที่รองรับ 3 ประเภทมี 3 ประเภท:
Request ตัวจัดการStream ตัวจัดการWebsocket Handlers 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() สตรีมตัวจัดการถูกใช้เพื่อสตรีมข้อมูล พวกเขาถูกกำหนดโดยใช้ macro @stream หรือฟังก์ชัน stream() และยอมรับวัตถุ HTTP.Stream เป็นอาร์กิวเมนต์แรก ตัวจัดการเหล่านี้รองรับทั้งฟังก์ชั่นและไวยากรณ์ do-block
@stream และ stream() ไม่จำเป็นต้องมีคำจำกัดความประเภทในอาร์กิวเมนต์แรกพวกเขาคิดว่ามันเป็นสตรีมStream สามารถกำหนดได้ด้วยแมโครและฟังก์ชั่นการกำหนดเส้นทางมาตรฐาน: @get , @post ฯลฯStream WebSocket Handlers ใช้เพื่อจัดการการเชื่อมต่อ WebSocket พวกเขาถูกกำหนดโดยใช้ macro @websocket หรือฟังก์ชั่น websocket() และยอมรับวัตถุ HTTP.WebSocket เป็นอาร์กิวเมนต์แรก ตัวจัดการเหล่านี้รองรับทั้งฟังก์ชั่นและไวยากรณ์ do-block
@websocket และ websocket() ไม่จำเป็นต้องมีคำจำกัดความประเภทในอาร์กิวเมนต์แรกพวกเขาคิดว่ามันเป็น WebSocketWebsocket Handlers ยังสามารถกำหนดได้ด้วยฟังก์ชั่น @get Macro หรือ get() เนื่องจากโปรโตคอล WebSocket ต้องการการร้องขอ GET เพื่อเริ่มการจับมือกันWebsocket มีสองวิธีหลักในการลงทะเบียนตัวจัดการคำขอของคุณ: แมโครการกำหนดเส้นทางมาตรฐานหรือฟังก์ชั่นการกำหนดเส้นทางที่ใช้ไวยากรณ์ Do-block
สำหรับมาโครการกำหนดเส้นทางแต่ละครั้งตอนนี้เรามีฟังก์ชั่นการกำหนดเส้นทางที่เทียบเท่า
@get -> get ()
@post -> post ()
@put -> put ()
@patch -> patch ()
@delete -> delete ()
@route -> route ()ความแตกต่างในทางปฏิบัติเพียงอย่างเดียวระหว่างทั้งสองคือแมโครการกำหนดเส้นทางถูกเรียกในระหว่างขั้นตอนการควบคุมล่วงหน้าในขณะที่ฟังก์ชั่นการกำหนดเส้นทางจะเรียกเฉพาะเมื่อเรียกใช้เท่านั้น (แมโครการกำหนดเส้นทางเรียกฟังก์ชั่นการกำหนดเส้นทางภายใต้ประทุน)
# 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 ออกซิเจนโดยค่าเริ่มต้นจะระบุประเภทเนื้อหาของค่าส่งคืนโดยอัตโนมัติจากตัวจัดการคำขอเมื่อสร้างการตอบกลับ ฟังก์ชั่นเริ่มต้นนี้ค่อนข้างมีประโยชน์ แต่มีผลกระทบต่อประสิทธิภาพ ในสถานการณ์ที่ทราบประเภทการส่งคืนขอแนะนำให้ใช้หนึ่งในฟังก์ชั่นเรนเดอร์ที่มีอยู่ก่อนเพื่อเร่งความเร็ว
นี่คือรายการของฟังก์ชั่นเรนเดอร์ที่รองรับในปัจจุบัน: 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 () ในกรณีส่วนใหญ่ฟังก์ชั่นเหล่านี้ยอมรับสตริงธรรมดาเป็นอินพุต ข้อยกเว้นเพียงอย่างเดียวคือฟังก์ชั่น binary ซึ่งยอมรับ Vector{UInt8} และฟังก์ชัน json ซึ่งยอมรับประเภท serializable ใด ๆ
พารามิเตอร์เส้นทางจะถูกประกาศด้วยการจัดฟันและจะถูกส่งไปยังตัวจัดการคำขอของคุณโดยตรง
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 ()วัตถุทั้งหมดจะถูก deserialialing โดยอัตโนมัติใน JSON โดยใช้ไลบรารี JSON3
using Oxygen
using HTTP
@get " /data " function (req :: HTTP.Request )
return Dict ( " message " => " hello! " , " value " => 99.3 )
end
# start the web server
serve ()ออกซิเจนให้การทำให้เป็นอนุกรมและ deserialization สำหรับวัตถุส่วนใหญ่ แต่ต้องใช้ structtypes เมื่อแปลงโครงสร้างส่วนใหญ่
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 - Serializes การร้องขอทั้งหมดให้กับประเภทที่กำหนด (สตริง, 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 ค่าเริ่มต้นสามารถตั้งค่าด้วย structs โดยใช้ 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 ด้านบนของข้อมูลที่เข้ามาแบบอนุกรมคุณยังสามารถกำหนดกฎการตรวจสอบความถูกต้องของคุณเองโดยใช้ฟังก์ชัน validate ในตัวอย่างด้านล่างเราแสดงวิธีการใช้ตัวตรวจสอบทั้ง global และ local ในรหัสของคุณ
global ก่อนที่จะเรียกใช้ตัวตรวจสอบความถูกต้อง 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 คุณสามารถแทรกตัวแปรโดยตรงเข้ากับเส้นทางซึ่งทำให้เส้นทางการลงทะเบียนแบบไดนามิกเป็นเรื่องง่าย
(ขอบคุณ @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 - ใช้ในการจัดระเบียบจุดสิ้นสุดในเอกสาร Autogeneratedmiddleware - ใช้ในการตั้งค่าเราเตอร์และมิดเดิลแวร์เฉพาะเส้นทาง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 ()ออกซิเจนมาพร้อมกับระบบกำหนดเวลา cron ในตัวซึ่งช่วยให้คุณสามารถโทรหาจุดสิ้นสุดและฟังก์ชั่นโดยอัตโนมัติเมื่อนิพจน์ cron ตรงกับเวลาปัจจุบัน
เมื่อกำหนดงานงานใหม่จะถูกสร้างและทำงานในพื้นหลัง แต่ละงานใช้นิพจน์ 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 " )
end ฟังก์ชั่น router() มีอาร์กิวเมนต์คำหลักที่เรียกว่า 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 " )
end เมื่อคุณเรียกใช้ serve() หรือ serveparallel() งาน cron ที่ลงทะเบียนทั้งหมดจะเริ่มต้นขึ้นโดยอัตโนมัติ หากเซิร์ฟเวอร์หยุดหรือฆ่างานที่กำลังทำงานทั้งหมดจะถูกยกเลิก คุณสามารถหยุดเซิร์ฟเวอร์และงานทำซ้ำและงาน cron ทั้งหมดโดยเรียกฟังก์ชั่น terminate() หรือฆ่าเซิร์ฟเวอร์ด้วยตนเองด้วย ctrl+C
นอกจากนี้ออกซิเจนยังให้ฟังก์ชั่นยูทิลิตี้เพื่อเริ่มต้นและหยุดงาน cron ด้วยตนเอง: startcronjobs() และ stopcronjobs() ฟังก์ชั่นเหล่านี้สามารถใช้นอกเว็บเซิร์ฟเวอร์ได้เช่นกัน
งานทำซ้ำให้ API อย่างง่ายเพื่อเรียกใช้ฟังก์ชั่นในช่วงเวลาที่กำหนด
มีสองวิธีในการลงทะเบียนงานทำซ้ำ:
interval ใน router()@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
จากนั้นคุณสามารถสร้างสคริปต์ entrypoint debug.jl :
using Revise
using Oxygen
using MyModule
MyModule.serve(revise=:eager)
ตัวเลือก revise ยังสามารถตั้งค่าเป็น :lazy ซึ่งในกรณีนี้การแก้ไขจะถูกทิ้งไว้เสมอก่อนที่จะมีการร้องขอแทนที่จะพยายามอย่างกระตือรือร้นเมื่อไฟล์ต้นฉบับเปลี่ยนบนดิสก์
โปรดทราบว่าคุณควรเรียกใช้สคริปต์ entrypoint อื่นโดยไม่ต้องแก้ไขในการผลิต
ในสถานการณ์ขั้นสูงบางอย่างคุณอาจต้องหมุนเว็บหลายตัวในโมดูลเดียวกันในพอร์ตที่แตกต่างกัน ออกซิเจนให้ทั้งวิธีคงที่และแบบไดนามิกในการสร้างหลายอินสแตนซ์ของเว็บเซิร์ฟเวอร์
ตามกฎทั่วไปของหัวแม่มือถ้าคุณรู้ว่าคุณต้องการกี่ครั้งก่อนเวลาควรไปด้วยวิธีการคงที่
@oxidiseออกซิเจนให้มาโครใหม่ซึ่งทำให้สามารถตั้งค่าและเรียกใช้หลายอินสแตนซ์ มันสร้างวิธีการและผูกไว้กับสถานะภายในใหม่สำหรับโมดูลปัจจุบัน
ในตัวอย่างด้านล่างเซิร์ฟเวอร์ง่าย ๆ สองตัวถูกกำหนดภายในโมดูล 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 ช่วยให้คุณสร้างอินสแตนซ์อิสระอย่างสมบูรณ์ของเว็บเซิร์ฟเวอร์ออกซิเจนที่รันไทม์ มันทำงานได้โดยการสร้างโมดูล Julia แบบไดนามิกที่รันไทม์และโหลดรหัสออกซิเจนภายใน
วิธีการเดียวกันทั้งหมดจากออกซิเจนมีอยู่ภายใต้อินสแตนซ์ที่มีชื่อ ในตัวอย่างด้านล่างเราสามารถใช้ get และ serve โดยใช้ไวยากรณ์ DOT บนตัวแปร app1 เพื่อเข้าถึงวิธีการพื้นฐาน
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 สำหรับสถานการณ์ที่คุณต้องการจัดการปริมาณการใช้งานที่สูงขึ้นคุณสามารถเรียกใช้ออกซิเจนในโหมดมัลติเธรด เพื่อที่จะใช้โหมดนี้จูเลียต้องมีมากกว่า 1 เธรดเพื่อทำงานด้วย คุณสามารถเริ่มเซสชัน Julia ด้วย 4 เธรดโดยใช้คำสั่งด้านล่าง
julia --threads 4 serveparallel() เริ่มต้นเว็บเซิร์ฟเวอร์ในโหมดสตรีมมิ่งและจัดการคำขอในวิธีการทำงานหลายอย่างแบบร่วมมือ ฟังก์ชั่นนี้ใช้ 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() ซึ่งทำให้กระบวนการทำงานกับบัฟเฟอร์โปรโตคอลง่ายขึ้นในบริบทของเว็บเซิร์ฟเวอร์ เพื่อความเข้าใจที่ดีขึ้นเกี่ยวกับแพ็คเกจนี้โปรดดูเอกสารอย่างเป็นทางการ
ฟังก์ชั่นนี้มีมากเกินไปสำหรับสถานการณ์ต่อไปนี้:
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ต่อไปนี้เป็นตัวอย่างของสคีมาที่ใช้ในการสร้างการผูก Julia ที่จำเป็น การผูกเหล่านี้อนุญาตให้มีการเข้ารหัสและถอดรหัสข้อความในตัวอย่างข้างต้น
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 () แทนที่จะสร้างเครื่องยนต์ภายในสำหรับการเทมเพลตหรือเพิ่มการพึ่งพาเพิ่มเติมออกซิเจนมีส่วนขยายแพ็คเกจสองแบบเพื่อรองรับ Mustache.jl และ OteraEngine.jl เทมเพลต
ออกซิเจนให้ API Wrapper อย่างง่ายรอบแพ็คเกจทั้งสองซึ่งทำให้ง่ายต่อการแสดงเทมเพลตจากสตริงเทมเพลตและไฟล์ Wrapper API นี้ส่งคืนฟังก์ชั่น render ซึ่งยอมรับพจนานุกรมของอินพุตเพื่อกรอกเทมเพลต
ในทุกสถานการณ์เทมเพลตที่แสดงผลจะถูกส่งกลับภายในวัตถุ HTTP.Response พร้อมที่จะรับใช้โดย API โดยค่าเริ่มต้นประเภท 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: เรียกใช้รหัส Julia ในเทมเพลต 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 ถูกกำหนดด้วยรหัสจูเลียฝังตัวที่คำนวณสี่เหลี่ยมจัตุรัสของหมายเลขที่กำหนด
คุณสามารถติดตั้งไฟล์แบบคงที่โดยใช้ฟังก์ชั่นที่มีประโยชน์นี้ซึ่งค้นหาโฟลเดอร์สำหรับไฟล์และติดตั้งทุกอย่าง ไฟล์ทั้งหมดจะถูกโหลดลงในหน่วยความจำเมื่อเริ่มต้น
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 () การปิดการใช้งานเครื่องบันทึกภายในสามารถให้การเพิ่มประสิทธิภาพอย่างมากซึ่งจะเป็นประโยชน์ในบางสถานการณ์ โดยทั่วไปฉันได้เห็นการเร่งความเร็ว 2-3x ใน serve() และการเร่งความเร็ว 4-5x ในประสิทธิภาพการทำงาน serveparallel()
# 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])หากคุณไม่ต้องการใช้ Serializer ตอบสนองเริ่มต้นของออกซิเจนคุณสามารถปิดและเพิ่มของคุณเอง! เพียงสร้างฟังก์ชั่นมิดเดิลแวร์พิเศษของคุณเองเพื่อสร้างการตอบสนองและเพิ่มในตอนท้ายของห่วงโซ่มิดเดิลแวร์ของคุณเอง
ทั้งสอง serve() และ serveparallel() มีอาร์กิวเมนต์คำหลักที่ serialize ซึ่งสามารถสลับ Serializer เริ่มต้นได้
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() นอกจากนี้คุณยังสามารถเลือกใช้เอกสารแบบ autogenerated ทั้งหมดได้โดยเรียกฟังก์ชั่น 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 | ปรับแต่งพฤติกรรมเมื่อโหลดไฟล์ |
ให้บริการไฟล์คงที่ทั้งหมดภายในโฟลเดอร์ ฟังก์ชั่นนี้ค้นหาไดเรกทอรีซ้ำและติดตั้งไฟล์ทั้งหมดภายใต้ไดเรกทอรี Mount โดยใช้เส้นทางสัมพัทธ์
dynamicfiles (folder, mount)| พารามิเตอร์ | พิมพ์ | คำอธิบาย |
|---|---|---|
folder | string | ที่จำเป็น . โฟลเดอร์เพื่อให้บริการไฟล์จาก |
mountdir | string | จุดสิ้นสุดรูทไปยังไฟล์ใต้ (ค่าเริ่มต้นคือ "คงที่") |
set_headers | function | ปรับแต่งส่วนหัวการตอบกลับ HTTP เมื่อส่งคืนไฟล์เหล่านี้ |
loadfile | function | ปรับแต่งพฤติกรรมเมื่อโหลดไฟล์ |
ให้บริการไฟล์คงที่ทั้งหมดภายในโฟลเดอร์ ฟังก์ชั่นนี้ค้นหาไดเรกทอรีซ้ำและติดตั้งไฟล์ทั้งหมดภายใต้ไดเรกทอรี Mount โดยใช้เส้นทางสัมพัทธ์ ไฟล์ถูกโหลดในแต่ละคำขอซึ่งอาจเพิ่มการเปลี่ยนแปลงไฟล์ใด ๆ
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 | โครงสร้างเพื่อ deserialize วัตถุ JSON เข้าไปใน |
deserialize ร่างของคำขอเข้าสู่โครงสร้างจูเลีย