坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

虽然说rustweb没有什么优势,但是来都来了,那顺便逛一下比较出名的Rust Web框架吧~


提前准备


Rocket

简介

一个异步的web框架

话不多说,直接上手


hello world

在开始之前,我们需要更新下我们当前rustup版本

然后创建我们的项目

然后配置下根目录的Cargo.toml

然后打开hello-rocket文件夹里的Cargo.toml

然后回到main.rs文件中

  • #[get("/")]这个是声明宏,别给它外表欺骗了,用于注册对应的路由。
  • build:创建服务示例。
  • mount:注册基础请求路径以及路由。
  • routes!:是个属性宏,这个应该是用来搭配get这个声明宏的,用于建立联系。
  • #[macro_use]:这个主要是显性的将rocket里的宏全部导入,这样我们就可以在全局任意地方使用rocket的宏了。

这样就可以跑了,是的,你没看错,main函数都被干没了,主要是被宏直接隐藏了。

然后直接cargo run -p hello-rocket


预览

生命周期

Rocket主任务是监听请求,然后分配请求给你实现的处理器,返回响应给客户端。

这里将监听到返回响应体的过程叫做生命周期。大体上可以分为以下四部分:

  1. Routing:解析一个HTTP请求并转换成我们可以间接操控的原生结构。然后通过我们定义的路由属性匹配对应的处理器(handler)来执行
  2. Validation:在处理器执行之前,会先验证处理器所需要的类型是否符合,如果检验失败,Rocket会继续往下找到下一个匹配到的处理器,如果找不到直接进入错误处理器(error handler)。
  3. Processing:处理器里面执行的逻辑就是我们开发者主要实现的部分,以返回一个Response为结束标志。
  4. Response:将process返回的Response整理一下发送给客户端。

Routing

Rocket应用的中心是围绕路由(routes)和处理器(handlers)的。一个路由由以下两部分组成:

  • 用于匹配请求的一系列参数
  • 一个用于处理请求和返回响应体的处理器

而处理器指的是一个简单的函数可以接收任意的参数和返回任意的响应。

参数可以匹配到的包含静态路径、动态路径、路径片段(segments)、表单、查询语句(query strings)、请求格式说明符(request format specifiers)、请求体数据(body data)。

Rocket将这些定义为“属性”,写法和其他语言中的装饰器类似。比如下面这样:

一个路由属性搭配上一个路由处理器

其中#[get]指的是一个get类型的请求,/world则是静态路径。除了get,自然还有post等,比如#[post]#[put]或者#[catch]用于服务自定义错误页面。我们会在之后的章节里更深入的了解到。


Mounting

在派发请求之前,需要先找到派发的对象。这就需要我们去注册或者说是挂载对应的路由。

  • /hello:请求匹配到的路径的基础路径(base path),比如我们注册了world(routes![world]),那么这个时候请求路径就得是/hello/world才能识别到对应的处理器。
  • routes!:前面说过这是一个宏,后面接入一组函数名,比如routes![fn_a, fn_b, fn_c]
  • build:创建一个新的Rocket实例。

同一个路由可以在不同基础路径下注册。比如这里的/hello/world以及/hi/world


Launching

不过在开始创建实例之前,我们还需要launchlaunch的作用是创建一个多线程异步服务器以及派发进来的请求给路由。

launch有两种方式:

第一个就是我们刚看到的#[launch],编译阶段它会展开成一个main函数搭配上异步的runtime然后开启服务。另外我们还需要提供返回的类型,一般设置_就行了,它会自动推断,如果你想,你也可以手动返回Rocket<Build>

第二种方式就是使用#[rocket::main]路由属性。和#[launch]不同的是它允许我们自行开启服务。比如下面这样:

你可以用来获取launchfuture,在这个过程中处理一些其它的事务。又或者你需要判断launch之后的错误场景做特殊处理,那么这个时候也是非常有用的。


Future and Async

rocket使用Future来处理并发(concurrency)问题。一般情况下我们是更喜欢使用异步库而不是同步的。

Rocket中存在异步的场景如下:

  • 路由和错误捕获器(Error Catchers)可以是异步的函数,那么在这里面我们就可以等待第三方库比如tokio等或者直接使用Rocket给的Future
  • 部分Rockettraits,比如FromDataFromRequest都有返回Future的方法。
  • DataDataStream、进来的请求携带的数据、Response以及Body、传输给客户端的响应体携带的数据都是基于tokio::io::AsyncRead的,而不是std::io::Read

需要注意的是Rocket v0.5使用了tokio runtime,我们通过#[launch]或者#[rocket::main]来开启的服务都是tokio runtime的,当然你也可以自定义。

异步路由

来看个例子

这里使用了tokio::time::sleep,是一个异步函数,所以需要.await

Multitasking

RustFuture是一组协作式多任务处理,一般情况下我们不应该去阻塞异步函数。不过我们会遇到一些万不得已的场景,比如相关api只有同步的,这种时候推荐使用tokio::task::spawn_blocking也就是提供一个wrapper包裹原来的方法。


Requests

Methods

我们前面已经接触过get了,实际上目前可以支持几乎所有的请求类型,比如post、put、delete、head、patch、options等。具体可以看这里:route in rocket - Rust

HEAD Requests

当你的get请求中带有HeadRocket会自动处理HEAD请求。当然,你也可以自定义一个HEAD的路由,Rocket并不会干扰程序显性的(explicitly)处理HEAD请求。

Reinterpreting

由于html那边定义了表单提交类型只支持GETPOST请求,Rocket“不得已”重新定义了部分场景下的请求类型,比如POST请求的Content-Typeapplication/x-www-form-urlencoded,并且form的第一个字段带有_method以及值是一个符合标准HTTP请求的比如PUT,那么这个请求类型就会变成对应_method值的类型。官方给的例子中就有用到这一点(不过恕我吐槽,还隔这传统SSR呢。。。):https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/todo/static/index.html.tera#L47


Dynamic Path

动态路径比较常见的场景是动态id场景,比如:

这个路由会匹配所有/hello/为基础路径的路由,然后将它匹配到的动态路径作为参数传递给处理器。

语法是<${xxx}>

你可以传N个动态类型即你的动态路径有多层,只要你这个类型实现了FromParam[11]这个trait即可,他们管这叫做:参数保护,其实也就是trait bound。。

比如:

Rocket默认给标准库里的一些常见类型以及Rocket自身的一些特殊类型实现了这个trait,具体可以看这里:https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromParam.html

多个片段(segments)

你也可以通过<param..>的方式来匹配多个动态路径,这种类型的参数一般被叫做分段防护装置(segments guards),都必须先实现FromSegments这个trait。这个参数必须放到路径的最后面,不然会导致编译错误(可以参考python*params**params参数匹配)

来看个例子:

PathBuf实现了FromSegments这个trait,所以你不用担心/page或者/page//导致的解析失败,另外因为实现了FromSegments,我们也不用担心路径遍历攻击(path traversal attacks)。基于这一点,我们可以简单的实现一个安全的静态文件服务器:

