ภาษาอังกฤษ简体中文繁體中文



Salvo เป็นกรอบแบ็คเอนด์เว็บสนิมที่เรียบง่ายและทรงพลังมาก จำเป็นต้องมีเพียงความรู้พื้นฐานที่เป็นสนิมเพื่อพัฒนาบริการแบ็กเอนด์
คุณสามารถดูตัวอย่างได้ที่นี่หรือดูเว็บไซต์ทางการ
ใช้รหัสเพียงไม่กี่บรรทัดในการใช้เซิร์ฟเวอร์ที่รองรับ ACME เพื่อรับใบรับรองโดยอัตโนมัติและรองรับ HTTP1, HTTP2 และ HTTP3 โปรโตคอล
use salvo :: prelude :: * ;
# [ handler ]
async fn hello ( res : & mut Response ) {
res . render ( Text :: Plain ( "Hello World" ) ) ;
}
# [ tokio :: main ]
async fn main ( ) {
let mut router = Router :: new ( ) . get ( hello ) ;
let listener = TcpListener :: new ( "0.0.0.0:443" )
. acme ( )
. add_domain ( "test.salvo.rs" ) // Replace this domain name with your own.
. http01_challenge ( & mut router ) . quinn ( "0.0.0.0:443" ) ;
let acceptor = listener . join ( TcpListener :: new ( "0.0.0.0:80" ) ) . bind ( ) . await ;
Server :: new ( acceptor ) . serve ( router ) . await ;
}ไม่มีความแตกต่างระหว่างตัวจัดการและมิดเดิลแวร์มิดเดิลแวร์เป็นเพียงตัวจัดการ คุณสามารถเขียนมิดเดิลแวร์โดยไม่ทราบแนวคิดเช่นประเภทที่เกี่ยวข้องและประเภททั่วไป หากคุณสามารถเขียนฟังก์ชั่นได้คุณสามารถเขียนมิดเดิลแวร์ !!!
use salvo :: http :: header :: { self , HeaderValue } ;
use salvo :: prelude :: * ;
# [ handler ]
async fn add_header ( res : & mut Response ) {
res . headers_mut ( )
. insert ( header :: SERVER , HeaderValue :: from_static ( "Salvo" ) ) ;
}จากนั้นเพิ่มลงในเราเตอร์:
Router :: new ( ) . hoop ( add_header ) . get ( hello ) นี่คือมิดเดิลแวร์ที่ง่ายมากมันจะเพิ่ม Header ใน Response ดูซอร์สโค้ดเต็ม
โดยปกติเราเขียนเส้นทางแบบนี้:
Router :: with_path ( "articles" ) . get ( list_articles ) . post ( create_article ) ;
Router :: with_path ( "articles/<id>" )
. get ( show_article )
. patch ( edit_article )
. delete ( delete_article ) ;บ่อยครั้งเช่นการดูบทความและรายการบทความไม่จำเป็นต้องมีการเข้าสู่ระบบของผู้ใช้ แต่การสร้างการแก้ไขบทความการลบ ฯลฯ จำเป็นต้องมีสิทธิ์การตรวจสอบสิทธิ์การเข้าสู่ระบบของผู้ใช้ ระบบการกำหนดเส้นทางที่เหมือนต้นไม้ใน Salvo สามารถตอบสนองความต้องการนี้ได้ เราสามารถเขียนเราเตอร์โดยไม่ต้องเข้าสู่ระบบของผู้ใช้ด้วยกัน:
Router :: with_path ( "articles" )
. get ( list_articles )
. push ( Router :: with_path ( "<id>" ) . get ( show_article ) ) ;จากนั้นเขียนเราเตอร์ที่ต้องการให้ผู้ใช้เข้าสู่ระบบเข้าด้วยกันและใช้มิดเดิลแวร์ที่เกี่ยวข้องเพื่อตรวจสอบว่าผู้ใช้เข้าสู่ระบบหรือไม่:
Router :: with_path ( "articles" )
. hoop ( auth_check )
. push ( Router :: with_path ( "<id>" ) . patch ( edit_article ) . delete ( delete_article ) ) ; แม้ว่าเส้นทางทั้งสองนี้จะมี path("articles") แต่ก็ยังสามารถเพิ่มลงในเส้นทางหลักเดียวกันได้ในเวลาเดียวกันดังนั้นเส้นทางสุดท้ายจึงเป็นแบบนี้:
Router :: new ( )
. push (
Router :: with_path ( "articles" )
. get ( list_articles )
. push ( Router :: with_path ( "<id>" ) . get ( show_article ) ) ,
)
. push (
Router :: with_path ( "articles" )
. hoop ( auth_check )
. push ( Router :: with_path ( "<id>" ) . patch ( edit_article ) . delete ( delete_article ) ) ,
) ; <id> จับคู่ส่วนในเส้นทางภายใต้สถานการณ์ปกติ id บทความเป็นเพียงตัวเลขซึ่งเราสามารถใช้นิพจน์ปกติเพื่อ จำกัด กฎการจับคู่ id , r"<id:/d+/>"
นอกจากนี้คุณยังสามารถใช้ <**> , <*+> หรือ <*?> เพื่อให้ตรงกับชิ้นส่วนเส้นทางที่เหลือทั้งหมด เพื่อให้รหัสอ่านได้มากขึ้นคุณยังสามารถเพิ่มชื่อที่เหมาะสมเพื่อให้ความหมายของพา ธ มีความชัดเจนมากขึ้นตัวอย่างเช่น: <**file_path>
การแสดงออกปกติสำหรับเส้นทางการจับคู่จะต้องใช้บ่อยและสามารถลงทะเบียนล่วงหน้าเช่น GUID:
PathFilter :: register_wisp_regex (
"guid" ,
Regex :: new ( "[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}" ) . unwrap ( ) ,
) ;สิ่งนี้ทำให้กระชับมากขึ้นเมื่อจำเป็นต้องมีการจับคู่เส้นทาง:
Router :: with_path ( "<id:guid>" ) . get ( index )ดูซอร์สโค้ดเต็มรูปแบบ
เราสามารถรับไฟล์ async โดย file ฟังก์ชันตาม Request :
# [ handler ]
async fn upload ( req : & mut Request , res : & mut Response ) {
let file = req . file ( "file" ) . await ;
if let Some ( file ) = file {
let dest = format ! ( "temp/{}" , file . name ( ) . unwrap_or_else ( || "file" . into ( ) ) ) ;
if let Err ( e ) = tokio :: fs :: copy ( & file . path , Path :: new ( & dest ) ) . await {
res . status_code ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
} else {
res . render ( "Ok" ) ;
}
} else {
res . status_code ( StatusCode :: BAD_REQUEST ) ;
}
}คุณสามารถรับข้อมูลจากแหล่งข้อมูลที่แตกต่างกันหลายแหล่งและรวบรวมไว้ในประเภทที่คุณต้องการ คุณสามารถกำหนดประเภทที่กำหนดเองก่อนตัวอย่างเช่น:
# [ derive ( Serialize , Deserialize , Extractible , Debug ) ]
/// Get the data field value from the body by default.
# [ salvo ( extract ( default_source ( from = "body" ) ) ) ]
struct GoodMan < ' a > {
/// The id number is obtained from the request path parameter, and the data is automatically parsed as i64 type.
# [ salvo ( extract ( source ( from = "param" ) ) ) ]
id : i64 ,
/// Reference types can be used to avoid memory copying.
username : & ' a str ,
first_name : String ,
last_name : String ,
} จากนั้นใน Handler คุณจะได้รับข้อมูลเช่นนี้:
# [ handler ]
async fn edit ( req : & mut Request ) {
let good_man : GoodMan < ' _ > = req . extract ( ) . await . unwrap ( ) ;
}คุณสามารถส่งผ่านประเภทโดยตรงไปยังฟังก์ชันเป็นพารามิเตอร์เช่นนี้:
# [ handler ]
async fn edit < ' a > ( good_man : GoodMan < ' a > ) {
res . render ( Json ( good_man ) ) ;
}ดูซอร์สโค้ดเต็มรูปแบบ
การสนับสนุนที่สมบูรณ์แบบสำหรับ OpenAPI สามารถทำได้โดยไม่ต้องทำการเปลี่ยนแปลงที่สำคัญในโครงการ
# [ derive ( Serialize , Deserialize , ToSchema , Debug ) ]
struct MyObject < T : ToSchema + std :: fmt :: Debug > {
value : T ,
}
# [ endpoint ]
async fn use_string ( body : JsonBody < MyObject < String > > ) -> String {
format ! ( "{:?}" , body )
}
# [ endpoint ]
async fn use_i32 ( body : JsonBody < MyObject < i32 > > ) -> String {
format ! ( "{:?}" , body )
}
# [ endpoint ]
async fn use_u64 ( body : JsonBody < MyObject < u64 > > ) -> String {
format ! ( "{:?}" , body )
}
# [ tokio :: main ]
async fn main ( ) {
tracing_subscriber :: fmt ( ) . init ( ) ;
let router = Router :: new ( )
. push ( Router :: with_path ( "i32" ) . post ( use_i32 ) )
. push ( Router :: with_path ( "u64" ) . post ( use_u64 ) )
. push ( Router :: with_path ( "string" ) . post ( use_string ) ) ;
let doc = OpenApi :: new ( "test api" , "0.0.1" ) . merge_router ( & router ) ;
let router = router
. push ( doc . into_router ( "/api-doc/openapi.json" ) )
. push ( SwaggerUi :: new ( "/api-doc/openapi.json" ) . into_router ( "swagger-ui" ) ) ;
let acceptor = TcpListener :: new ( "127.0.0.1:5800" ) . bind ( ) . await ;
Server :: new ( acceptor ) . serve ( router ) . await ;
}Salvo CLI เป็นเครื่องมือบรรทัดคำสั่งที่ทำให้การสร้างโครงการ Salvo ใหม่ง่ายขึ้นเทมเพลตสำหรับเว็บ API, เว็บไซต์, ฐานข้อมูล (รวมถึง SQLite, PostgreSQL และ MySQL ผ่าน SQLX, Seaorm, Diesel, Rbatis) คุณสามารถใช้ salvo-cli เพื่อสร้างโครงการ Salvo ใหม่:
cargo install salvo-clisalvo new project_nameคุณสามารถค้นหาตัวอย่างเพิ่มเติมในโฟลเดอร์ตัวอย่าง คุณสามารถเรียกใช้ตัวอย่างเหล่านี้ด้วยคำสั่งต่อไปนี้:
cd examples
cargo run --bin example-basic-auth คุณสามารถใช้ชื่อตัวอย่างที่คุณต้องการเรียกใช้แทน basic-auth ได้ที่นี่
ผลการทดสอบมาตรฐานสามารถพบได้จากที่นี่:
https://web-frameworks-benchmark.netlify.app/result?l=rust
https://www.techempower.com/benchmarks/#section=data-r22
Salvo เป็นโครงการโอเพ่นซอร์ส หากคุณต้องการสนับสนุน Salvo คุณสามารถ ซื้อกาแฟให้ฉันได้ที่นี่
Salvo ได้รับใบอนุญาตภายใต้
ใบอนุญาต Apache, เวอร์ชัน 2.0, (license-apache หรือ http://www.apache.org/licenses/license-2.0)
ใบอนุญาต MIT (ใบอนุญาต-MIT หรือ http://opensource.org/licenses/mit)