Sapper is a lightweight web mvc framework, easy for use. Like python falcon/flask.
Sapper is based on Hyper 0.10.13, now using sync net mode, when async/await are ready, Sapper will follow it to walk to async framework.
Sapper consists of:
- Sapper: Main repository, exported some important traits and structs. It is used to router, parse parameters, error handling and so on. It also supplies a simple static file server. This repository can work without following helper repositories, but not very easy;
- Sapper_std: Wrappers for some basic components, such as parsing Query/Form/JsonBody, supplying some convenient macros to help rapid business coding;
Generally speaking, in project, it is workable only using above two creates, but if you want to custom your flow, you can use following seperately:
The following crates are all dependant by Sapper_std and been exported.
-
Sapper_session offers cookie setting and session parsing function;
-
Sapper_logger offers a basic log function, format as:
[2017-12-09 12:20:12] GET /ip/view Some("limit=25&offset=0") -> 200 OK (0.700839 ms)
-
Sapper_tmpl using tera to render page;
-
Sapper_query parsing url query string;
-
Sapper_body parsing http body url encoded form data, or json data;
The biggest feature of Sapper is that: it divide business logic things into three levels - global, module, and in handler. You can define global shared variables and global shell (middlewares) and module router and middlewares, and handler middlewares.
I will try my best to contain all features of sapper into this demo, but not all. Sapper demo.
$ cargo new sapper_demo
add dependencies to cargo.toml.
[dependencies]
sapper = "^0.1"
sapper_std = "^0.1"
serde="*"
serde_json="*"
serde_derive="*"
A full Sapper project has these directories. Static files are all in static/, such as js/css/images, all html page templates are in views/.
|-- src
| |-- bin
| | |-- main.rs
| |-- lib.rs
| |-- bar.rs
| |-- foo.rs
|-- static
|-- views
|-- Cargo.lock
|-- Cargo.toml
add the following lines to lib.rs:
extern crate sapper;
#[macro_use]
extern crate sapper_std;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
pub mod foo;
pub mod bar;
pub use foo::Foo;
pub use bar::{ Bar, Global };let's write codes in main.rs:
extern crate sapper;
extern crate sapper_std;
extern crate sapper_demo;
use sapper::{ SapperApp, SapperAppShell, Request, Response, Result as SapperResult };
use sapper_demo::{ Foo, Bar, Global };
use std::sync::Arc;
struct WebApp;
impl SapperAppShell for WebApp {
fn before(&self, req: &mut Request) -> SapperResult<()> {
sapper_std::init(req, Some("session"))?;
Ok(())
}
fn after(&self, req: &Request, res: &mut Response) -> SapperResult<()> {
sapper_std::finish(req, res)?;
Ok(())
}
}
fn main() {
let global = Arc::new(String::from("global variable"));
let mut app = SapperApp::new();
app.address("127.0.0.1")
.port(8080)
.init_global(
Box::new(move |req: &mut Request| {
req.ext_mut().insert::<Global>(global.clone());
Ok(())
})
)
.with_shell(Box::new(WebApp))
.add_module(Box::new(Foo))
.add_module(Box::new(Bar))
.static_service(true)
.not_found_page(String::from("not found"));
println!("Start listen on {}", "127.0.0.1:8080");
app.run_http();
}Now, the above code can't work, we havn't define Foo and Bar now. We explain above code first:
SapperApp is one of the core structure of Sapper, all sapper project run around it. Let's introduce it:
-
fn init_global()we register global shared variables (e.g. database connecting pool object) in this method; -
fn with_shell()we register global middlewares in this shell; -
fn add_moudle()register sub module, one module one time; -
fn static_server()open static file service, if parameter istrue, otherwise (false) close it; -
fn not_found_page()default is None, you can use this method to return custom 404 page.
add lines to foo.rs
use sapper::{ SapperModule, SapperRouter, Response, Request, Result as SapperResult };
use sapper_std::{ QueryParams, PathParams, FormParams, JsonParams, Context, render };
use serde_json;
pub struct Foo;
impl Foo {
fn index(_req: &mut Request) -> SapperResult<Response> {
let mut web = Context::new();
web.add("data", &"Foo 模块");
res_html!("index.html", web)
}
// parse `/query?query=1`
fn query(req: &mut Request) -> SapperResult<Response> {
let params = get_query_params!(req);
let query = t_param_parse!(params, "query", i64);
let mut web = Context::new();
web.add("data", &query);
res_html!("index.html", web)
}
// parse `/user/:id`
fn get_user(req: &mut Request) -> SapperResult<Response> {
let params = get_path_params!(req);
let id = t_param!(params, "id").clone();
println!("{}", id);
let json2ret = json!({
"id": id
});
res_json!(json2ret)
}
// parse body json
fn post_json(req: &mut Request) -> SapperResult<Response> {
#[derive(Serialize, Deserialize, Debug)]
struct Person {
foo: String,
bar: String,
num: i32,
}
let person: Person = get_json_params!(req);
println!("{:#?}", person);
let json2ret = json!({
"status": true
});
res_json!(json2ret)
}
// parse form
fn test_post(req: &mut Request) -> SapperResult<Response> {
let params = get_form_params!(req);
let foo = t_param!(params, "foo");
let bar = t_param!(params, "bar");
let num = t_param_parse!(params, "num", i32);
println!("{}, {}, {}", foo, bar, num);
let json2ret = json!({
"status": true
});
res_json!(json2ret)
}
}
impl SapperModule for Foo {
fn before(&self, _req: &mut Request) -> SapperResult<()> {
Ok(())
}
fn after(&self, _req: &Request, _res: &mut Response) -> SapperResult<()> {
Ok(())
}
fn router(&self, router: &mut SapperRouter) -> SapperResult<()> {
router.get("/foo", Foo::index);
router.get("/query", Foo::query);
router.get("/user/:id", Foo::get_user);
router.post("/test_post", Foo::test_post);
router.post("/post_json", Foo::post_json);
Ok(())
}
}add lines to bar.rs
use sapper::{ SapperModule, SapperRouter, Response, Request, Result as SapperResult, Key, Error as SapperError };
use std::sync::Arc;
use sapper::header::ContentType;
pub struct Bar;
impl Bar {
fn index(_req: &mut Request) -> SapperResult<Response> {
let mut res = Response::new();
res.headers_mut().set(ContentType::html());
res.write_body(String::from("bar"));
Ok(res)
}
}
impl SapperModule for Bar {
fn before(&self, req: &mut Request) -> SapperResult<()> {
let global = req.ext().get::<Global>().unwrap().to_string();
let res = json!({
"error": global
});
Err(SapperError::CustomJson(res.to_string()))
}
fn after(&self, _req: &Request, _res: &mut Response) -> SapperResult<()> {
Ok(())
}
fn router(&self, router: &mut SapperRouter) -> SapperResult<()> {
router.get("/bar", Bar::index);
Ok(())
}
}
pub struct Global;
impl Key for Global {
type Value = Arc<String>;
}add new file index.html to views:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/foo.css" rel="stylesheet"/>
<title>index</title>
</head>
<body>
<p>{{ data }}</p>
</body>
</html>and new css file foo.css in static/
p {
color: red;
text-align: center;
}Now, this demo can be run by cargo run, it will listen 127.0.0.1:8080, we can test it with curl.
Foo module contains 5 routers, to demonstrate web page rendering, query string parsing, path parameter parsing, json string parsing, form body parsing.
Bar module defines a middleware, used to return error directly, visiting 127.0.0.1:8080/bar will cause middleware capture, returning that global.
https://github.com/sappworks/sapper_examples/tree/master/sapper_demo
The whole flow is: ** request -> global before -> module before -> handler -> module after -> global after -> response **, during this procedure, if error occures, it will break and do response directly.
Normally, the middleware of sapper will return Ok(()), meaning continuation. If middleware return Err(Error instance), it will break and do response immediately. The Error enum is as follow:
pub enum Error {
InvalidConfig,
InvalidRouterConfig,
FileNotExist,
NotFound,
Break, // 400
Unauthorized, // 401
Forbidden, // 403
TemporaryRedirect(String), // 307
Custom(String),
CustomHtml(String),
CustomJson(String),
}TemporaryRedirect is used to redirect, Custom, CustomHtml, CustomJson are used to custom return values.
- [Forustm] (https://github.com/rustcc/forustm)
- [MyBlog] (https://github.com/driftluo/MyBlog)
Welcome to Sapper Community, welcome PRs. Thank you.