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

原文链接: 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设计问题,并研究模块化和共享的途径。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容