【RUST】Tide框架 (1)–公开构建模块化web框架 – buddyCoder

原文链接: https://rustasync.github.io/team/2018/09/11/tide.html
全文用有道翻译进行翻译,部分经过自己理解进行修改。

Rust 网络服务工作组今年的目标是在几个方面改进web开发:通过支持async/ await之类的基础,通过改进与web相关的crates的生态系统,以及通过将这些部分整合到一个名为Tide的框架和书籍中来改进web开发。“Tide”一词是指“水涨船高”;其目的是在Rust中改进所有web开发和框架之间的共享、兼容性和改进。

web框架的角色

简而言之,web框架的目标是让您以一种感觉“原生”于宿主语言的方式开发web应用程序。这意味着什么是一个有争议的问题,大多数语言都有多个流行的web框架采用不同的方法。

无论如何,核心问题是解决各种不匹配:

  • web应用程序最终需要使用HTTP,但是程序员通常更愿意用纯粹的本地术语来考虑核心“业务逻辑”。在最简单的层次上,您希望您的核心逻辑直接处理整数和结构体之类的东西,而不是原始字符串数据。但这种划分也适用于身份验证、解析url,甚至是如何表示应用程序提供的服务集。
  • 上述分解的一个特别重要的方面是处理序列化and/or模板:原始请求/响应主体中出现的数据类型是什么,以及它如何连接到Rust类型?
  • 许多web应用程序也与数据库一起工作,在“本机”视图和以数据库为中心的视图之间存在另一组不匹配。像Diesel这样的对象关系映射是解决这个问题的一种方法。

除了解决这些不匹配之外,web框架有时还有更远大的目标,比如:

  • 默认情况下安全性很好。
  • 一致的应用程序结构,使导航使用框架的外部代码库变得容易。
  • 其他关注点的标准版本,如线程和连接池等。
  • 用于快速代码生成的工作流。

这是艰苦的工作!它需要大量的底层库来完成繁重的工作,并在其上进行精心的设计和符合人体工程学的工作,以实现所需的关注点分离。

Tide 的愿景

我们与Tide的目标是双重的:

  • 首先,支持提供核心web功能的“繁重的工作”。像http和url这样的crates就是这个领域中相对成熟的核心库的例子。我们想要更多这样成熟的crates!其中很大一部分工作是确定和寻找改进、标准化and/or记录这些creates的方法。
  • 其次,在这些crates的顶部构建一个严谨的框架,理想情况下作为一个非常小的层级。也就是说,只要可行,我们就想使用现有的crates;如果不可行,我们就想创建小的、独立的crates,而不是一个整体框架。

所有这些还将汇集在一本书中,记录底层的crates和框架。

我们希望从一开始就以开放和合作的方式来做这一切。这篇博客文章是开放式系列文章的第一篇,探索各种设计问题,并寻求反馈和其他想法。如果您想参与这项工作的任何方面,请访问team-web频道了解争议和冲突!

开始的话题

本文的其余部分旨在通过对生态系统的调查,开启对几个核心主题的讨论。我们计划有一个稳定的后续帖子,提出问题,提出“strawman”的想法,并作为一个社区项目开发框架。

基本服务API

大多数语言生态系统最终都提供了一个关键接口来讨论web服务:Ruby有Rack, Python有WSGI, Java有servlet,等等。

这些接口指定了web服务器的含义,web服务器通常看起来像某种给定请求并生成响应的函数(通常是HTTP)。拥有这样一个接口意味着您可以将web框架(它帮助您生成这样一个函数)与底层web服务器(它实际上处理实现HTTP的工作)解耦。它还使提供通用的底层中间件成为可能,这些中间件可以用于任何服务器和框架的选择。

今天在Rust中,大多数web框架(但不是所有web框架)都直接使用hyper来提供基本的HTTP功能,这就防止了这种解耦和简单的中间件应用程序。然而,Tower – Service crate准备通过它的 Service trait 提供一个更通用的接口,而且作为Tower project的一部分,已经有大量的中间件,尽管它还没有发布到crats .io。

简而言之,Service trait 对响应函数的异步请求建模,其中请求和响应类型本身是通用的。对于HTTP,这些将专门用于HTTP creats中的类型。trait还被设置为处理每个连接状态(通过NewService factory trait)和 backpressure (通过ready方法)。

这里有一些重要的开放性问题::

  • 服务特性是在 async/await 中借用之前设计的,并且可能需要进行一些更改。特别是,在某些情况下,您可能借用了一些数据,希望将这些数据写入响应中,而不需要额外拥有副本。目前还不清楚在当前的设置下是否可以做到这一点。这里有一个问题需要进一步讨论。
  • http crates 提供基本的请求和响应类型,而主体数据一般被处理。因此,要建立HTTP服务的标准化,我们还需要标准化这些类型(或围绕它们的约束),这也意味着要考虑流体。最近一篇关于tower-web的文章提出了一种基于BufStream新特性的方法。

