简介

rust官方推出的基于async-std的异步web框架,内部使用了http服务端和客户端抽象库http_types,目前最新版本为v0.11.0,目前还不太成熟,比如缺少文档、缺少stream流式编程风格(master版本已经支持此特性)、缺少像rocket或actix-web那样的路由宏定义(如#[get("/hello")]).

快速开始

  1. 创建项目
cargo new tide-demo
  1. 在cargo.toml中添加依赖
[dependencies]
tide = "0.11.0"
async-std = { version = "1.6.0", features = ["attributes"]
  1. 修改main.js
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    let mut app = tide::new();
    app.at("/").get(|_| async { Ok("Hello, world!") });
    app.listen("127.0.0.1:8080").await?;
    Ok(())
}
  1. 启动
cargo run
  1. 访问
    打开浏览器访问http://localhost:8080/

Request和Response

body json数据

content-type=application/json

use async_std::task;
use serde::{Deserialize, Serialize};
use tide::prelude::*;
use tide::{Body, Request, Response};

#[derive(Deserialize, Serialize)]
struct Cat {
    name: String,
}

fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();

        app.at("/submit").post(|mut req: Request<()>| async move {
            let cat: Cat = req.body_json().await?;
            println!("cat name: {}", cat.name);

            let cat = Cat {
                name: "chashu".into(),
            };

            let mut res = Response::new(200);
            res.set_body(Body::from_json(&cat)?);
            Ok(res)
        });

        app.at("/animals").get(|_| async {
            Ok(json!({
                "meta": { "count": 2 },
                "animals": [
                    { "type": "cat", "name": "chashu" },
                    { "type": "cat", "name": "nori" }
                ]
            }))
        });

        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

body string数据

content-type=text/plain;charset=utf-8

use async_std::task;
use tide::{ Request};

fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();
        app.at("/").post(|mut req: Request<()>| async move {
            let body: String = req.body_string().await.unwrap();
            println!("body: {}", body);
            Ok("")
        });

        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

从path中获取参数

use async_std::task;
use tide::prelude::*;
use tide::{Body, Request, Response};

fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();

        app.at("/:name").get(|mut req: Request<()>| async move {
            let name = req.param("name").unwrap();
            println!(" name: {}", name);

            let mut res = Response::new(200);
            res.set_body(Body::from_string(name));
            Ok(res)
        });

        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

从query中获取参数

use async_std::task;
use tide::prelude::*;
use tide::{Body, Request, Response};
#[derive(Serialize,Deserialize)]
struct Test {
    name: String,
    age: u32,
}
fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();
        app.at("/").get(|mut req: Request<()>| async move {
            let test:Test= req.query().unwrap();
            println!(" name: {}", test.name);
            println!(" age: {}", test.age);
            let mut res = Response::new(200);
            res.set_body(Body::from_json(&test)?);
            Ok(res)
        });

        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

从form中获取参数

content-type=application/x-www-form-urlencoded

use async_std::task;
use tide::prelude::*;
use tide::{Body, Request, Response};
#[derive(Serialize,Deserialize)]
struct Test {
    name: String,
    age: u32,
}
fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();
        app.at("/").get(|mut req: Request<()>| async move {
            let test:Test= req.query().unwrap();
            println!(" name: {}", test.name);
            println!(" age: {}", test.age);
            let mut res = Response::new(200);
            res.set_body(Body::from_json(&test)?);
            Ok(res)
        });

        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

状态码设置

use async_std::task;
use tide::{StatusCode, Request,Response};

fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();
        app.at("/").get(|mut req: Request<()>| async move {
            let mut res = Response::new(StatusCode::Ok);
            //let mut res = Response::new(200);
            res.set_body("Hello, Nori!");
            Ok(res)
        });
        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

返回内容type设置

use async_std::task;
use tide::{StatusCode, Request,Response};
use http_types::Mime;
use std::str::FromStr;

fn main() -> tide::Result<()> {
    task::block_on(async {
        let mut app = tide::new();
        app.at("/").get(|mut req: Request<()>| async move {
            let mut res = Response::new(StatusCode::Ok);
            let mime = Mime::from_str("text/html;charset=utf-8").unwrap();
            res.set_content_type(mime);
            res.set_body("<h1>Hello, Nori!</h1>");
            Ok(res)
        });
        app.listen("127.0.0.1:8080").await?;
        Ok(())
    })
}

日志

use tide::log;

log::start();

log::info!("Hello cats");
log::debug!("{} wants tuna", "Nori");
log::error!("We're out of tuna!");
log::info!("{} are hungry", "cats", {
    cat_1: "Chashu",
    cat_2: "Nori",
});

静态文件

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    tide::log::start();
    let mut app = tide::new();
    app.at("/").get(|_| async move { Ok("visit /src/*") });
    app.at("/src").serve_dir("src/")?;
    app.listen("127.0.0.1:8080").await?;
    Ok(())
}