PS:如果你真的想要搞一个静态文件服务器,你可以使用FileServer,只需一行代码即可:

Ignored Segments

如果你的路径是动态的,但是你又不想要其中几个参数,那么这个时候你可以直接使用_占位符<_>占位即可,如果是多个可以使用<_..>

比如:


Forwarding

回到我们前面匹配多个动态路径的路由例子:

如果这里的cool不是一个布尔值呢?又或者age不是u8呢?我们前面提到的生命周期的里有一个validation,这个时候就是invalid,那么就不会走这个handler,而是继续往下匹配,直到遇到了下一个匹配的上的或者找到最后找不到了走err handler

另外我们可以通过rank关键字调整同个路由下不同handler匹配的优先级。比如下面这个例子:

上面这个例子中首先匹配到的就是user这个处理器,如果不符合校验,接着匹配的是user_int,如果还不匹配则轮到user_str

这个rank我们可以在终端看到,比如

Rocket的默认rank范围是-12 ~ -1之间,这一点后面会解释到。

如果你给处理器的动态参数类型设置为Result<T, E>或者Option<T>,那么这个时候这个处理器函数将不会再往下匹配,Ok拿到值,而Err则表示解析错误等。

如果你确实存在多个同名路径路由,那么这个时候这个rank是不能省略的,不然编译的时候会直接报错。

注意,这里是forward的场景下才会出现。什么意思呢?

实际上就是上面这种场景,虽然是动态的,但是路径都长得一样。

那么对于长得不一样但是也有可能相同的场景来说并且没有指定rank的情况下,这里就需要引入权重(优先权)这一点了。比如下面这个例子

虽然长得不一样,但是肯定是在同一个forward中的。

那么哪个优先级高呢?

很明显是/foo/<_>/bar高,因为它比较“静”也就是static

_..则是完全的“动”,即dynamic。简单地说就是以/分割那边字面量多哪边优先级高一些。

这里就是我们上面提到的rank的默认范围,数越小优先级越高。而path的权重则是高于query的(这里指的是路由的不同分类)。

  • static:路径中全是静态的,即都是字面量,比如/a/b/c
  • partial:部分是动态的,比如/root/<_>/bar
  • wild:全是动态的,比如<_..>

这里简单了解下即可,正常情况下我们并不会遇到这类问题。


Request Guards

简单地说就是类似trait bound,也就是需要实现FromRequest[13]这个trait。实现了FromRequest的类型就都叫做请求守卫。

来简单的看下源码和例子

可以看到实际上就是每个类型自身去实现校验的逻辑,而rocket会在执行处理器之前进行校验。

对于没有命名的类型参数来说,你可以手动指定它们为请求守卫,比如:

其中A, B, C三个类型都是请求守卫。 他们仨可以是任意类型,只要是实现了FromRequest即可。

Custom Guards

基于上面这一点,我们实际上可以实现自己想要的请求守卫,自定义校验逻辑,比如:

如果不传key就会报错

Guard Transparency

另外我们还可以基于守卫去实现访问权限的方式,rocket中如果需要实现这一块功能,那么就需要数据都通过FromRequest才行。如果某个类型是只能通过FromRequest实现创建的,那么我们就将这类型称之为"类型级证明"(type-level proof),证明当前的请求已经根据任意的策略进行了验证。而FromRequest则作为这个类型的见证者,rocket中称这一概念为守卫透明度(guard transparency)。

这一大段话就很绕了,我们来搞个具体的例子加深下理解。

某个程序具有一个函数 health_records,该函数返回数据库中的所有健康记录。由于健康记录是敏感信息,因此只能由超级用户访问。超级用户请求保护对超级用户进行身份验证和授权,其 FromRequest 实现是可以构造超级用户的唯一方法。通过按如下方式声明health_records函数,可以保证在编译时防止违反健康记录的访问控制。

原因如下:

  1. health_records函数需要&SuperUser这个trait
  2. SuperUser的构造器只有一个FromRequest
  3. 只有Rocket可以通过FromRequest来传递request

那么这里就只能是通过FromRequest来访问request,而SuperUser又是可以唯一一个通过验证的,那么这个时候自然就只有这个SuperUser可以访问数据。

不过这里代价则是生命周期,你可以看到我们前面写的代码,一连串的生命周期参数。通过牺牲一点可维护性来强化安全问题。

Forwarding Guards

Request GuardsForwarding可以作为一套组合拳,至于组合啥,我们来通过一个例子来知晓组合的是啥。

首先我们这里有两个Request guard:

  • User:一个常规的、被授权的用户,我们给它实现FromRequest用来确认用户的cookie并且当确认无误的时候返回用户的信息,如果没有用户可以授权,这个守卫将forward
  • AdminUser:一个用户被授权为管理员。我们给它实现FromRequest用来确认是否是超级管理员并且确认无误之后返回管理员信息。如果没有管理员可以授权,这个守卫也会forward

我们使用这俩组合成一套用户身份验证的逻辑,优先验证是否是管理员,次之才是用户,最后则是游客,直接跳登录页面。

这么写有函数重载那味儿了,代码会简洁非常多,不用在同一个处理器中用if去做判断,而是通过impl FromRequest去实现check

这么一看确实优雅了很多。


Cookies

Cookie这玩意儿在Web中是绕不开了,而Rocket自然也考虑过这一点,所以它内置了相关的请求守卫:CookieJar[15],它允许我们对cookies的增删改。既然它是一个请求守卫,那它自然是可以直接用来作为处理器参数的。

使用例子:

Private Cookies

Cookie有个问题就是你发送给客户端的数据是暴露的,用户可以通过某些方法去得到对应的信息,而对于一些敏感的信息来说,被用户拿到了是非常危险的。所以自然就需要做处理,Rocket中提供了私有cookie,和其他cookie唯一的不同点就是它们是加密过的。不过Rocket默认不导入这块,我们需要手动去添加features

这些方法的名字和之前的一样,只不过加上了_private的后缀,即get_private、set_private、add_private、remove_private等。

来看个例子:

Secret Key

秘钥这玩意儿相信大家都知道是干啥的,简单地说就是用于加密时的一个私有变量。Rocket中加密采用的是secret_key配置中指定的 256位的key参数。在debug模式下,这个key会自动生成,而生产环境则需要我们开发者来定义。

值也不一定是256位的base64,也有可能是hex字符串又或者32字节的切片。我们可以通过openssl等工具直接生成,我们本机连接远程服务器的方式之一就是通过它。

关于配置这一块,具体可以看:https://rocket.rs/v0.5-rc/guide/configuration


Format

前面我们自己写的路由处理器获取到的数据或者返回的数据最终都变成了字符串,而真实开发中这并不是我们需要的,我们需要的是某些特定格式的,比如当header中的Content-Typeapplication/json时,拿到或者返回的格式我们希望是json的。

作为Web框架,Rocket自然也兼顾到了这一点。