简而言之,Rust中的服务抽象已经有了非常坚实的基础,希望工作组能够帮助推动标准化和改进。这种抽象也可以作为构建Tide的起点。

路由策略

将上面的抽象视为理所当然,您可以将web框架视为一种非常奇特的方式,帮助您编写最终是单个请求-响应函数的内容。正如本文开头所解释的,框架所做的许多工作是将关注点分离出来,这样就可以用自然的风格编写核心业务逻辑。

大多数框架的起点是一些路由系统,它将url和HTTP请求类型映射到包含业务逻辑的特定函数(通常称为端点)。路由机制通常是处理其他一些以http为中心的问题的地方,例如验证。因此,它可以是框架如何组合在一起的一个定义方面。

路由方法一般分为三类:

Endpoint-centric routing

这是Rocket 和 Tower Web采用的方法。您可以用属性“装饰”一个标准的Rust函数,这些属性包括关于应该映射到该函数的URL的信息,以及如何提取各种参数和(对于Rocket)执行附加验证。以下是来自Tower Web的一个片段:

#[get("/")]
#[content_type("json")]
fn hello_world(&self) -> Result<HelloResponse, ()> { .. }

其吸引力显而易见:这种设置具有非常低的进入障碍,并将所有重点放在实现 Endpoint 的““native Rust”函数上。另一方面,它通常需要基于宏的实现,并且可能比其他一些方法更难扩展或定制。

Table-of-contents routing

这是Gotham、Rouille和Actix Web采取的方法。通过使用构建器样式或基于宏的api,可以独立于端点构造路由逻辑。例如,在Gotham中,你可以创建一个显式的路由器数据类型:

fn router() -> Router {
    build_simple_router(|route| {
        route.request(vec![Get, Head], "/").to(index);
        route.get_or_head("/products").to(products::index);
        route.get("/bag").to(bag::index);
        route.scope("/checkout", |route| {
            route.get("/start").to(checkout::start);
            route
                .post("/payment_details")
                .to(checkout::payment_details::create);
            route
                .put("/payment_details")
                .to(checkout::payment_details::update);
                route.post("/complete").to(checkout::complete);
        });
    })
}

这种“目录”分解使得从endpoint 逻辑独立地查看应用程序的高层结构更加容易。它还有助于处理endpoint问题,比如一组路由,所有这些路由都共享一个公共验证需求。另一方面,它比以endpoint为中心的方法更重,而且处理提取(从请求中提取信息)往往更棘手。

通过自由形式的组合进行路由

这是Warp采用的方法。在某种程度上,这也可以称为“无路由”方法:不区分端点和应用程序的其他方面。相反,所有东西都是一个“过滤器”,本质上是一个http感知函数,通过组合过滤器来构建应用程序。因此,路由由特定的过滤器处理:

// Just the path segment "todos"...
let todos = warp::path("todos");
// Combined with `index`, this means nothing comes after "todos".
// So, for example: `GET /todos`, but not `GET /todos/32`.
let todos_index = todos.and(warp::path::index());
// `GET /todos`
let list = warp::get2()
    .and(todos_index)
    .and(db.clone())
    .map(list_todos);
// `POST /todos`
let create = warp::post2()
    .and(todos_index)
    .and(warp::body::json())
    .and(db.clone())
    .and_then(create_todo);
// Combine our endpoints, since we want requests to match any of them:
let api = list
    .or(create);
// View access logs by setting `RUST_LOG=todos`.
let routes = api.with(warp::log("todos"));

现在谈论这种方法在Rust中的优缺点还为时过早。

Routing in Tide

考虑到上面的类型,问题是哪种方法最适合Tide ——既能给生态系统带来最大的利益,又能模式化地适应(因此不针对Tide)。

目前,“table of contents”样式的路由似乎是最佳选择。它在其他语言中已经很成熟,并且已经在Rust中率先使用——但是也可以使用一些标准化和API审查。与以 endpoint 为中心的路由相比,它更易于模块化/可扩展。与自由形式的组合相比,权衡更容易理解。

不过,为了全面展开讨论,”strawman”是有帮助的。本系列的下一篇文章将介绍一个这样的”strawman”,深入研究Rust中content -of-content -style路由中出现的一些API设计问题,并研究模块化和共享的途径。

© 著作权归作者所有,转载或内容合作请联系作者

你或许想:《去原作者写文章的地方

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
Python
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论