草庐IT

【一起学Rust | 框架篇 | Viz框架】轻量级 Web 框架——Viz

广龙宇 2023-04-04 原文

文章目录


前言

Viz,是个基于RUst的,快速、健壮、灵活、轻量级的 Web 框架。

特点

  • 安全,禁止不安全代码
  • 轻量
  • 简单 + 灵活的处理器和中间件
  • 链式操作
  • 强大的Routing路由

一、Hello Viz

1. 创建项目

正如学习编程语言一样,我们先从官方入门案例学起,首先我们创建一个新项目

cargo new viz_hello

然后使用vscode打开

2. 引入viz

Cargo.toml中写入,如下图

tokio = { version = "1.20.1", features = ["full"] }
viz = "0.3.1"

然后使用build来下载依赖

cargo build


安装完成

3. 运行Hello Viz

复制以下代码到main.rs

use std::net::SocketAddr;
use viz::{Request, Result, Router, Server, ServiceMaker};

async fn index(_: Request) -> Result<&'static str> {
    Ok("Hello Viz")
}

#[tokio::main]
async fn main() -> Result<()> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("listening on {}", addr);

    let app = Router::new().get("/", index);

    if let Err(err) = Server::bind(&addr)
        .serve(ServiceMaker::from(app))
        .await
    {
        println!("{}", err);
    }

    Ok(())
}

4. 运行结果

如果你以上步骤没有出错,那么在终端中运行

cargo run

效果如下图
最后一行的意思是正在监听本地的127.0.0.1的3000端口,说明程序没有出错

此时在浏览器打开网址

http://localhost:3000/

注意

localhost指向127.0.0.1

此时页面应该是这个样子的

二、Hello Viz代码详解


从整体上来看,这块代码主要分为3个部分,分别是导入组件,处理index请求和主程序

导入组件

首先导入了SocketAddr,用来表示socket地址,然后导入了Viz的一些组件

  • Request 请求
  • Result 响应
  • Router 路由
  • Server 服务器
  • ServiceMaker 服务

处理请求

这里使用异步函数来实现index的处理,传入Request,这个过程系统会自动为我们处理。然后响应的是字符串类型,在函数体中返回了字符串“Hello Viz”

主函数

在Viz中,主函数也是异步函数,使用addr表示本地地址和监听的端口,然后挂载Router,使与index处理器相联系,再开启服务器。

三、常见用法

简单的处理程序

async fn index(_: Request) -> Result<Response> {
    Ok(Response::text("Hello, World!"))
}

async fn about(_: Request) -> Result<&'static str> {
    Ok("About Me!")
}

async fn not_found(_: Request) -> Result<impl IntoResponse> {
    Ok("Not Found!")
}

实现处理程序特质

#[derive(Clone)]
struct MyHandler {
    code: Arc<AtomicUsize>,
}

#[async_trait]
impl Handler<Request> for MyHandler {
    type Output = Result<Response>;  

    async fn call(&self, req: Request) -> Self::Output {
        let path = req.path().clone();
        let method = req.method().clone();
        let code = self.code.fetch_add(1, Ordering::SeqCst);
        Ok(format!("code = {}, method = {}, path = {}", code, method, path).into_response())
    }
}

路由传参

Viz 允许更灵活地组织代码。

async fn show_user(mut req: Request) -> Result<Response> {
    let Params(id)  = req.extract::<Params<u64>>().await?;
    Ok(format!("post {}", id).into_response())
}

async fn show_user_ext(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("Hi, NO.{}", id))
}

async fn show_user_wrap(req: Request) -> Result<impl IntoResponse> {
    // https://github.com/rust-lang/rust/issues/48919
    // show_user_ext.call(req).await
    FnExt::call(&show_user_ext, req).await
}

let app = Router::new()
    .get("/users/:id", show_user)
    .get("/users_wrap/:id", show_user_wrap)
    .get("/users_ext/:id", show_user_ext.into_handler());

链式组合程序

HandlerExt是Handler的拓展特质,它提供了各种方便的组合函数。比如FutureExt和StreamExt特质。

async fn index(_: Request) -> Result<Response> {
    Ok(Response::text("hyper"))
}

async fn before(req: Request) -> Result<Request> {
    if req.method() == Method::POST {
        Ok(req)
    } else {
        Err(StatusCode::METHOD_NOT_ALLOWED.into_error())
    }
}

async fn around<H>((req, handler): Next<Request, H>) -> Result<Response>
where
    H: Handler<Request, Output = Result<Response>> + Clone,
{
    // before ...
    let result = handler.call(req).await;
    // after ...
    result
}