重定向

use tide::{http::StatusCode, Redirect, Response};

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    let mut app = tide::new();
    app.at("/").get(|_| async { Ok("Root") });

    // Redirect hackers to YouTube.
    app.at("/.env")
        .get(Redirect::new("https://www.youtube.com/watch?v=dQw4w9WgXcQ"));

    app.at("/users-page").get(|_| async {
        Ok(if signed_in() {
            Response::new(StatusCode::Ok)
        } else {
            // If the user is not signed in then lets redirect them to home page.
            Redirect::new("/").into()
        })
    });

    app.listen("127.0.0.1:8080").await?;
    Ok(())
}

fn signed_in() -> bool {
    false
}

middleware中间件

和nodejs里的koa 中间件类似

use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tide::http::mime;
use tide::{After, Before, Middleware, Next, Request, Response, Result, StatusCode};

#[derive(Debug)]
struct User {
    name: String,
}

#[derive(Default, Debug)]
struct UserDatabase;
impl UserDatabase {
    async fn find_user(&self) -> Option<User> {
        Some(User {
            name: "nori".into(),
        })
    }
}

// This is an example of a function middleware that uses the
// application state. Because it depends on a specific request state,
// it would likely be closely tied to a specific application
fn user_loader<'a>(
    mut request: Request<UserDatabase>,
    next: Next<'a, UserDatabase>,
) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>> {
    Box::pin(async {
        if let Some(user) = request.state().find_user().await {
            tide::log::trace!("user loaded", {user: user.name});
            request.set_ext(user);
            next.run(request).await
        // this middleware only needs to run before the endpoint, so
        // it just passes through the result of Next
        } else {
            // do not run endpoints, we could not find a user
            Ok(Response::new(StatusCode::Unauthorized))
        }
    })
}

//
//
// this is an example of middleware that keeps its own state and could
// be provided as a third party crate
#[derive(Default)]
struct RequestCounterMiddleware {
    requests_counted: Arc<AtomicUsize>,
}

impl RequestCounterMiddleware {
    fn new(start: usize) -> Self {
        Self {
            requests_counted: Arc::new(AtomicUsize::new(start)),
        }
    }
}

struct RequestCount(usize);

impl<State: Send + Sync + 'static> Middleware<State> for RequestCounterMiddleware {
    fn handle<'a>(
        &'a self,
        mut req: Request<State>,
        next: Next<'a, State>,
    ) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>> {
        Box::pin(async move {
            let count = self.requests_counted.fetch_add(1, Ordering::Relaxed);
            tide::log::trace!("request counter", { count: count });
            req.set_ext(RequestCount(count));

            let mut res = next.run(req).await?;

            res.insert_header("request-number", count.to_string());
            Ok(res)
        })
    }
}

const NOT_FOUND_HTML_PAGE: &str = "<html><body>
  <h1>uh oh, we couldn't find that document</h1>
  <p>
    probably, this would be served from the file system or
    included with `include_bytes!`
  </p>
</body></html>";

const INTERNAL_SERVER_ERROR_HTML_PAGE: &str = "<html><body>
  <h1>whoops! it's not you, it's us</h1>
  <p>
    we're very sorry, but something seems to have gone wrong on our end
  </p>
</body></html>";

#[async_std::main]
async fn main() -> Result<()> {
    tide::log::start();
    let mut app = tide::with_state(UserDatabase::default());

    app.middleware(After(|result: Result| async move {
        let response = result.unwrap_or_else(|e| Response::new(e.status()));
        match response.status() {
            StatusCode::NotFound => {
                let mut res = Response::new(404);
                res.set_content_type(mime::HTML);
                res.set_body(NOT_FOUND_HTML_PAGE);
                Ok(res)
            }
            StatusCode::InternalServerError => {
                let mut res = Response::new(500);
                res.set_content_type(mime::HTML);
                res.set_body(INTERNAL_SERVER_ERROR_HTML_PAGE);
                Ok(res)
            }
            _ => Ok(response),
        }
    }));

    app.middleware(user_loader);
    app.middleware(RequestCounterMiddleware::new(0));
    app.middleware(Before(|mut request: Request<UserDatabase>| async move {
        request.set_ext(std::time::Instant::now());
        request
    }));

    app.at("/").get(|req: Request<_>| async move {
        let count: &RequestCount = req.ext().unwrap();
        let user: &User = req.ext().unwrap();

        Ok(format!(
            "Hello {}, this was request number {}!",
            user.name, count.0
        ))
    });

    app.listen("127.0.0.1:8080").await?;
    Ok(())
}