Expand description
Get Started
There is only one dependency.
cargo add rupringAnd you can write your server like this:
#[derive(Debug, Clone, Copy)]
#[rupring::Module(controllers=[HomeController{}], modules=[])]
pub struct RootModule {}
#[derive(Debug, Clone)]
#[rupring::Controller(prefix=/, routes=[hello, echo])]
pub struct HomeController {}
#[rupring::Get(path = /)]
pub fn hello(_request: rupring::Request) -> rupring::Response {
rupring::Response::new().text("Hello, World!".to_string())
}
#[rupring::Get(path = /echo)]
pub fn echo(request: rupring::Request) -> rupring::Response {
rupring::Response::new().text(request.body)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let app = rupring::RupringFactory::create(RootModule {});
app.listen(3000).await?;
Ok(())
}
Response
You can create a response like this:
#[rupring::Get(path = /)]
pub fn hello(_request: rupring::Request) -> rupring::Response {
rupring::Response::new().text("Hello, World!".to_string())
}You can also return a json value like this:
#[derive(serde::Serialize)]
struct User {
name: String,
}
#[rupring::Get(path = /user)]
pub fn get_user(_request: rupring::Request) -> rupring::Response {
rupring::Response::new().json(User {
name: "John".to_string(),
})
}You can set the status code like this:
#[rupring::Get(path = /asdf)]
pub fn not_found(_request: rupring::Request) -> rupring::Response {
rupring::Response::new().text("not found".to_string()).status(404)
}You can set the header like this:
#[rupring::Get(path = /)]
pub fn hello(_request: rupring::Request) -> rupring::Response {
rupring::Response::new()
.text("Hello, World!".to_string())
.header("content-type", "text/plain".to_string())
}If you want, you can receive it as a parameter instead of creating the response directly.
#[rupring::Get(path = /)]
pub fn hello(_request: rupring::Request, response: rupring::Response) -> rupring::Response {
response
.text("Hello, World!".to_string())
.header("content-type", "text/plain".to_string())
}This is especially useful when you need to inherit and use Response through middleware.
If you want to redirect, you can use Response’s redirect method.
#[rupring::Get(path = /)]
pub fn hello(_request: rupring::Request) -> rupring::Response {
rupring::Response::new().redirect("/hello")
}This method automatically sets status to 302 unless you set it to 300-308.
Middleware
rupring provides middleware features for common logic processing.
If you want to log requests for all APIs that exist in a module, you can apply middleware in the form below.
First, define a middleware function.
pub fn logger_middleware(
request: rupring::Request,
response: rupring::Response,
next: NextFunction,
) -> rupring::Response {
println!(
"Request: {} {}",
request.method.to_string(),
request.path.to_string()
);
next(request, response)
}The above function only records logs and forwards them to the next middleware or route function. If you want to return a response immediately without forwarding, just return the response without calling the next function.
And you can register the middleware function just defined in the module or controller unit.
#[derive(Debug, Clone, Copy)]
#[rupring::Module(
controllers=[HomeController{}],
modules=[UserModule{}],
providers=[],
middlewares=[logger_middleware]
)]
pub struct RootModule {}
// or Controller
#[derive(Debug, Clone)]
#[rupring::Controller(prefix=/, routes=[get_user], middlewares=[logger_middleware])]
pub struct UserController {}Middleware registered in a module is recursively applied to the routes of controllers registered in that module and to child modules. On the other hand, middleware registered in a controller applies only to the routes of that controller.
The priorities in which middleware is applied are as follows:
- Middleware of the same unit is executed in the order defined in the array.
- If module middleware and controller middleware exist at the same time, module middleware is executed first.
- If the parent module’s middleware and the child module’s middleware exist at the same time, the parent module middleware is executed first.
Dependency Injection
rupring provides DI feature.
If you want to implement and DI a Provider that contains simple functionality, you can do it like this: First, define the Provider.
#[derive(Debug, Clone, Default)]
pub struct HomeService {}
impl HomeService {
pub fn hello(&self) -> String {
"hello!!".to_string()
}
}
impl rupring::IProvider for HomeService {
fn provide(&self, di_context: &rupring::DIContext) -> Box<dyn std::any::Any> {
Box::new(HomeService {})
}
}Second, add it as a dependency to the module you want to use.
#[derive(Debug, Clone, Copy)]
#[rupring::Module(controllers=[HomeController{}], modules=[], providers=[HomeService::default()])]
pub struct RootModule {}And, you can use it by getting it from the router through the request object.
#[rupring::Get(path = /)]
pub fn hello(request: rupring::Request) -> rupring::Response {
let home_service = request.get_provider::<HomeService>().unwrap();
rupring::Response::new().text(home_service.hello())
}If a provider requires another provider, you must specify the dependency cycle as follows:
impl rupring::IProvider for HomeService {
fn dependencies(&self) -> Vec<TypeId> {
vec![TypeId::of::<HomeRepository>()]
}
fn provide(&self, di_context: &rupring::DIContext) -> Box<dyn std::any::Any> {
Box::new(HomeService {
home_repository: di_context.get::<HomeRepository>().unwrap().to_owned(),
})
}
}If you need mutables within the provider, you must ensure thread safety through Mutex or Atomic as follows:
#[derive(Debug, Clone, Default)]
pub struct CounterService {
counter: Arc<Mutex<i32>>,
}
impl CounterService {
pub fn new() -> Self {
CounterService {
counter: Arc::new(Mutex::new(0)),
}
}
pub fn increment(&self) {
let mut counter = self.counter.lock().unwrap();
*counter += 1;
}
pub fn get(&self) -> i32 {
let counter = self.counter.lock().unwrap();
*counter
}
}If you need to abstract based on a trait, you need to box it twice as follows:
pub trait IUserService {
fn get_user(&self) -> String;
}
#[derive(Debug, Clone, Default)]
pub struct UserService {}
impl IUserService for UserService {
fn get_user(&self) -> String {
"user".to_string()
}
}
impl rupring::IProvider for UserService {
fn dependencies(&self) -> Vec<TypeId> {
vec![]
}
fn provide(&self, _di_context: &rupring::DIContext) -> Box<dyn std::any::Any> {
let service: Box<dyn IUserService> = Box::new(UserService::default());
return Box::new(service);
}
}
// ...
#[rupring::Get(path = /user)]
pub fn get_user(request: rupring::Request) -> rupring::Response {
let user_service = request.get_provider::<Box<dyn IUserService>>().unwrap();
rupring::Response::new().text(user_service.get_user())
}Structs
- Dependency Injection Context for entire life cycle
- HTTP Request
- HTTP Response
- Rupring Factory for creating server
Traits
- Controller interface
- Handler interface
- Module interface
- Dependency Injection Provider
- Route interface
Type Aliases
- HTTP Header Name (from hyper crate)
- HTTP method (from hyper crate)
- Next function type for middleware