Inglés 简体中文 繁體中文



Salvo es un marco de backend web de óxido extremadamente simple y potente. Solo se requiere un conocimiento básico de óxido para desarrollar servicios de backend.
Puede ver muestras aquí o ver el sitio web oficial.
Solo se necesitan unas pocas líneas de código para implementar un servidor que admita ACME para obtener automáticamente certificados, y admite los protocolos HTTP1, HTTP2 y 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 ;
}No hay diferencia entre un controlador y un middleware, un middleware es solo un controlador. Puede escribir middleware sin conocer conceptos como tipos asociados y tipos genéricos. Si puede escribir una función, ¡puede escribir middleware!
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" ) ) ;
}Luego agrégalo al enrutador:
Router :: new ( ) . hoop ( add_header ) . get ( hello ) Este es un middleware muy simple, agrega un Header a la Response , vea el código fuente completo.
Normalmente escribimos enrutamiento como este:
Router :: with_path ( "articles" ) . get ( list_articles ) . post ( create_article ) ;
Router :: with_path ( "articles/<id>" )
. get ( show_article )
. patch ( edit_article )
. delete ( delete_article ) ;A menudo, algo como ver artículos y listas de artículos no requiere inicio de sesión del usuario, sino crear, editar, eliminar artículos, etc. requieren permisos de autenticación de inicio de sesión del usuario. El sistema de enrutamiento similar al árbol en Salvo puede satisfacer esta demanda. Podemos escribir enrutadores sin inicio de sesión del usuario juntos:
Router :: with_path ( "articles" )
. get ( list_articles )
. push ( Router :: with_path ( "<id>" ) . get ( show_article ) ) ;Luego escriba los enrutadores que requieren que el usuario inicie sesión juntos y use el middleware correspondiente para verificar si el usuario ha iniciado sesión:
Router :: with_path ( "articles" )
. hoop ( auth_check )
. push ( Router :: with_path ( "<id>" ) . patch ( edit_article ) . delete ( delete_article ) ) ; Aunque estas dos rutas tienen la misma path("articles") , aún se pueden agregar a la misma ruta principal al mismo tiempo, por lo que la ruta final se ve así:
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> coincide con un fragmento en la ruta, en circunstancias normales, la id del artículo es solo un número, que podemos usar expresiones regulares para restringir las reglas de coincidencia id , r"<id:/d+/>" .
También puede usar <**> , <*+> o <*?> Para que coincida con todos los fragmentos de ruta restantes. Para que el código sea más legible, también puede agregar el nombre apropiado para que la semántica de la ruta sea más clara, por ejemplo: <**file_path> .
Algunas expresiones regulares para las rutas coincidentes deben usarse con frecuencia, y se pueden registrar de antemano, como 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 ( ) ,
) ;Esto lo hace más conciso cuando se requiere una coincidencia de ruta:
Router :: with_path ( "<id:guid>" ) . get ( index )Ver código fuente completo
Podemos obtener el archivo async por el file de función en 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 ) ;
}
}Puede obtener fácilmente datos de varias fuentes de datos diferentes y ensamblarlos en el tipo que desea. Puede definir primero un tipo personalizado, por ejemplo:
# [ 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 ,
} Luego, en Handler puede obtener los datos como este:
# [ handler ]
async fn edit ( req : & mut Request ) {
let good_man : GoodMan < ' _ > = req . extract ( ) . await . unwrap ( ) ;
}Incluso puede pasar el tipo directamente a la función como un parámetro, como este:
# [ handler ]
async fn edit < ' a > ( good_man : GoodMan < ' a > ) {
res . render ( Json ( good_man ) ) ;
}Ver código fuente completo
Se puede lograr un soporte perfecto para OpenAPI sin hacer cambios significativos en el proyecto.
# [ 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 es una herramienta de línea de comandos que simplifica la creación de nuevos proyectos de Salvo, que admite plantillas para API web, sitios web, bases de datos (incluidos SQLite, PostgreSQL y MySQL a través de SQLX, Seorm, diesel, RBatis) y Middleware básico. Puede usar Salvo-Cli para crear un nuevo proyecto de Salvo:
cargo install salvo-clisalvo new project_namePuede encontrar más ejemplos en la carpeta de ejemplos. Puede ejecutar estos ejemplos con el siguiente comando:
cd examples
cargo run --bin example-basic-auth Puede usar cualquier nombre de ejemplo que desee ejecutar en lugar de basic-auth aquí.
El resultado de la prueba de referencia se puede encontrar desde aquí:
https://webframeworks-benchmark.netlify.app/result?l=rust
https://www.techempower.com/benchmarks/#section=data-r22
Salvo es un proyecto de código abierto. Si quieres apoyar a Salvo, puedes comprarme un café aquí .
Salvo tiene licencia bajo cualquiera de
Licencia Apache, versión 2.0, (licencia-apache o http://www.apache.org/licenses/license-2.0).
Licencia MIT (licencia-mit o http://opensource.org/licenses/mit).