Skip to content

Latest commit

 

History

History
366 lines (274 loc) · 10 KB

Tutorial-en.md

File metadata and controls

366 lines (274 loc) · 10 KB

Tutorial

Preface

Sapper is a lightweight web mvc framework, easy for use. Like python falcon/flask.

Introduction

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.

[2017-12-09 12:20:12]  GET /ip/view Some("limit=25&offset=0") -> 200 OK (0.700839 ms)

Characteristics

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.

Start

I will try my best to contain all features of sapper into this demo, but not all. Sapper demo.

Create Project

$ 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 };

bin

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 is true, otherwise (false) close it;

  • fn not_found_page() default is None, you can use this method to return custom 404 page.

Foo and Bar

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;
}

Explaination

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

Foo module contains 5 routers, to demonstrate web page rendering, query string parsing, path parameter parsing, json string parsing, form body parsing.

Bar module

Bar module defines a middleware, used to return error directly, visiting 127.0.0.1:8080/bar will cause middleware capture, returning that global.

demo source code

https://github.com/sappworks/sapper_examples/tree/master/sapper_demo

Middleware and Error Handling

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.

Online Projects

Contribute

Welcome to Sapper Community, welcome PRs. Thank you.