async fn after(result: Result<Response>) -> Result<Response> {
    result.map(|mut res| {
        *res.status_mut() = StatusCode::NO_CONTENT;
        res
    })
}

let routing = Router::new()
    .get("/", index.before(before).around(around).after(after));

中间件

Viz 的中间件和处理程序具有共同的Handler特质,因此它很容易实现和扩展中间件。

我们可以将中间件添加到单个处理程序或所有处理程序。

我们还可以在构造过程中使用Transform特质 trait 来包装内部处理程序。

async fn index(_: Request) -> Result<Response> {
    Ok(StatusCode::OK.into_response())
}

async fn not_found(_: Request) -> Result<impl IntoResponse> {
    Ok(StatusCode::OK)
}

async fn show_user(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("post {}", id))
}

// middleware fn
async fn around<H>((req, handler): Next<Request, H>) -> Result<Response>
where
    H: Handler<Request, Output = Result<Response>>,
{
    // before ...
    let result = handler.call(req).await;
    // after ...
    result
}

// middleware struct
#[derive(Clone)]
struct MyMiddleware {}

#[async_trait]
impl<H> Handler<Next<Request, H>> for MyMiddleware
where
    H: Handler<Request>,
{
    type Output = H::Output;

    async fn call(&self, (i, h): Next<Request, H>) -> Self::Output {
        h.call(i).await
    }
}

// A configuration for Timeout Middleware
struct Timeout {
    delay: Duration,
}

impl Timeout {
    pub fn new(secs: u64) -> Self {
        Self { delay: Duration::from_secs(secs) }
    }
}

impl<H: Clone> Transform<H> for Timeout {
    type Output = TimeoutMiddleware<H>;

    fn transform(&self, h: H) -> Self::Output {
        TimeoutMiddleware(h, self.delay)
    }
}

// Timeout Middleware
#[derive(Clone)]
struct TimeoutMiddleware<H>(H, Duration);

#[async_trait]
impl<H> Handler<Request> for TimeoutMiddleware<H>
where
    H: Handler<Request> + Clone,
{
    type Output = H::Output;

    async fn call(&self, req: Request) -> Self::Output {
        self.0.call(req).await
    }
}

let app = Router::new()
    .get("/", index
        // handler level
        .around(around)
        .around(MyMiddleware {})
        .with(Timeout::new(1))
    )
    .route("/users/:id", get(
        show_user
            .into_handler()
            .map_into_response()
            // handler level
            .around(around)
            .with(Timeout::new(0))
        )
        .post(
            (|_| async { Ok(Response::text("update")) })
            // handler level
            .around(around)
            .with(Timeout::new(0))
        )
        // route level
        .with_handler(MyMiddleware {})
        .with(Timeout::new(2))
    )
    .get("/*", not_found
        .map_into_response()
        // handler level
        .around(around)
        .around(MyMiddleware {})
    )
    // router level
    .with_handler(around)
    .with_handler(MyMiddleware {})
    .with(Timeout::new(4));

参数接收器

从Request中提取参数。

struct Counter(u16);

#[async_trait]
impl FromRequest for Counter {
    type Error = Infallible;
    async fn extract(req: &mut Request) -> Result<Self, Self::Error> {
        let c = get_query_param(req.query_string());
        Ok(Counter(c))
    }
}

fn get_query_param(query: Option<&str>) -> u16 {
   let query = query.unwrap_or("");
   let q = if let Some(pos) = query.find('q') {
       query.split_at(pos + 2).1.parse().unwrap_or(1)
   } else {
       1
   };
   cmp::min(500, cmp::max(1, q))
}

路由

识别URL和分配处理器。

一个简单的路由

async fn index(_: Request) -> Result<Response> {
    Ok(().into_response())
}

let root = Router::new()
  .get("/", index)
  .route("/about", get(|_| async { Ok("about") }));

let search = Router::new()
  .route("/", Route::new().get(|_| async { Ok("search") }));

CRUD操作

添加带请求方式的方法。

async fn index_todos(_: Request) -> Result<impl IntoResponse> {
    Ok(())
}

async fn create_todo(_: Request) -> Result<&'static str> {
    Ok("created")
}