我们可以在路由的路径参数中(从这里往后定义在#${method}[]里面的参数都叫做组件了)设置format组件来forward, 比如:

只有Content-Typeapplication/json时才会匹配成功。

format = "application/json"可以简写为format = "json",具体可见:ContentType in rocket::http - Rust

这里有点需要注意:format组件对于支持负载(payload-supporting)的 POST/PUT/DELETE/CATCH四种method来说是验证Content-Type,而对于不支持的GET/HEAD/OPTIONS来说则是匹配Accept


Body Data

上面Format中的例子带有一个data = <user>的组件,实际上这个就是定义的Body Data。这个组件同样会作为处理器的参数传入处理器中。

来再看个例子:

这个参数需要实现FromData[18]这个trait ,一般管这叫做数据守卫(data guard).

JSON

JSON<T>[19]可以将数据序列化成JSON结构,唯一的要求则是T需要实现serde[20]Deserialize这个trait

来看个例子:

完整的例子可见:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/serialization/src/json.rs

**注意:**这里使用的#[serde(crate = "rocket::serde")]是用来指向rocket导出的serde这个包,由于孤岛规则的原因,我们这么写是存在损耗的,所以如果不想导致损耗的话,可以直接修改Cargo.toml让它直接和rocket同级

另外这里的支持json也需要修改Cargo.toml

还有其他格式的,详情可见:rocket - Rust

Temporary Files

看名字就知道是和文件操作相关的,这个TempFile数据守卫将数据流写入一个临时文件中,这样使得文件操作变得轻松很多。

来看个例子

Streaming

有的时候我们就是想自己操作进来的数据,那么这个时候我们就可以通过Data[21]这个类型来包裹参数,比如:

最多接收512KB的字节流,注意这里为了避免遭受到DoS的攻击,需要声明接收的最大值,这里使用的是rocket自带的ToByteUnit[22]trait用来限制


Forms

前面我们用format限制了Content-Type等,但是我们的数据拿到手还是字符串类型的,这时就需要我们手动去实现这一类的功能,将数据转换成我们想要的格式,前面Body DataJSON<T>就是其中一种方法,不过Rocket中还内置了另一种数据守卫Form[24]以及FromForm[25]这个trait,可以让我们转换成更多的类型。

我们先来看个例子

Form作为一个数据守卫,要求它的泛型T得实现FromForm这个trait,我们这里直接使用派生宏给Task实现FromForm

FromForm可以用于给任何字段都实现了FromForm/FromFormField的数据机构实现。

注意这里依旧是会校验Content-Type的,也就是说即使我们不使用format组件依旧没问题,当不匹配的时候就会forward

我们甚至可以用Option<T>或者Result<T, E>包裹参数,这样我们就可以让fail被捕获到了。

Multipart

Multipart表单会被无感知处理,也没有其余损耗。

例子:

这就行了,大多数Multipart都有被内置处理,我们并不需要手动去处理

Parsing Strategy

对于FromForm来说,它并不会做严格限制,限制啥呢?字段,我们学Rust基础内容的时候如果实例多了或者少了字段就会导致编译报错,不过这里这段代码是跑在runtime的,所以并不会有编译上的问题。Form<T>仅对当前类型的字段做处理,它不会去限制你的数据是否缺失字段或者多了字段,少了它自动帮你补全成默认值,多了它不理会。

当然,你如果不想要这么宽松,你可以使用Form<Strict<T>>[26]来严格限制,Strict<T>也是实现了FromForm的,所以任何可以使用Form的地方都可以使用Form<Strict<T>>

你也可以把它用于其中一个字段

比如必填。

既然有严格,那自然就有宽松lenient[27]Form默认就是lenient的,所以请不要做Form<Lenient<T>>这种骚操作。

Defaults

前面我们说过字段缺失会有补充默认值的行为,那么这个默认值我们是可以自定义的。

#[field(default = expr)] 当这里的default为None的时候则不会作为这个字段的默认值,而是采用FromForm提供的。

如果不是None,则会调用expr.into()将值作为字段的默认值,具体可看:FromForm in rocket - Rust

Field Renaming

就如标题所说,这里还支持字段重命名,你可能觉得有些蛋疼,但实际上在一些场景下还是有用的,比如对于前端给的数据中给的字段是另一个名字,这个时候如果跟前端battle不过,你就可以用,当然,更建议是继续battle直到胜利,来看个例子:

我们给first_name这个字段定义一个first-Name的大小写敏感名字,和一个firstName大小写敏感的名字。也就是说这里支持first-NamefirstNameFirstName或者FIRSTName

Ad-Hoc Validation

除了设置默认值之外,我们还可以做验证,是的,针对于某个字段的校验。比如:

我们需要age > 21才行,所以这里给了个range(21..)。不过这里的rangerocket::form::validate::range[28]

除此之外,还可以使用validate模块里提供的其它函数,比如:

当然,这也是可以自定义的,不过需要借助Rocket::form::Result来实现,比如:

Wrapping Validators

如果很多地方都有用到同一种验证,那么我们最好是将这块逻辑抽取出来重复使用。Rocket也提供了这种功能

比如这个Age的验证在很多地方都有用到,那么这个时候就可以这么封装

别忘了给它加上derive(FromForm),毕竟实现FromForm的前提是所有子字段都需要实现FromForm

Rocket 的表单支持允许您的应用程序使用任何级别的嵌套和集合来表达任何结构,从而超越任何其他 Web 框架提供的表达能力。

简单地说,就是通过.或者[]去访问嵌套结构等。比如:

上面的格式不是一个引用,而是一个例子,用来加深理解。

等号左边这个链式调用被.[]分割成keys,然后keys可以通过:来分割为indices也就是常说的index

而等号右边则是一段key2=value2&key2=value2

注意如果.是跟在[]后面的,那么这里的.不是必要的,比如a[b].ca[b]c是等效的。

Nesting

Form是可以嵌套的,这一点我们前面的Age就是,我们再来看个例子:

上面的结构可能被编码成owner.name=Bob&pet.name=Sally&pet.good_pet=on这样,然后解析之后会变成下面这样

Vectors

也有支持Vectors的,而Vec里的元素也是可以使用.或者[]来赋值。比如下面这样:

这上面几种都是等效的,可能有些不好理解。对于空[]来说,直接pushvector的最后面,比如上面第一行,而对于同一个key来说,会按顺序往后放,比如上面第三行来说,等效于[a, b, a] = [1, 2, 3]

它们都会被解析为

其中"numbers=1&numbers=2&numbers=3"有些离谱,不过它等效于numbers[]=1&numbers[]=2&number3=[]

不过对于实现了FromFormField的类型来说,如果已经存在对应的key了就不会再继续push了。

仅保留第一个key的值。

Nesting in Vectors

既然是Vector,那自然可以放其它结构的数据类型,比如Struct,来看个例子

这个就不用多说了,和前面分析的流程一样。

需要提一下下面的场景是会导致解析失败的:

不允许使用空[]或者key丢失。

Nested Vectors

既然Vec也实现了FromForm,那么它自然也可以作为Vec的元素类型。

比如:

然后我们来看下规则:

老实说这真的可读性极差极差,这就是灵活的代价。

Maps

一个表单还可以包含maps,比如:

Vector不一样的是,对于Map来说,它的key是有意义的,同一个key永远指向同一个数据。

来看个例子:

由于mapkeyvalue可以是任意值,所以这里你把key设置为usize,那么你赋值时的表现完全可以和Vector的一样,但尽量不要这么做。

由于key值需要比较,所以对于你设置的key类型来说,它需要实现Eq,另外还需要实现一个Hash,这个点和Rocket底层逻辑有关,这里就不分析了。

当然,别忘了实现FromForm

这里还有个问题,由于key可以是任意值,那么这里是可以塞入一个struct的,而struct又有多个字段,如果要写就得把所有字段再加上,这就很蛋疼了,而Rocket官方显然也知道这个问题,所以针对这个问题他们又提供了一个方案(这可读性真的。。。。)

来看个例子:

而针对于这种,写法这是:

m[k:$key],其中k表示key,而$key可以是任意的,对于同一序列来说,$key一样的都算是同一个元素。

同理于m[v:$value]

另外如果不指明k:则表示是value

上面的代码解析后相当于:

Arbitrary Collections

这个就不多解释了,简单地说就是自由~,但是自由的代价既是可读性差

来看个类型:

根据上面的类型,我们可以搞个例子:

你现在可以很好的分析这一块的意思吗?如果可以,那你基本上就学会了,但是如果还没,那也不急,咱以后实战磨练。

再分析序列之前,我们先来分析下类型,是一个Map类型,然后它的key类型是一个Vectorvalue类型则是Map,它的keyusize,而valuestruct。而key里的元素则是BTree也是一个Map,而这个Mapkey则是structvalueusize

很乱是吧,不过也不是不能理解。

然后我们来分析下序列。

top_key指的是Vector,也就是最外层Mapkey。而iVector里的元素,是BtreeMapsub_key则是BtreeMap里的key,也就是Persion

而后面没有k:则表示是同一个数据的value,也就是Persion

Context

Contextual[29]是其它form guard的代理(proxy),记录所有表单提交的值和提供关联到对应字段名的错误。Contextual一般是用于表单的渲染回填,即上次填写的内容再次打开表单的时候可以看到,另外就是提供错误这一点了。

我们可以使用Form<Contextual<'_, T>>作为数据守卫,T则需要实现FromFormcontext字段包含表单的上下文Context[30]

来看下用法:

错误收集是支持嵌套写法的,即a.b.c这种。

另外Context会被序列化成一个map,所以它可以用于渲染在templates中,不过需要使用Serialize类型。具体可见:https://api.rocket.rs/v0.5-rc/rocket/form/struct.Context.html。而例子可见:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/forms


Query Strings

前面我们分析的都属于path,那么这个Query是个啥呢?Query声明和path参数类似,但是处理方式则是和常规URL编码表单字段类似。

来看张图:

img_path_and_query

除了实现的trait不同之外。。。。

不过不用急,我们等会接触到例子就知道了

Static Parameters

和静态路径一样,也是强匹配,直接来看下例子:

注意是contains即是子集就行,并且不需要注意每个参数的顺序

可以看到实际上就是在匹配参数,专注点不同。

Dynamic Parameters

用法相信大家也都猜出来了,这里咱就直接上例子了

注意默认也是不敏感 + 宽容的

Trailing Parameter

也就是收尾参数<xx..>,直接看例子:

这里收集的包括staticdynamic的,另外只能放到最后不然也会有编译错误


Error Catchers

一般错误的出现大多来自以下场景:

  • 守卫失败
  • 响应失败
  • 路由匹配失败

catcher捕获器就是用来捕获这些错误的,包含了错误码和错误引起的地方或者区域。

捕获器和路由挺像的,不如说它也算是一种路由,但是比较特殊,专门用于处理出错误的场景。

它有以下一些和路由不同的点:

  1. 捕获器只设计错误的场景
  2. 捕获器使用catch属性声明
  3. 捕获器通过register()[33]而不是mount()[34]来注册
  4. 在捕获器被执行之前,任何的cookies的修改都需要先处理完
  5. 错误处理器无法使用守卫
  6. 错误处理器返回的响应不能失败
  7. 处理器的作用域是路径的前缀

来看个例子:

表示捕获错误码为404的场景。

这里参数实际上除了&Request之外可能还有一个Status。返回的响应类型得是实现Responder的。比如String

当然,不要忘了注册

Scoping

前面说了捕获器的作用域是路由前缀,比如上面的例子中是/

我们来看个例子:

这里如果你请求的是/foo,它只会走foo_not_found,而不会再触发general_not_found,所以作用域是严格的。

Default Catchers

默认捕获器会捕获所有类型的状态码。你可以用它来作为一个回调,用于处理公共业务,来看个例子:

推荐是通过路由路径限制,而不是状态码,这样要处理的业务比较单一,作用域也比较小。当然,看你个人喜好。

Built-In Catcher

Rocket提供了内置的捕获器,返回的数据是HTML还是JSON取决于你说设置HeaderAccept。具体可见:https://api.rocket.rs/v0.5-rc/rocket/catcher/struct.Catcher.html


Responses

Request学的差不多了,我们来学下Response

Responder

我们在处理器中返回的内容最终会被rocket自动包装成一个标准的htttp responder[37],不过这是有代价的,我们的return type需要实现Responder这个trait

Wrapping

有些情况下,我们的返回数据可能是一个多层嵌套的responder,即一个实现了Responder的类型包裹另外一个实现了Responder的类型,比如:

具体的例子:

例子挺好懂的,就不多说了。

Errors

这里的Errors实际上有两种场景,一种是来自于业务,比如某些数据在业务逻辑中判断是无效的,那么这个时候就应该返回Error,但是状态一般都是200表示http响应正常。而另一种则是程序自己的错误,是开发的bug,这种情况一般是返回500,表示服务器内部出错,这种就是之前我们接触到的catcher来处理。

Status

我们可以利用Status[38]forward,比如:

NotAcceptable对应的状态码是406,属于error status codes(范围是[400, 599]),它会直接forwarderror catcher。其它可以跳catcher的还有以下的场景:


Custom Responder

直接来看个例子:

上面的代码在包裹的过程中会经历下面的流程:

  1. 将当前状态码设置成500
  2. 返回数据类型设置成application/json
  3. 插入self.headerself.more;
  4. 执行self.inner返回组装好的response

这里注意inner这个字段作为内部的responder,而其他的(除了使用#[response(ignore)]的)都会作为header插入到response。具体可见:https://api.rocket.rs/v0.5-rc/rocket/derive.Responder.html

一般情况下我们都是直接impl Responder来实现(具体可见:https://api.rocket.rs/v0.5-rc/rocket/response/trait.Responder.html),但是这里实际上还有另一种方式。

如果你的类型包裹了一个Responder,那么你的这个类型会自动实现Responder


implementations

Rocket内置给String、&str、File、Option以及Result这些类型实现了Responder

Strings

Strings&str实现的Responderbodysized以及Content-Typetext/plain的。

根据这点,相信大家自己都可以写一个,来看下源码的部分

这没什么好说的了,大家想要自己定义的话可以参照这个例子。

Option

由于OptionResult都要接收泛型作为数据类型,所以它们的T都需要实现Responder才行。

对于None的场景,会直接进入404的场景。来看个例子:

成功直接返回文件,失败直接404。

Result

由于Result需要接受两个泛型TE,所以他俩都得实现Responder

还是fileserver,不过我们可以拓展下我们想要返回的内容:


Rocket Responders

Rocket自己的一些类型也都实现了Responder,来看下最常用的几个:

  • NamedFile:返回给客户端的文件流,会自动根据文件的拓展名来设置Content-Type
  • Redirect:重定向。
  • content:用于覆盖响应的Content-Type。
  • status:用于覆盖响应的状态码。
  • Flash:设置访问时删除的“闪存”cookie。
  • Json:自动序列化值为JSON。
  • MsgPack:自动序列化值为MessagePack。
  • Template:使用Tera或者handlebars渲染一个动态的模板

Async Streams

stream响应器允许无线的异步stream。一个stream可以通过任何的异步Stream或者AsyncRead类型或者宏stream![42]来创建。Streams是一块实时单向通信(unidirectional real-time communication)。比如通过EventStream[43]实现的一个聊天室[44](Server-Sent EventsSSE)。

我们来看个例子,通过AsyncRead类型来创建的ReaderStream[45]

再来看个使用宏TextStream[46]的例子:

JSON

我们一般用这个来快速的返回一个JSON类型的数据,不过Json<T>的泛型需要实现前面说过的来自serde[47]Serialize[48]才行。

例子:

默认设置的Content-TypeJSON,如果序列化失败,错误的场景是500

序列化的例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/serialization


Templates

直接来看个例子:

这里使用了rocket_dyn_templatesTemplate[50],渲染了一个名为index的模板。

注意这里的context需要实现Serialize。如果是字面量,我们可以直接使用context!这个宏。

模板需要先注册才行,我们可以使用Template::fairing()来让Template自动注册所有它接触到的template

前面说过这里可以使用两种模型:handlerbarstera,用哪个取决于你在template_dir文件夹中模板的后缀是.hbs还是.tera

至于如何配置,我们后面会接触到。

Live Reloading

局部热更新,在开发模式下修改模板不需要重启服务器。

具体可见:https://api.rocket.rs/v0.5-rc/rocket_dyn_templates/struct.Template.html

例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/templating


Typed URIs

这个直接描述可能不太好理解,所以这里先来看例子:

现在应该就很好理解了。

简单地说,我们通过uri!这个宏来创建路由的URI。它会返回一个Origin的结构,将路由插入结构中作为返回的值,这个值实现了UriDisplay确保了它是类型安全的等。

Origin实现了Into<Uri>,所以它可以通过.into()以及传入Redirect::into()等方法的方式转换成一个Uri

Ignorables

前面说过动态的参数可以通过_占位符来实现忽略(仅querypath不支持),而在这里自然需要兼容处理,要求URI必须实现Ignorable.

Deriving UriDisplay

前面说过返回的值需要实现UriDisplay这个trait才行,所以如果我们需要自定义某些路由参数的时候就需要手动去实现才行。

对于path需要实现UriDisplayPath,而query的则是UriDisplayQuery

比如:

它实际上是编译阶段自动帮我们实现UriDisplay<Path/Query>

Typed URI Parts

前面的UriDisplay实际上实现了Part这个trait,用来作为marker type,作用自然是用来标记数据是Path还是Query。这么做自然是为了传参的安全,我们传参也可以直接一套代码就行而不用做区分。

Conversions

FromUriParam这个类型是用来在UriDisplay接收值之前转换传给uri!的值的。其中P自然就是Part,而S表示转换的目标类型。比如:

下面这些都是可以转的:

  • &T => T
  • &mut T => T
  • String => &str
  • &str => &Path
  • &str => PathBuf
  • T => Form<T>

专属于path的:

  • T => Option<T>
  • T => Result<T, E>

专属于query的:

  • Option<T> => Result<T, E> (for any E)
  • Result<T, E> => Option<T> (for any E)

另外是支持传递的,比如A => B, B => C,那么可以直接改成A => C

&str先变成PathBuf,然后又变成Option<PathBuf>

具体可见:https://api.rocket.rs/v0.5-rc/rocket/http/uri/fmt/trait.FromUriParam.html

State

一个web应用基本都需要维护一些状态,比如访客量、任务队列以及多数据库。Rocket提供了工具用于安全的管理状态。

Managed State

Rocket提供的状态管理是按类型来的,每个类型最多只会拥有一个值。

上手很简单:

  1. 调用manage,并且初始化。
  2. 给任意处理器添加&State<T>,其中T是提供给manage的值的类型。

注意都得是SendSync的,得保证线程安全,因为Rocket会自动开多个线程。

Adding State

前面说的manage是一个方法,来自于Rocket实例。来看个例子:

我们直接传入了初始值。

另外还可以多次调用:

Retrieving State

我们可以通过&State<T>获取到T类型对应的值。&State是一个用于管理状态的请求守卫。来看个例子:

另外如果你想要获取的值之前并没有注册到manage中,那么在服务器启动的时候会直接失败。而针对于runtime的数据来说,也就是动态类型,Rocket会使用sentinels来判断是否符合要求,不符合会触发500

完整的例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/state

manage细节:https://api.rocket.rs/v0.5-rc/rocket/struct.Rocket.html#method.manage

State typehttps://api.rocket.rs/v0.5-rc/rocket/struct.State.html

Within Guards

由于&State自身是一个请求守卫,所以我们可以直接通过实现了Request::guard()[54]或者Rocket::state()[55]的方式获取到&State,不过使用Request::guard()的前提是实现了FromRequest

例子:


Request-Local State

根据前面manage方法来自于Rocket的实例我们可以知道前面的状态管理是在全局的,如果我们只想要在局部管理的话我们需要使用Request-local state,这种state随着请求的结束而被drop,注意这里的请求指的是一次http请求,包括了request、fairing、request guard、responder

这种局部状态是会被缓存的,这对于一些场景来说是很有用的,比如同一个http请求过程中多次触发的请求守卫等。

来看个例子:

每次请求都会返回同一个id,这么做可以省去参数传递,写起来比较方便。

具体可见:https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html#request-local-state


Databases

Rocket通过rocket_db_pools内置了对数据库的支持,通过连接池的方式访问多个数据库。

至于如何配置我们之后会接触到。

下面简单三步实现连接数据库:

  1. 在这里选择一个支持的数据库驱动:rocket_db_pools - Rust。然后将rocket_db_pools引入项目中:
  1. 设置一个数据区的名字,下面的例子中数据库名字是sqlite_logs。配置相关信息,其中数据库路径是必须的。
  1. 引入数据库,首先需要定义数据库驱动的类型以及它指向的数据库名字。在创建实例的时候需要初始化数据库驱动。最后可以通过路由守卫Connection<T>来获取数据库实例

更多细节:https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html

Driver Features

rocket_db_pools默认只会导出必要的特性,如果需要拓展,我们得在Cargo.toml中引入:

Synchronous ORMs

rocket_db_pools是不堵塞的,如果你需要同步堵塞的话,你可以使用rocket_sync_db_pools

Examples

例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/databases


Fairings

Fairings前面提过几次,但是都没说是个啥,实际上就是Rocket提供用来实现类似中间层(middleware)的方法。有了中间层,我们可以在一个http请求的生命周期中做很多事情。

Overview

对于实现了Fairing这个trait的类型来说都叫fairing

虽然和其它框架中的中间层很像,但是几个关键点还是有区别的:

  • Fairings无法直接对请求进行终结/响应。
  • Fairings无法注入没有非request数据到一个请求中。
  • Fairings可以阻止程序启动。
  • Fairings可以修改程序配置。

实际上其它框架中的中间层和请求守卫/数据守卫更像一些。

根据上面几点,一个Fairings的作用更多的是和全局有关的,而不是局部。如果你的逻辑是局部的,建议使用请求守卫等。

Attaching

Fairings是需要注册的,需要通过Rocket实例的attching方法注册。

看下例子:

Fairings是按顺序触发的,所以你需要确保顺序是正确的。

除了singleton fairings之外fairing都是允许触发多次的。

callbacks

fairing可以在下面几个事件中注册回调:

  • Ignite(on_ignite):在Rocket实例构建的时候触发hook。一般用来校验和处理配置,或者处理全局状态。
  • Liftoff(on_liftoff):在程序launch的瞬间触发。可以用来阻止Rocket实例launch
  • Request(on_request):当请求到达的时候触发。可以用来修改请求。
  • Response(on_response):当一个响应已经准备好发送给客户端了的时候触发。可以用来修改响应。
  • Shutdown(on_shutdown):在程序shutdown的时候触发。

细节可见:https://api.rocket.rs/v0.5-rc/rocket/fairing/trait.Fairing.html


implementing

实现Fairing必须实现info这个方法,它会返回一个Info。这个Info会被用于Rocket指派fairing和触发回调。

Requirements

另外实现Fairing需要Send + Sync + 'static,也就是线程安全。

Examples

我们有好几个get/post请求,这里面有一些相同的操作,这里就可以抽出来放到全局。

注册了on_requeston_response两个回调,用来插入请求值和修改响应值。

完整的例子可见于:https://api.rocket.rs/v0.5-rc/rocket/fairing/trait.Fairing.html#example


Ad-Hoc Fairings

这里还提供了字面量的快捷方法,比如:

AdHoc是一个内置的实现了Fairing的结构体。


Testing

Rocket还提供了单元/集成测试。

不过这就需要我们模拟请求了。

Local Dispatching

Rocket有一个local模块,它包含了一个Client结构体可以用来创建LocalRequest

使用例子:

  1. 创建一个Rocket实例。
  1. 使用这个实例构建客户端。
  1. 使用客户端实例构建请求。
  1. 获取响应

Validating Response

上面第四步返回的response的类型是LocalResponse。我们可以用它来校验。

  • status: 返回http状态码。
  • content_type: 返回Content-Type
  • headers: 返回响应的header,是一个map
  • into_string: 将响应体数据转换成字符串。
  • into_bytes: 将响应体数据转换成Vec<u8>
  • into_json: 转换成JSON格式。
  • into_msgpack: 转换成MessagePack格式。

来看个例子:


Testing demo

我们来搞个例子:

首先搭建下基础:

Setting Up

然后来创建测试环境

然后完善测试逻辑

Testing

然后就可以直接cargo test了。

最终代码:


Asynchronous Testing

上面的例子你应该注意到了blocking,是的,目前测试都是同步的,即使Rocket自身是异步的。

同步测试比较简单,但是在某些场景就不太好用了,比如最常见的并发场景,需要模拟多个请求。

Rocket其实也提供了异步的apirocket::local::asynchronous。例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/testing/src/async_required.rs


Codegen Debug

我们可以通过设置参数ROCKET_CODEGEN_DEBUG=1环境变量来查看更详细的错误信息。

比如:


Configuration

Rocket系统配置是基于Figment这个crate的。

Overview

Rocket的配置系统是基于FigmentProvider的,它用于提供配置数据。RocketConfigConfig::figment()Toml 以及 Json都是Providers的实例。多个provider可以合并成一个provider,不过需要实现Deserialize

下面是一些基础配置:

keykinddescriptiondebug/release default
addressIpAddrIP服务地址127.0.0.1
portu16服务的端口8000
workers*usize用于处理futures的线程cpu core count
max_blocking*usize限制线程用于同步任务512
identstring, false是否/如何通过 Server header.辨别"Rocket"
ip_headerstring, falseIP header用于检测,获取通过 client's real IP."X-Real-IP"
keep_aliveu32Keep-alive 时间; 为 0是禁止.5
log_levelLogLevel最高支持多少层log(off/normal/debug/critical)normal/critical
cli_colorsbool是否在log中支持颜色和emoji.true
secret_keySecretKey密钥None
tlsTlsConfigTLS 的配置,如果有.None
limitsLimitsStreaming读取长度的限制Limits::default()
limits.$name&str/uint读取$name的长度限制form = "32KiB"
ctrlcbool是否支持ctrl + c终止程序true
shutdown*Shutdown自定义终结程序方式Shutdown::default()

*注意:这里的woekersmax_blocking以及shutdown.forcedefault provider中是只读的。

Profiles

配置文件可以通过 Profile设置任何的名字。开发模式下 ConfigConfig::figment()在开发模式下会自动设置为debug,而生产环境则是release,当然都是可以自定义的。

比如我们可以通过ROCKET_PROFILE来覆盖default provider的基础配置。

对于配置文件来说,这里有两个元(meta)配置文件:defaultglobal,它们提供的值可以应用于所有配置文件。default的数据是兜底的,如果必要的数据没有配置就会直接拿default的,而global则是最顶级的,会覆盖任何的配置文件下同个配置项。


Default Provider

Rocket默认的配置providerConfig::figment()。在rocket::build()的时候会被使用。

配置文件的基础是key也就是配置项。

下面配置内容的优先级逐级升高:

  1. 通过Config::default()配置出来的,是兜底数据。
  2. Rocket.toml或者TOML文件路径配置在ROCKET_CONFIG的环境变量。
  3. ROCKET_前缀的环境变量。

Rocket.toml

根据前面的内容,我们可以通过三种方式来自定义配置文件。

通过Rocket.toml的不需要我们去自定义路径,Rocket会自动去判断当前文件夹里是否存在,如果没有就会去父目录里找,如果还找不到就继续去父目录里找,直到根目录。

通过相对路径配置的ROCKET_CONFIG的话同上。

而如果通过ROCKET_CONFIG配置的路径是绝对路径的话,就不需要去递归查找。

一个配置文件中可以包含多种配置文件,比如:

而下面这个则是一个设置了所有项的配置文件

注意,如果不是必要的情况切勿去配置,用默认的即可。

Environment Variables

前面说过ROCKET_为前缀的配置项优先级是高于Rocket.toml的。

Rocket会读取所有以ROCKET_为前缀的环境变量,然后将_后面的内容作为配置项的名字。比如:

格式也是toml的。


Configuration Parameters

Secret Key

这个前面私有cookies的那块介绍过了,这里就不多说了。

Limits

用于限制数据类型最大允许接收的量。这里需要传一个字段table,比如limits = { json = "10MiB" }带单位和不带都可以,默认是32KiB

TLS

Rocket内置支持TLS的,不过需要导入:

可以通过tls.keytls.certs两个配置项来配置,对于default provider来说,可以这样:

tls的配置也是传一个字典,它会被反序列化成TlsConfig结构体。

keyrequiredtype
keyyesDER 编码的 ASN.1 PKCS#1/#8 或 SEC1 密钥的路径或字节。
certsyesDER编码的X.509 TLS证书链的路径或字节。
ciphersno启用CipherSuite数组.
prefer_server_cipher_orderno布尔值表示是否首选服务器密码套件(prefer server cipher suites.)
mutualno一个mutual TLS 的map配置

每一个cipherSuite变体都会被转换成字符串,比如CipherSuite::TLS_AES_256_GCM_SHA384 变成 TLS_AES_256_GCM_SHA384

默认的配置如下:

Mutual TLS

Rocket也支持双向TLS客户端验证的模块mtls。它提供一个请求守卫用于校验和获取客户端证书。

默认情况下,双向TLS是禁止的并且客户端证书是不必要的。如果想要支持,需要引入:

然后配置tls.mutual

tls.mutual,ca_certs配置的是CA证书。

tls.mutual也是要求传一个字典,最终也是会被反序列化成MutualTls结构体:

keyrequiredtype
ca_certsyesDER编码的X.509 TLS证书链的路径或字节。
mandatoryno控制客户端是否必须进行身份验证的布尔值。

当允许双向TLS时,mtls::Certificate请求守卫可以用来校验客户端证书。

例子:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/tls

Worker

worker配置项前面说过,用于处理并行(parallel)任务执行,注意是并行,并发这里不做限制。

补充下:并行可以理解为多个人处理多个事件,并发则是一个人处理多个事件。

这个配置项只能通过ROCKET_WORKERS环境变量或者Rocket.toml来配置,其它都只能使用默认的。

max_blocking参数限制异步runtime创建的线程的最大个数,和worker一样都不能配置默认的,不过和worker不一样的是,它空载(idling)的时候会终止。另外512的值一般不要去改动它,除非硬件不支持。


Extracting Values

我们可以自行拓展通过Rocket::figment()暴露出来的配置,不过前提是实现了 Deserialize 。比如:

一般情况下,我们的配置项都是直接放到全局状态管理的,Rocket也考虑到了这一点,它提供了一个AdHoc fairing,这货我们前面接触过。它可以提取配置,输出错误以及存储到全局状态管理器中。比如:


Custom Providers

我们可以通过rocket::custom()自定义provider,它替换了对 rocket::build()的调用。

自定义provider可以基于Config::figment()或者 Config::default()

具体可见:https://docs.rs/figment/0.10/figment

另外需要注意,你可能需要直接用到figmentserde它们。rocket默认导出了它俩,所以直接引用即可。不过rocket导出的它俩可能是阉割版的,所以如果要用到一些特性,可能还得从包自身引入:

来看个自定义配置项的例子:

再来看个复杂一点的:

这里把能涉及到的配置文件都涉及到的,default的,global的,环境变量的。


实战(Pastebin)

接下来来搞个简单的文件存储系统,返回文件存储的路径。

Pastebin指的是文件存储站点,可以上传文件。

Finished Product

线上体验:https://paste.rs/

可以直接用curl来访问,比如:

我们需要先设计接口:

  • upload - #[post("/")] 通过requestbody获取原始数据并且返回文件存储的url
  • retrieve - #[get("/<id>")] 返回上传的文件内容,通过id访问。

Getting Started

先搭建环境

然后配置下根目录的Cargo.toml

然后回到rocket-pastebin中引入rocket

然后到main.rs中:

然后试运行下cargo run -p rocket-pastebin,应该是正常运行了。


Design

在开始实现之前,我们需要确定俩问题:

  1. 存储的文件放哪?一般情况下我们是需要单独买一个存储型服务器作为文件存储的地方,但是我们这个就只是一个demo,直接就放到项目里就行了,那么新建一个和src同级的文件夹upload作为静态资源。
  2. 如何命名,常见的就是根据当前时间戳来命名,或者根据时间戳再进一步hash处理。我们这里就简单的通过随机数创建id就行了。我们在src下创建一个paste_id.rs文件

随机生成一个数,然后创建PasteId实例自身存储id,到时候访问的时候直接从里面拿即可。 别忘了在toml中引入rand

最后在main.rs中引入


本来我们还需要一个index用来提供上传的入口,但是我们这里是基于curl的,所以就没必要了。

Retrieving Pastes

我们先来实现通过id获取上传内容的部分

通过传入的id拼接存储目录,然后获取对应文件的内容,最后响应出去。

别忘了注册:

不过这里还有个问题:full path disclosure attack。如果我们的文件中有些文件是私有的,不允许外面访问的,或者说需要一定的权限才能访问,那么这个时候我们就有可能会被攻击。

那么这个时候我们就需要搞个请求守卫用来解决这个问题,回到paste_id.rs文件中,我们需要给Paste实现FromParam

这里面的代码并没有体现出解决方案,实际上我们需要加上的代码是black list也就是黑名单,用来过滤需要权限访问的文件。

然后别忘了修改路由,

这样看就舒服很多了。


Uploading

首先,我们需要接收的肯定是数据流

Streaming Data

我们需要使用前面说过的 Data来做数据守卫,限制获取的量。

限制为128K大小。然后通过uri!这个宏生成路由路径,不过根据我们前面学的知识,这里我们还需要给retrieve的参数id的类型PasteId实现UriDisplayPath才行:

那么现在程序应该是可以跑了,我们运行下cargo run -p rocket-pastebin

然后我们需要借助curl来实现上传,关于curl如何在win10/win11使用的,我之前go的一篇文件中有说到,这里就不多说了,可以去翻看下。

我们现在rocket-pastebin根目录下创建一个test.txt文件,然后在里面随便写点东西

上传完之后我们再访问下

这样就可以了,虽然和粗糙。。。

完整代码可见:https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/pastebin


Help

又到了经典《I Need Help》(某褪色者口号)环节。

还是那句话,能去github上找/问就尽量去github上,不过提issue前务必确认没有相同的缺陷:https://github.com/SergioBenitez/Rocket/issues

另外你还可以加入聊天:https://chat.mozilla.org/#/room/#rocket:mozilla.org

或者: Matrix via Element

又或者: Kiwi IRC client

你还可以在这问问题:https://rocket.rs/v0.5-rc/guide/faq/


例子

https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples

或者你也可以看源码(?):https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples


总结

让我想想。。。

参考

  1. ^Rocket https://rocket.rs/v0.5-rc/guide/introduction/
  2. ^hello world https://rocket.rs/v0.5-rc/guide/getting-started/#hello-world
  3. ^LifeCycle https://rocket.rs/v0.5-rc/guide/overview/#lifecycle
  4. ^Routing https://rocket.rs/v0.5-rc/guide/overview/#routing
  5. ^mounting https://rocket.rs/v0.5-rc/guide/overview/#mounting
  6. ^Launching https://rocket.rs/v0.5-rc/guide/overview/#launching
  7. ^Future and Async https://rocket.rs/v0.5-rc/guide/overview/#futures-and-async
  8. ^Requests https://rocket.rs/v0.5-rc/guide/requests/
  9. ^Methods https://rocket.rs/v0.5-rc/guide/requests/#methods
  10. ^Dynamic Paths https://rocket.rs/v0.5-rc/guide/requests/#dynamic-paths
  11. ^FromParam https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromParam.html
  12. ^Forwarding https://rocket.rs/v0.5-rc/guide/requests/#forwarding
  13. ^FromRequest https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html
  14. ^Cookies https://rocket.rs/v0.5-rc/guide/requests/#cookies
  15. ^CookieJar https://api.rocket.rs/v0.5-rc/rocket/http/struct.CookieJar.html
  16. ^Format https://rocket.rs/v0.5-rc/guide/requests/#format
  17. ^Body Data https://rocket.rs/v0.5-rc/guide/requests/#body-data
  18. ^FromData https://api.rocket.rs/v0.5-rc/rocket/data/trait.FromData.html
  19. ^JSON https://api.rocket.rs/v0.5-rc/rocket/serde/json/struct.Json.html
  20. ^serde https://serde.rs/
  21. ^data https://api.rocket.rs/v0.5-rc/rocket/data/struct.Data.html
  22. ^ToByteUnit https://api.rocket.rs/v0.5-rc/rocket/data/trait.ToByteUnit.html
  23. ^Forms https://rocket.rs/v0.5-rc/guide/requests/#forms
  24. ^Form https://api.rocket.rs/v0.5-rc/rocket/form/struct.Form.html
  25. ^FromForm https://api.rocket.rs/v0.5-rc/rocket/form/trait.FromForm.html
  26. ^Form<Strict> https://api.rocket.rs/v0.5-rc/rocket/form/struct.Strict.html
  27. ^lenient https://api.rocket.rs/v0.5-rc/rocket/form/struct.Lenient.html
  28. ^Rocket::form::validate https://api.rocket.rs/v0.5-rc/rocket/form/validate/fn.range.html
  29. ^Contextual https://api.rocket.rs/v0.5-rc/rocket/form/struct.Contextual.html
  30. ^Context https://api.rocket.rs/v0.5-rc/rocket/form/struct.Context.html
  31. ^Query Strings https://rocket.rs/v0.5-rc/guide/requests/#query-strings
  32. ^Error Catchers https://rocket.rs/v0.5-rc/guide/requests/#error-catchers
  33. ^register() https://api.rocket.rs/v0.5-rc/rocket/struct.Rocket.html#method.register
  34. ^mount() https://api.rocket.rs/v0.5-rc/rocket/struct.Rocket.html#method.mount
  35. ^Responses https://rocket.rs/v0.5-rc/guide/responses/
  36. ^title-Responder https://rocket.rs/v0.5-rc/guide/responses/#responses
  37. ^trait-Responder https://api.rocket.rs/v0.5-rc/rocket/response/trait.Responder.html
  38. ^Status https://api.rocket.rs/v0.5-rc/rocket/response/status/
  39. ^custom responder https://rocket.rs/v0.5-rc/guide/responses/#custom-responders
  40. ^implementions https://rocket.rs/v0.5-rc/guide/responses/#implementations
  41. ^Rocket Responders https://rocket.rs/v0.5-rc/guide/responses/#rocket-responders
  42. ^stream! https://api.rocket.rs/v0.5-rc/rocket/response/stream/macro.stream.html
  43. ^EventStream https://api.rocket.rs/v0.5-rc/rocket/response/stream/struct.EventStream.html
  44. ^chat-demo https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/chat
  45. ^ReaderStream https://api.rocket.rs/v0.5-rc/rocket/response/stream/struct.ReaderStream.html
  46. ^TextStream https://docs.serde.rs/serde/trait.Serialize.html
  47. ^serde https://serde.rs/
  48. ^Serialize https://docs.serde.rs/serde/trait.Serialize.html
  49. ^Templates https://rocket.rs/v0.5-rc/guide/responses/#templates
  50. ^Template https://api.rocket.rs/v0.5-rc/rocket_dyn_templates/struct.Template.html
  51. ^Typed URIs https://rocket.rs/v0.5-rc/guide/responses/#typed-uris
  52. ^State https://rocket.rs/v0.5-rc/guide/state/
  53. ^Managed State https://rocket.rs/v0.5-rc/guide/state/#managed-state
  54. ^Request::guard() https://api.rocket.rs/v0.5-rc/rocket/struct.Request.html#method.guard
  55. ^Rocket::state() https://api.rocket.rs/v0.5-rc/rocket/struct.Rocket.html#method.state
  56. ^Request-Local State https://rocket.rs/v0.5-rc/guide/state/#request-local-state
  57. ^Databases https://rocket.rs/v0.5-rc/guide/state/#databases
  58. ^Fairings https://rocket.rs/v0.5-rc/guide/fairings/#fairings
  59. ^overview https://rocket.rs/v0.5-rc/guide/fairings/#overview
  60. ^implementing https://rocket.rs/v0.5-rc/guide/fairings/#implementing
  61. ^Ad-Hoc Fairings https://rocket.rs/v0.5-rc/guide/fairings/#ad-hoc-fairings
  62. ^testing https://rocket.rs/v0.5-rc/guide/testing/
  63. ^Local Dispatching https://rocket.rs/v0.5-rc/guide/testing/#local-dispatching
  64. ^validating response https://rocket.rs/v0.5-rc/guide/testing/#validating-responses
  65. ^Testing "Hello World" https://rocket.rs/v0.5-rc/guide/testing/#testing-hello-world
  66. ^Asynchronous Testing https://rocket.rs/v0.5-rc/guide/testing/#asynchronous-testing
  67. ^Codegen Debug https://rocket.rs/v0.5-rc/guide/testing/#codegen-debug
  68. ^Configuration https://rocket.rs/v0.5-rc/guide/configuration/#configuration

编辑于 2023-05-11 15:01・IP 属地广东