async fn new_todo(_: Request) -> Result<Response> {
    Ok(Response::html(r#"
        <form method="post" action="/">
            <input name="todo" />
            <button type="submit">Create</button>
        </form>
    "#))
}

async fn show_todo(mut req: Request) -> Result<Response> {
    let Params(id): Params<u64> = req.extract().await?;
    Ok(Response::text(format!("todo's id is {}", id)))
}

async fn update_todo(_: Request) -> Result<()> {
    Ok(())
}

async fn destroy_todo(_: Request) -> Result<()> {
    Ok(())
}

async fn edit_todo(_: Request) -> Result<()> {
    Ok(())
}

let todos = Router::new()
  .route("/", get(index_todos).post(create_todo))
  .post("/new", new_todo)
  .route("/:id", get(show_todo).patch(update_todo).delete(destroy_todo))
  .get("/:id/edit", edit_todo);

资源

// GET `/search`
async fn search_users(_: Request) -> Result<Response> {
    Ok(Response::json::<Vec<u64>>(vec![])?)
}

// GET `/`
async fn index_users(_: Request) -> Result<Response> {
    Ok(Response::json::<Vec<u64>>(vec![])?)
}

// GET `/new`
async fn new_user(_: Request) -> Result<&'static str> {
    Ok("User Form")
}

// POST `/`
async fn create_user(_: Request) -> Result<&'static str> {
    Ok("Created User")
}

// GET `/user_id`
async fn show_user(_: Request) -> Result<&'static str> {
    Ok("User ID 007")
}

// GET `/user_id/edit`
async fn edit_user(_: Request) -> Result<&'static str> {
    Ok("Edit User Form")
}

// PUT `/user_id`
async fn update_user(_: Request) -> Result<&'static str> {
    Ok("Updated User")
}

// DELETE `/user_id`
async fn delete_user(_: Request) -> Result<&'static str> {
    Ok("Deleted User")
}

let users = Resources::default()
  .named("user")
  .route("/search", get(search_users))
  .index(index_users)
  .new(new_user)
  .create(create_user)
  .show(show_user)
  .edit(edit_user)
  .update(update_user)
  .destroy(delete_user);

总结

本期主要是对Rust的轻量级Web框架Viz进行了入门级的了解,并且给出了Viz官方的示例代码,包括中间件,响应处理,路由等组件的用法,可以看出Viz是个纯web框架,非常的简洁。在后续的文章中,将会陆续为大家介绍rust的数据库操作,json操作等相关技术,rust做web后端的相关技术补齐就开始项目实战。如果你对rust感兴趣,请关注本系列文章。

有关【一起学Rust | 框架篇 | Viz框架】轻量级 Web 框架——Viz的更多相关文章

  1. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  2. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

  3. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  4. ruby-on-rails - 如何让 datamapper 与 postgresql 数据库一起工作? - 2

    我已经找到了几个使用datamapper的示例,并且能够让它们正常工作。不过,所有这些示例都是针对sqlite数据库的。我正在尝试将数据映射器与postgresql一起使用。我将datamapper中的调用从sqlite3更改为postgres,并且我已经安装了dm-postgres-adapter。但它仍然不起作用。我还需要做什么? 最佳答案 与SQLite不同,PostgreSQL不将数据库存储在单个文件中。在你拥有createdyourdatabase之后,尝试这样的事情:DataMapper.setup:default,{:

  5. ruby - 如何配置 Ruby Mechanize 代理以通过 Charles Web 代理工作? - 2

    我正在使用Ruby/Mechanize编写一个“自动填写表格”应用程序。它几乎可以工作。我可以使用精彩CharlesWeb代理以查看服务器和我的Firefox浏览器之间的交换。现在我想使用Charles查看服务器和我的应用程序之间的交换。Charles在端口8888上代理。假设服务器位于https://my.host.com。.一件不起作用的事情是:@agent||=Mechanize.newdo|agent|agent.set_proxy("my.host.com",8888)end这会导致Net::HTTP::Persistent::Error:...lib/net/http/pe

  6. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

  7. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  8. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

  9. ruby-on-rails - 将 acts_as_list 与 has_many 一起使用 :through in rails - 2

    我有一个Rails应用程序,我正在尝试使用acts_as_list插件设置可排序列表。数据库中的位置字段正在更新,但是在呈现页面时,不考虑顺序。我想我是在寻求帮助。这是我的模型...classQuestionMembership:question_membershipsendclassQuestion:question_membershipsacts_as_listend还有给我列表的草率View代码...>true)%>拖放用于重新排序。数据库中QuestionMembership对象的位置值更新,页面实际上正确显示重新排序。问题是在页面重新加载时,它默认返回到它感觉的任何顺序。我认

  10. ruby-on-rails - 正确了解 Rails 框架的最佳方式是什么? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。我一直在Rails上做两个项目,它们运行良好,但在这个过程中重新发明了轮子,自来水(和热水)和止痛药,正如我随后了解到的那样,这些已经存在于框架中。那么基本上,正确了解框架中所有智能部分的最佳方法是什么,这将节省时间而不是自己构建已经实现的功能?从第1页开始阅读文档?是否有公开所有内容的特定示例应用程序?一个特定的开源项目?所有的rails交通?还是完全

随机推荐