mirror of
https://github.com/actix/actix-website
synced 2024-11-23 16:31:08 +01:00
clean code add new cn site
This commit is contained in:
parent
cf8f8c9b3e
commit
bc5989fa38
@ -13,10 +13,6 @@ baseURL = "https://actix.rs"
|
||||
languageCode = "en-US"
|
||||
languageName = "English"
|
||||
weight = 1
|
||||
[languages.cn]
|
||||
languageCode = "zh-CN"
|
||||
languageName = "中文"
|
||||
weight = 2
|
||||
|
||||
[params]
|
||||
actixVersion = "0.5"
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Actix中文
|
||||
---
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 旧
|
||||
description: 2018-01-01
|
||||
menu: blog_2018
|
||||
weight: 999
|
||||
---
|
||||
|
||||
# Title
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 新
|
||||
description: 2018-06-11
|
||||
menu: blog_2018
|
||||
weight: 998
|
||||
---
|
||||
|
||||
# Title
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
title: 博客
|
||||
description: 快乐的Actix之旅
|
||||
menu:
|
||||
blog_intro:
|
||||
name: 欢迎
|
||||
weight: 1
|
||||
---
|
||||
|
||||
欢迎开始快乐的Actix之旅, 你可以从左侧的时间点选择开始。
|
@ -1,18 +0,0 @@
|
||||
---
|
||||
title: 源码
|
||||
description: 浏览,下载源码
|
||||
---
|
||||
|
||||
# 浏览源码
|
||||
|
||||
所有的Actix代码都是开源的,可以在我们的github组织中找到: [actix
|
||||
on github](https://github.com/actix)
|
||||
|
||||
以下是最重要的项目以及与其github存储库和相关资源的链接:
|
||||
|
||||
* [actix](https://github.com/actix/actix) ([issues](https://github.com/actix/actix/issues), [ci](https://travis-ci.org/actix/actix), [crate](https://crates.io/crates/actix), [api docs](https://docs.rs/actix))
|
||||
* [actix-web](https://github.com/actix/actix-web) ([issues](https://github.com/actix/actix-web/issues), [ci](https://travis-ci.org/actix/actix-web), [crate](https://crates.io/crates/actix-web), [api docs](https://docs.rs/actix-web))
|
||||
* [example code](https://github.com/actix/examples)
|
||||
* [this website](https://github.com/actix/actix-website)
|
||||
|
||||
Actix在MIT和Apache 2下获得双重许可。[许可证文本](license/).
|
@ -1,201 +0,0 @@
|
||||
---
|
||||
title: License
|
||||
---
|
||||
|
||||
Actix is dual licensed under MIT and Apache licenses.
|
||||
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2017 Nikolay Kim
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Apache License
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
title: 社区
|
||||
description: 人生中最美好的事物就是分享
|
||||
---
|
||||
|
||||
# 加入我们
|
||||
|
||||
想与其他人讨论问题吗?Actix的你最好的起点是:
|
||||
|
||||
- [gitter](https://gitter.im/actix/actix)频道
|
||||
- [reddit](https://www.reddit.com/r/actix/)社区
|
||||
- QQ群:570065685
|
||||
|
||||
如果你发现了一个bug,最好直接去
|
||||
[github](https://github.com/actix) . 有2个主要仓库:
|
||||
|
||||
- [actix](https://github.com/actix/actix) Rust强大的actor系统
|
||||
- [actix-web](https://github.com/actix/actix-web) Rust高水平Web框架。
|
||||
|
||||
我们是一个热情的社区,所以不要害怕参与. [我们遵守的行为准则](coc/).
|
||||
|
||||
# 案例
|
||||
如果你还没有准备好,这里有一些案例
|
||||
|
||||
- [OUISRC/muro](https://github.com/OUISRC/muro) : The interest and community for internet .(reddit clone)
|
||||
- [swipe-app/swipe-server](https://github.com/swipe-app/swipe-server) : Swipe app api with actix-web and graphql
|
||||
- [yew-actix-protobuf-sample](https://github.com/havarnov/yew-actix-protobuf-sample) : web app written in yew and actix.
|
||||
- [yinyanlv/partner](https://github.com/yinyanlv/partner) : 一个私人生活辅助系统,前后端分离。前端:angular6+,material-design;后端:actix-web,diesel,mysql,redis
|
||||
- [yinyanlv/partner-client](https://github.com/yinyanlv/partner-client) : partner项目的前端仓库
|
@ -1,49 +0,0 @@
|
||||
---
|
||||
title: Contributor Covenant Code of Conduct
|
||||
description:
|
||||
---
|
||||
|
||||
# Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
# Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
# Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
# Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
# Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
# Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
@ -1,40 +0,0 @@
|
||||
---
|
||||
title: 安装
|
||||
menu: docs_intro
|
||||
weight: 110
|
||||
---
|
||||
|
||||
# 安装 Rust
|
||||
|
||||
既然actix-web是一个Rust框架,你需要Rust来使用它。如果您还没有它,我们建议您使用rustup来管理您的Rust安装。该[官员Rust指南](https://doc.rust-lang.org/book/second-edition/ch01-01-installation.html) 有精彩的入门部分。
|
||||
|
||||
我们目前至少需要Rust 1.24,因此请确保您运行的rustup update 是最新且最好的Rust版本。特别是本指南将假定您实际运行Rust 1.26或更高版本。
|
||||
|
||||
|
||||
# 安装 `actix-web`
|
||||
|
||||
感谢Rust的cargo包管理,您不需要明确安装 actix-web。只需要声明依赖就行了。对于想要使用actix-web的开发版本的情况,您可以直接依赖git存储库。
|
||||
|
||||
Release版本:
|
||||
|
||||
```ini
|
||||
[dependencies]
|
||||
actix-web = "{{< actix-version "actix-web" >}}"
|
||||
```
|
||||
|
||||
Development 版本:
|
||||
|
||||
```ini
|
||||
[dependencies]
|
||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
||||
```
|
||||
|
||||
# 深入
|
||||
|
||||
在这里有两条你可以选择的路径。你可以沿着指南或者如果你非常不耐烦,你可能想看看我们 广泛的[示例库](https://github.com/actix/examples)并运行包含的示例。这里举例说明如何运行包含的basics 示例:
|
||||
|
||||
```
|
||||
git clone https://github.com/actix/examples
|
||||
cd examples/basics
|
||||
cargo run
|
||||
```
|
@ -1,14 +0,0 @@
|
||||
---
|
||||
title: 文档
|
||||
description: 指导您使用actix构建应用程序
|
||||
menu:
|
||||
docs_intro:
|
||||
name: 欢迎
|
||||
weight: 10
|
||||
---
|
||||
|
||||
# 欢迎来到Actix
|
||||
|
||||
Actix是您用Rust开发Web服务的大门,本文档将引导您。
|
||||
|
||||
本文档目前主要介绍actix-web构建在[actix](https://docs.rs/actix)的actor框架和 [Tokio](https://tokio.rs/)异步IO系统之上的高级Web框架。这是从API稳定的角度出发。
|
@ -1,75 +0,0 @@
|
||||
---
|
||||
title: 应用
|
||||
menu: docs_basics
|
||||
weight: 140
|
||||
---
|
||||
|
||||
# 编写应用
|
||||
|
||||
actix-web提供了用Rust构建Web服务器和应用程序的各种基础类型。它提供了路由,中间件,请求的预处理,响应的后处理,websocket协议处理,multipart流,等等。
|
||||
|
||||
所有actix web服务器都是围绕该App实例构建的。它用于为资源和中间件注册路由。它还存储同一应用程序中所有处理程序之间共享的应用程序状态
|
||||
|
||||
应用程序充当所有路由的命名空间,即特定应用程序的所有路由具有相同的url路径前缀。应用程序前缀总是包含一个前导的“/”斜杠。如果提供的前缀不包含前导斜杠,则会自动插入。前缀应该由路径值组成。
|
||||
|
||||
对于具有前缀的应用程序/app,与任何请求路径中有/app,/app/或/app/test匹配; 然而,路径/application不匹配。
|
||||
|
||||
{{< include-example example="application" section="make_app" >}}
|
||||
|
||||
在此示例中,将创建具有/app前缀和index.html资源的应用。该资源可通过/app/index.html路径获得。
|
||||
|
||||
>有关更多信息,请查看[URL Dispatch](../url-dispatch)部分。
|
||||
|
||||
|
||||
一台服务器服务多个应用:
|
||||
|
||||
{{< include-example example="application" section="run_server" >}}
|
||||
|
||||
所有/app1请求路由到第一个应用程序,/app2到第二个,所有其他到第三个。 应用程序根据注册顺序进行匹配。如果具有更通用的前缀的应用程序在不通用的应用程序之前注册,它将有效地阻止较不通用的应用程序匹配。例如,如果App将前缀"/"注册为第一个应用程序,它将匹配所有传入的请求。
|
||||
|
||||
## 状态
|
||||
|
||||
同一应用程序内应用程序状态被所有路由和资源共享。当使用http actor时,状态可以HttpRequest::state()作为只读访问,但内部可变性RefCell可用于实现状态可变性。状态也可用于路由匹配谓词和中间件。
|
||||
|
||||
我们来编写一个使用共享状态的简单应用程序。我们打算将请求计数存储在状态中:
|
||||
|
||||
{{< include-example example="application" file="state.rs" section="setup" >}}
|
||||
|
||||
应用程序需要通过初始化状态来初始化。
|
||||
|
||||
{{< include-example example="application" file="state.rs" section="make_app" >}}
|
||||
|
||||
> **注意**:http服务器接受应用程序工厂而不是应用程序实例。Http服务器为每个线程构造一个应用程序实例,因此应用程序状态必须多次构建。如果你想在不同线程之间共享状态,应该使用共享对象,例如Arc。应用程序状态并不需要是Send和Sync,但应用程序的工厂必须是Send+ Sync。
|
||||
|
||||
要启动以前的应用程序,请为其创建闭包:
|
||||
|
||||
{{< include-example example="application" file="state.rs" section="start_app" >}}
|
||||
|
||||
## 结合不同状态的应用程序
|
||||
|
||||
将多个应用程序与不同状态组合也是可能的。
|
||||
|
||||
[server::new](https://docs.rs/actix-web/*/actix_web/server/fn.new.html)需要handler具有单一类型。
|
||||
|
||||
使用[App::boxed](https://docs.rs/actix-web/*/actix_web/struct.App.html#method.boxed)方法可以轻松解决此限制,该方法可将App转换为boxed trait object。
|
||||
|
||||
{{< include-example example="application" file="state.rs" section="combine" >}}
|
||||
|
||||
## 使用应用程序前缀来组合应用程序
|
||||
|
||||
该App::prefix()方法允许设置特定的应用程序前缀。此前缀表示将添加到所有资源模式的资源前缀通过资源配置。 这可以用来帮助挂载一组路由在不同的
|
||||
地点比所包含的可调用作者的意图仍保持原样资源名称。
|
||||
|
||||
例如:
|
||||
|
||||
{{< include-example example="url-dispatch" file="prefix.rs" section="prefix" >}}
|
||||
|
||||
在上面的示例中,`show_users`路由将具有/users/show的有效路由模式, 而不是/ show,因为应用程序的前缀参数将预先添加到该模式。只有当URL路径为/users/show,并且HttpRequest.url_for()路由名称show_users调用该函数时,路由才会匹配,它将生成具有相同路径的URL。
|
||||
|
||||
## 应用程序谓词和虚拟主机
|
||||
|
||||
您可以将谓词看作一个接受请求对象引用并返回true或false的简单函数。形式上,谓词是实现[`Predicate`](https://docs.rs/actix-web/0.6.10/actix_web/pred/trait.Predicate.html) trait的任何对象 。Actix提供了几个谓词,你可以检查 API文档的[functions section](https://docs.rs/actix-web/0.6.10/actix_web/pred/index.html#functions)部分。
|
||||
|
||||
任何这些谓词都可以用于App::filter()方法。提供的谓词之一是Host,它可以根据请求的主机信息用作应用程序的过滤器。
|
||||
|
||||
{{< include-example example="application" file="vh.rs" section="vh" >}}
|
@ -1,65 +0,0 @@
|
||||
---
|
||||
title: 自动重加载
|
||||
menu: docs_patterns
|
||||
weight: 1000
|
||||
---
|
||||
|
||||
# 自动重新加载开发服务器
|
||||
|
||||
在开发过程中,cargo自动重新编译变更代码会非常方便。这可以通过使用[cargo-watch](https://github.com/passcod/cargo-watch)来完成 。由于actix应用程序通常会绑定到端口以侦听传入的HTTP请求,因此将它与[listenfd](https://crates.io/crates/listenfd)和[systemfd](https://github.com/mitsuhiko/systemfd)实用程序结合起来以确保套接字在应用程序编译和重新加载时保持打开状态是有意义的。
|
||||
|
||||
`systemfd`将打开一个套接字并将其传递给`cargo-watch`以监视更改,然后调用编译器并运行您的actix应用程序。actix应用程序将使用`listenfd`获取 `systemfd`打开的套接字systemfd。
|
||||
|
||||
## 需要的二进制文件
|
||||
|
||||
对于自动重新加载体验,您需要安装`cargo-watch`和 `systemfd`。两者都用`cargo install`安装
|
||||
|
||||
```
|
||||
cargo install systemfd cargo-watch
|
||||
```
|
||||
|
||||
## 修改代码
|
||||
|
||||
此外,您需要稍微修改您的actix应用程序,以便它可以获取由`systemfd`打开的外部套接字。将`listenfd`添加到您的应用依赖项中:
|
||||
|
||||
```ini
|
||||
[dependencices]
|
||||
listenfd = "0.3"
|
||||
```
|
||||
|
||||
然后修改您的服务器代码以仅以`bind`作为回调:
|
||||
|
||||
```rust
|
||||
extern crate listenfd;
|
||||
|
||||
use listenfd::ListenFd;
|
||||
use actix_web::{server, App, HttpRequest, Responder};
|
||||
|
||||
fn index(_req: HttpRequest) -> impl Responder {
|
||||
"Hello World!"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut listenfd = ListenFd::from_env();
|
||||
let mut server = server::new(|| {
|
||||
App::new()
|
||||
.resource("/", |r| r.f(index))
|
||||
});
|
||||
|
||||
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
|
||||
server.listen(l)
|
||||
} else {
|
||||
server.bind("127.0.0.1:3000").unwrap()
|
||||
};
|
||||
|
||||
server.run();
|
||||
}
|
||||
```
|
||||
|
||||
## 运行服务器
|
||||
|
||||
现在运行开发服务器调用这个命令:
|
||||
|
||||
```
|
||||
systemfd --no-pid -s http::3000 -- cargo watch -x run
|
||||
```
|
@ -1,119 +0,0 @@
|
||||
---
|
||||
title: 数据库
|
||||
menu: docs_patterns
|
||||
weight: 1010
|
||||
---
|
||||
|
||||
# Diesel
|
||||
|
||||
目前,Diesel 1.0不支持异步操作,但可以将actix同步actor系统用作数据库接口API。从技术上讲,同步actor是worker风格的actor。
|
||||
多个同步actors可以并行运行并处理来自同一队列的消息。同步actors以mpsc模式工作。
|
||||
|
||||
我们来创建一个简单的数据库api,它可以将一个新的 user row插入到SQLite表中。我们必须定义一个同步actor和该actor将使用的连接。其他数据库可以使用相同的方法。
|
||||
|
||||
```rust
|
||||
use actix::prelude::*;
|
||||
|
||||
struct DbExecutor(SqliteConnection);
|
||||
|
||||
impl Actor for DbExecutor {
|
||||
type Context = SyncContext<Self>;
|
||||
}
|
||||
```
|
||||
|
||||
这是我们actor的定义。现在,我们必须定义创建用户消息和响应。
|
||||
|
||||
```rust
|
||||
struct CreateUser {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Message for CreateUser {
|
||||
type Result = Result<User, Error>;
|
||||
}
|
||||
```
|
||||
|
||||
我们可以向演员发送`CreateUser`消息`DbExecutor` actor,因此我们将收到一个 `User`实例。接下来,我们必须为此消息定义处理程序实现。
|
||||
|
||||
```rust
|
||||
impl Handler<CreateUser> for DbExecutor {
|
||||
type Result = Result<User, Error>;
|
||||
|
||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
||||
{
|
||||
use self::schema::users::dsl::*;
|
||||
|
||||
// Create insertion model
|
||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
||||
let new_user = models::NewUser {
|
||||
id: &uuid,
|
||||
name: &msg.name,
|
||||
};
|
||||
|
||||
// normal diesel operations
|
||||
diesel::insert_into(users)
|
||||
.values(&new_user)
|
||||
.execute(&self.0)
|
||||
.expect("Error inserting person");
|
||||
|
||||
let mut items = users
|
||||
.filter(id.eq(&uuid))
|
||||
.load::<models::User>(&self.0)
|
||||
.expect("Error loading person");
|
||||
|
||||
Ok(items.pop().unwrap())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
仅此而已!现在,我们可以使用来在于任何http处理程序或中间件的DbExecutor actor。我们需要的只是启动DbExecutor actors并将地址存储在http处理程序可以访问的状态中。
|
||||
|
||||
```rust
|
||||
/// This is state where we will store *DbExecutor* address.
|
||||
struct State {
|
||||
db: Addr<Syn, DbExecutor>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sys = actix::System::new("diesel-example");
|
||||
|
||||
// Start 3 parallel db executors
|
||||
let addr = SyncArbiter::start(3, || {
|
||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||
});
|
||||
|
||||
// Start http server
|
||||
HttpServer::new(move || {
|
||||
App::with_state(State{db: addr.clone()})
|
||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.start().unwrap();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8080");
|
||||
let _ = sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
我们将在请求处理程序中使用该地址。处理程序返回future对象; 因此,我们异步接收响应消息。`Route::a()`必须用于异步处理注册。
|
||||
|
||||
```rust
|
||||
/// Async handler
|
||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
let name = &req.match_info()["name"];
|
||||
|
||||
// Send message to `DbExecutor` actor
|
||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
||||
.from_err()
|
||||
.and_then(|res| {
|
||||
match res {
|
||||
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
||||
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||
}
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
```
|
||||
|
||||
[diesel directory](https://github.com/actix/examples/tree/master/diesel/)提供了一个完整的示例。
|
||||
|
||||
有关同步actors的更多信息可以在[actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html)文档中找到 。
|
@ -1,238 +0,0 @@
|
||||
---
|
||||
title: Errors
|
||||
menu: docs_advanced
|
||||
weight: 180
|
||||
---
|
||||
|
||||
# Errors
|
||||
|
||||
Actix uses its own [`actix_web::error::Error`][actixerror] type and
|
||||
[`actix_web::error::ResponseError`][responseerror] trait for error handling
|
||||
from web handlers.
|
||||
|
||||
If a handler returns an `Error` (referring to the [general Rust trait
|
||||
`std::error::Error`][stderror]) in a `Result` that also implements the
|
||||
`ResponseError` trait, actix will render that error as an HTTP response.
|
||||
`ResponseError` has a single function called `error_response()` that returns
|
||||
`HttpResponse`:
|
||||
|
||||
```rust
|
||||
pub trait ResponseError: Fail {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A `Responder` coerces compatible `Result`s into HTTP responses:
|
||||
|
||||
```rust
|
||||
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
||||
```
|
||||
|
||||
`Error` in the code above is actix's error definition, and any errors that
|
||||
implement `ResponseError` can be converted to one automatically.
|
||||
|
||||
Actix-web provides `ResponseError` implementations for some common non-actix
|
||||
errors. For example, if a handler responds with an `io::Error`, that error is
|
||||
converted into an `HttpInternalServerError`:
|
||||
|
||||
```rust
|
||||
use std::io;
|
||||
|
||||
fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
|
||||
Ok(fs::NamedFile::open("static/index.html")?)
|
||||
}
|
||||
```
|
||||
|
||||
See [the actix-web API documentation](responseerrorimpls) for a full list of
|
||||
foreign implementations for `ResponseError`.
|
||||
|
||||
## An example of a custom error response
|
||||
|
||||
Here's an example implementation for `ResponseError`:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
#[fail(display="my error")]
|
||||
struct MyError {
|
||||
name: &'static str
|
||||
}
|
||||
|
||||
// Use default implementation for `error_response()` method
|
||||
impl error::ResponseError for MyError {}
|
||||
|
||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
||||
Err(MyError{name: "test"})
|
||||
}
|
||||
```
|
||||
|
||||
`ResponseError` has a default implementation for `error_response()` that will
|
||||
render a *500* (internal server error), and that's what will happen when the
|
||||
`index` handler executes above.
|
||||
|
||||
Override `error_response()` to produce more useful results:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate failure;
|
||||
use actix_web::{App, HttpRequest, HttpResponse, http, error};
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
enum MyError {
|
||||
#[fail(display="internal error")]
|
||||
InternalError,
|
||||
#[fail(display="bad request")]
|
||||
BadClientData,
|
||||
#[fail(display="timeout")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl error::ResponseError for MyError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
MyError::InternalError => HttpResponse::new(
|
||||
http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||
MyError::BadClientData => HttpResponse::new(
|
||||
http::StatusCode::BAD_REQUEST),
|
||||
MyError::Timeout => HttpResponse::new(
|
||||
http::StatusCode::GATEWAY_TIMEOUT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
||||
Err(MyError::BadClientData)
|
||||
}
|
||||
```
|
||||
|
||||
# Error helpers
|
||||
|
||||
Actix provides a set of error helper functions that are useful for generating
|
||||
specific HTTP error codes from other errors. Here we convert `MyError`, which
|
||||
doesn't implement the `ResponseError` trait, to a *400* (bad request) using
|
||||
`map_err`:
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
use actix_web::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MyError {
|
||||
name: &'static str
|
||||
}
|
||||
|
||||
fn index(req: HttpRequest) -> Result<&'static str> {
|
||||
let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
|
||||
|
||||
Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?)
|
||||
}
|
||||
```
|
||||
|
||||
See the [API documentation for actix-web's `error` module][errorhelpers] for a
|
||||
full list of available error helpers.
|
||||
|
||||
# Compatibility with failure
|
||||
|
||||
Actix-web provides automatic compatibility with the [failure] library so that
|
||||
errors deriving `fail` will be converted automatically to an actix error. Keep
|
||||
in mind that those errors will render with the default *500* status code unless you
|
||||
also provide your own `error_response()` implementation for them.
|
||||
|
||||
# Error logging
|
||||
|
||||
Actix logs all errors at the `WARN` log level. If an application's log level is
|
||||
set to `DEBUG` and `RUST_BACKTRACE` is enabled, the backtrace is also logged.
|
||||
These are configurable with environmental variables:
|
||||
|
||||
```
|
||||
>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
|
||||
```
|
||||
|
||||
The `Error` type uses the cause's error backtrace if available. If the
|
||||
underlying failure does not provide a backtrace, a new backtrace is constructed
|
||||
pointing to the point where the conversion occurred (rather than the origin of
|
||||
the error).
|
||||
|
||||
# Recommended practices in error handling
|
||||
|
||||
It might be useful to think about dividing the errors an application produces
|
||||
into two broad groups: those which are intended to be be user-facing, and those
|
||||
which are not.
|
||||
|
||||
An example of the former is that I might use failure to specify a `UserError`
|
||||
enum which encapsulates a `ValidationError` to return whenever a user sends bad
|
||||
input:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate failure;
|
||||
use actix_web::{HttpResponse, http, error};
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
enum UserError {
|
||||
#[fail(display="Validation error on field: {}", field)]
|
||||
ValidationError {
|
||||
field: String,
|
||||
}
|
||||
}
|
||||
|
||||
impl error::ResponseError for UserError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
UserError::ValidationError { .. } => HttpResponse::new(
|
||||
http::StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will behave exactly as intended because the error message defined with
|
||||
`display` is written with the explicit intent to be read by a user.
|
||||
|
||||
However, sending back an error's message isn't desirable for all errors --
|
||||
there are many failures that occur in a server environment where we'd probably
|
||||
want the specifics to be hidden from the user. For example, if a database goes
|
||||
down and client libraries start producing connect timeout errors, or if an HTML
|
||||
template was improperly formatted and errors when rendered. In these cases, it
|
||||
might be preferable to map the errors to a generic error suitable for user
|
||||
consumption.
|
||||
|
||||
Here's an example that maps an internal error to a user-facing `InternalError`
|
||||
with a custom message:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate failure;
|
||||
use actix_web::{App, HttpRequest, HttpResponse, http, error, fs};
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
enum UserError {
|
||||
#[fail(display="An internal error occurred. Please try again later.")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl error::ResponseError for UserError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
UserError::InternalError => HttpResponse::new(
|
||||
http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn index(_req: HttpRequest) -> Result<&'static str, UserError> {
|
||||
fs::NamedFile::open("static/index.html").map_err(|_e| UserError::InternalError)?;
|
||||
Ok("success!")
|
||||
}
|
||||
```
|
||||
|
||||
By dividing errors into those which are user facing and those which are not, we
|
||||
can ensure that we don't accidentally expose users to errors thrown by
|
||||
application internals which they weren't meant to see.
|
||||
|
||||
[actixerror]: ../../actix-web/actix_web/error/struct.Error.html
|
||||
[errorhelpers]: ../../actix-web/actix_web/error/index.html#functions
|
||||
[failure]: https://github.com/rust-lang-nursery/failure
|
||||
[responseerror]: ../../actix-web/actix_web/error/trait.ResponseError.html
|
||||
[responseerrorimpls]: ../../actix-web/actix_web/error/trait.ResponseError.html#foreign-impls
|
||||
[stderror]: https://doc.rust-lang.org/std/error/trait.Error.html
|
@ -1,284 +0,0 @@
|
||||
---
|
||||
title: Extractors
|
||||
menu: docs_basics
|
||||
weight: 170
|
||||
---
|
||||
|
||||
# Type-safe information extraction
|
||||
|
||||
Actix provides facility for type-safe request information extraction. By default,
|
||||
actix provides several extractor implementations.
|
||||
|
||||
# Accessing Extractors
|
||||
|
||||
How you access an Extractor depends on whether you are using a handler function
|
||||
or a custom Handler type.
|
||||
|
||||
## Within Handler Functions
|
||||
|
||||
An Extractor can be passed to a handler function as a function parameter
|
||||
*or* accessed within the function by calling the ExtractorType::<...>::extract(req)
|
||||
function.
|
||||
```rust
|
||||
|
||||
// Option 1: passed as a parameter to a handler function
|
||||
fn index((params, info): (Path<(String, String,)>, Json<MyInfo>)) -> HttpResponse {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
// Option 2: accessed by calling extract() on the Extractor
|
||||
|
||||
use actix_web::FromRequest;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
let params = Path::<(String, String)>::extract(&req);
|
||||
let info = Json::<MyInfo>::extract(&req);
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Within Custom Handler Types
|
||||
|
||||
Like a handler function, a custom Handler type can *access* an Extractor by
|
||||
calling the ExtractorType::<...>::extract(&req) function. An Extractor
|
||||
*cannot* be passed as a parameter to a custom Handler type because a custom
|
||||
Handler type must follow the ``handle`` function signature specified by the
|
||||
Handler trait it implements.
|
||||
|
||||
```rust
|
||||
|
||||
struct MyHandler(String);
|
||||
|
||||
impl<S> Handler<S> for MyHandler {
|
||||
type Result = HttpResponse;
|
||||
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let params = Path::<(String, String)>::extract(&req);
|
||||
let info = Json::<MyInfo>::extract(&req);
|
||||
|
||||
...
|
||||
|
||||
HttpResponse::Ok().into()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# Path
|
||||
|
||||
[*Path*](../../actix-web/actix_web/struct.Path.html) provides information that can
|
||||
be extracted from the Request's path. You can deserialize any variable
|
||||
segment from the path.
|
||||
|
||||
For instance, for resource that registered for the `/users/{userid}/{friend}` path
|
||||
two segments could be deserialized, `userid` and `friend`. These segments
|
||||
could be extracted into a `tuple`, i.e. `Path<(u32, String)>` or any structure
|
||||
that implements the `Deserialize` trait from the *serde* crate.
|
||||
|
||||
```rust
|
||||
use actix_web::{App, Path, Result, http};
|
||||
|
||||
/// extract path info from "/users/{userid}/{friend}" url
|
||||
/// {userid} - - deserializes to a u32
|
||||
/// {friend} - deserializes to a String
|
||||
fn index(info: Path<(u32, String)>) -> Result<String> {
|
||||
Ok(format!("Welcome {}! {}", info.1, info.0))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/users/{userid}/{friend}", // <- define path parameters
|
||||
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
Remember! A handler function that uses extractors has to be registered using the
|
||||
[*Route::with()*](../../actix-web/actix_web/dev/struct.Route.html#method.with) method.
|
||||
|
||||
It is also possible to extract path information to a specific type that
|
||||
implements the `Deserialize` trait from *serde*. Here is an equivalent example that uses *serde*
|
||||
instead of a *tuple* type.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Path, Result, http};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
userid: u32,
|
||||
friend: String,
|
||||
}
|
||||
|
||||
/// extract path info using serde
|
||||
fn index(info: Path<Info>) -> Result<String> {
|
||||
Ok(format!("Welcome {}!", info.friend))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/users/{userid}/{friend}", // <- define path parameters
|
||||
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
# Query
|
||||
|
||||
Same can be done with the request's query.
|
||||
The [*Query*](../../actix-web/actix_web/struct.Query.html)
|
||||
type provides extraction functionality. Underneath it uses *serde_urlencoded* crate.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Query, http};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
username: String,
|
||||
}
|
||||
|
||||
// this handler get called only if the request's query contains `username` field
|
||||
fn index(info: Query<Info>) -> String {
|
||||
format!("Welcome {}!", info.username)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/index.html",
|
||||
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
# Json
|
||||
|
||||
[*Json*](../../actix-web/actix_web/struct.Json.html) allows to deserialize
|
||||
a request body into a struct. To extract typed information from a request's body,
|
||||
the type `T` must implement the `Deserialize` trait from *serde*.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Json, Result, http};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// deserialize `Info` from request's body
|
||||
fn index(info: Json<Info>) -> Result<String> {
|
||||
Ok(format!("Welcome {}!", info.username))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/index.html",
|
||||
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
Some extractors provide a way to configure the extraction process. Json extractor
|
||||
[*JsonConfig*](../../actix-web/actix_web/dev/struct.JsonConfig.html) type for configuration.
|
||||
When you register a handler using `Route::with()`, it returns a configuration instance. In case of
|
||||
a *Json* extractor it returns a *JsonConfig*. You can configure the maximum size of the json
|
||||
payload as well as a custom error handler function.
|
||||
|
||||
The following example limits the size of the payload to 4kb and uses a custom error hander.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Json, HttpResponse, Result, http, error};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// deserialize `Info` from request's body, max payload size is 4kb
|
||||
fn index(info: Json<Info>) -> Result<String> {
|
||||
Ok(format!("Welcome {}!", info.username))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/index.html", |r| {
|
||||
r.method(http::Method::POST)
|
||||
.with(index)
|
||||
.limit(4096) // <- change json extractor configuration
|
||||
.error_handler(|err, req| { // <- create custom error response
|
||||
error::InternalError::from_response(
|
||||
err, HttpResponse::Conflict().finish()).into()
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
# Form
|
||||
|
||||
At the moment only url-encoded forms are supported. The url-encoded body
|
||||
could be extracted to a specific type. This type must implement
|
||||
the `Deserialize` trait from the *serde* crate.
|
||||
|
||||
[*FormConfig*](../../actix-web/actix_web/dev/struct.FormConfig.html) allows
|
||||
configuring the extraction process.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Form, Result};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FormData {
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// extract form data using serde
|
||||
/// this handler gets called only if the content type is *x-www-form-urlencoded*
|
||||
/// and the content of the request could be deserialized to a `FormData` struct
|
||||
fn index(form: Form<FormData>) -> Result<String> {
|
||||
Ok(format!("Welcome {}!", form.username))
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
# Multiple extractors
|
||||
|
||||
Actix provides extractor implementations for tuples (up to 10 elements)
|
||||
whose elements implement `FromRequest`.
|
||||
|
||||
For example we can use a path extractor and a query extractor at the same time.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Query, Path, http};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
username: String,
|
||||
}
|
||||
|
||||
fn index((path, query): (Path<(u32, String)>, Query<Info>)) -> String {
|
||||
format!("Welcome {}!", query.username)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/users/{userid}/{friend}", // <- define path parameters
|
||||
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
# Other
|
||||
|
||||
Actix also provides several other extractors:
|
||||
|
||||
* [*State*](../../actix-web/actix_web/struct.State.html) - If you need
|
||||
access to an application state. This is similar to a `HttpRequest::state()`.
|
||||
* *HttpRequest* - *HttpRequest* itself is an extractor which returns self,
|
||||
in case you need access to the request.
|
||||
* *String* - You can convert a request's payload to a *String*.
|
||||
[*Example*](../../actix-web/actix_web/trait.FromRequest.html#example-1)
|
||||
is available in doc strings.
|
||||
* *bytes::Bytes* - You can convert a request's payload into *Bytes*.
|
||||
[*Example*](../../actix-web/actix_web/trait.FromRequest.html#example)
|
||||
is available in doc strings.
|
@ -1,40 +0,0 @@
|
||||
---
|
||||
title: 开始
|
||||
menu: docs_basics
|
||||
weight: 130
|
||||
---
|
||||
|
||||
# 开始
|
||||
|
||||
我们来编写第一个actix web应用程序!
|
||||
|
||||
## Hello, world!
|
||||
|
||||
首先创建一个新的基于二进制的Cargo项目并进入新目录:
|
||||
|
||||
```bash
|
||||
cargo new hello-world
|
||||
cd hello-world
|
||||
```
|
||||
|
||||
现在,确保actix-web的Cargo.toml 包含以下项目依赖关系:
|
||||
|
||||
```ini
|
||||
[dependencies]
|
||||
actix-web = "{{< actix-version "actix-web" >}}"
|
||||
```
|
||||
为了实现一个Web服务器,我们首先需要创建一个请求处理程序。请求处理函数接受一个HttpRequest实例作为其唯一参数,并返回一个可转换为HttpResponse的类型:
|
||||
|
||||
|
||||
文件名: `src/main.rs`
|
||||
|
||||
{{< include-example example="getting-started" section="setup" >}}
|
||||
|
||||
接下来,创建一个Application实例,并将请求处理程序与应用程序的resource一起注册在特定HTTP方法和路径,然后,应用程序实例可以用于HttpServer来侦听将传入的连接。服务器接受一个应该返回一个HttpHandler实例的函数 。简单来说server::new可以使用了,它是HttpServer::new的简写:
|
||||
|
||||
{{< include-example example="getting-started" section="main" >}}
|
||||
|
||||
|
||||
仅此而已!现在编译并运行该程序cargo run。去http://localhost:8088 看结果。
|
||||
|
||||
如果你想要在开发过程中重新编译后自动重新加载服务器。请查看[自动重新加载模式](../autoreload/)。
|
@ -1,292 +0,0 @@
|
||||
---
|
||||
title: Handlers
|
||||
menu: docs_basics
|
||||
weight: 160
|
||||
---
|
||||
|
||||
# Request Handlers
|
||||
|
||||
A request handler can be any object that implements
|
||||
[*Handler*](../../actix-web/actix_web/dev/trait.Handler.html) trait.
|
||||
|
||||
Request handling happens in two stages. First the handler object is called,
|
||||
returning any object that implements the
|
||||
[*Responder*](../../actix-web/actix_web/trait.Responder.html#foreign-impls) trait.
|
||||
Then, `respond_to()` is called on the returned object, converting itself to a `AsyncResult` or `Error`.
|
||||
|
||||
By default actix provides `Responder` implementations for some standard types,
|
||||
such as `&'static str`, `String`, etc.
|
||||
|
||||
> For a complete list of implementations, check
|
||||
> [*Responder documentation*](../../actix-web/actix_web/trait.Responder.html#foreign-impls).
|
||||
|
||||
Examples of valid handlers:
|
||||
|
||||
```rust
|
||||
fn index(req: HttpRequest) -> &'static str {
|
||||
"Hello world!"
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
fn index(req: HttpRequest) -> String {
|
||||
"Hello world!".to_owned()
|
||||
}
|
||||
```
|
||||
|
||||
You can also change the signature to return `impl Responder` which works well if more
|
||||
complex types are involved.
|
||||
|
||||
```rust
|
||||
fn index(req: HttpRequest) -> impl Responder {
|
||||
Bytes::from_static("Hello world!")
|
||||
}
|
||||
```
|
||||
|
||||
```rust,ignore
|
||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
*Handler* trait is generic over *S*, which defines the application state's type.
|
||||
Application state is accessible from the handler with the `HttpRequest::state()` method;
|
||||
however, state is accessible as a read-only reference. If you need mutable access to state,
|
||||
it must be implemented.
|
||||
|
||||
> **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes
|
||||
> mutable reference to *self*. **Beware**, actix creates multiple copies
|
||||
> of the application state and the handlers, unique for each thread. If you run your
|
||||
> application in several threads, actix will create the same amount as number of threads
|
||||
> of application state objects and handler objects.
|
||||
|
||||
Here is an example of a handler that stores the number of processed requests:
|
||||
|
||||
```rust
|
||||
use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};
|
||||
|
||||
struct MyHandler(usize);
|
||||
|
||||
impl<S> Handler<S> for MyHandler {
|
||||
type Result = HttpResponse;
|
||||
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
self.0 += 1;
|
||||
HttpResponse::Ok().into()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Although this handler will work, `self.0` will be different depending on the number of threads and
|
||||
number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`.
|
||||
|
||||
```rust
|
||||
use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
struct MyHandler(Arc<AtomicUsize>);
|
||||
|
||||
impl<S> Handler<S> for MyHandler {
|
||||
type Result = HttpResponse;
|
||||
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
self.0.fetch_add(1, Ordering::Relaxed);
|
||||
HttpResponse::Ok().into()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sys = actix::System::new("example");
|
||||
|
||||
let inc = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
server::new(
|
||||
move || {
|
||||
let cloned = inc.clone();
|
||||
App::new()
|
||||
.resource("/", move |r| r.h(MyHandler(cloned)))
|
||||
})
|
||||
.bind("127.0.0.1:8088").unwrap()
|
||||
.start();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8088");
|
||||
let _ = sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
> Be careful with synchronization primitives like `Mutex` or `RwLock`. The `actix-web` framework
|
||||
> handles requests asynchronously. By blocking thread execution, all concurrent
|
||||
> request handling processes would block. If you need to share or update some state
|
||||
> from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system.
|
||||
|
||||
## Response with custom type
|
||||
|
||||
To return a custom type directly from a handler function, the type needs to implement the `Responder` trait.
|
||||
|
||||
Let's create a response for a custom type that serializes to an `application/json` response:
|
||||
|
||||
```rust
|
||||
# extern crate actix;
|
||||
# extern crate actix_web;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MyObj {
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
/// Responder
|
||||
impl Responder for MyObj {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
let body = serde_json::to_string(&self)?;
|
||||
|
||||
// Create response and set content type
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
}
|
||||
|
||||
fn index(req: HttpRequest) -> impl Responder {
|
||||
MyObj { name: "user" }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sys = actix::System::new("example");
|
||||
|
||||
server::new(
|
||||
|| App::new()
|
||||
.resource("/", |r| r.method(http::Method::GET).f(index)))
|
||||
.bind("127.0.0.1:8088").unwrap()
|
||||
.start();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8088");
|
||||
let _ = sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
## Async handlers
|
||||
|
||||
There are two different types of async handlers. Response objects can be generated asynchronously
|
||||
or more precisely, any type that implements the [*Responder*](../../actix-web/actix_web/trait.Responder.html) trait.
|
||||
|
||||
In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::once;
|
||||
use futures::future::{Future, result};
|
||||
|
||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
|
||||
result(Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(format!("Hello!"))))
|
||||
.responder()
|
||||
}
|
||||
|
||||
fn index2(req: HttpRequest) -> Box<Future<Item=&'static str, Error=Error>> {
|
||||
result(Ok("Welcome!"))
|
||||
.responder()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource("/async", |r| r.route().a(index))
|
||||
.resource("/", |r| r.route().a(index2))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
Or the response body can be generated asynchronously. In this case, body
|
||||
must implement the stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::once;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
let body = once(Ok(Bytes::from_static(b"test")));
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(Body::Streaming(Box::new(body)))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource("/async", |r| r.f(index))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
Both methods can be combined. (i.e Async response with streaming body)
|
||||
|
||||
It is possible to return a `Result` where the `Result::Item` type can be `Future`.
|
||||
In this example, the `index` handler can return an error immediately or return a
|
||||
future that resolves to a `HttpResponse`.
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::once;
|
||||
use futures::future::{Future, result};
|
||||
|
||||
fn index(req: HttpRequest) -> Result<Box<Future<Item=HttpResponse, Error=Error>>, Error> {
|
||||
if is_error() {
|
||||
Err(error::ErrorBadRequest("bad request"))
|
||||
} else {
|
||||
Ok(Box::new(
|
||||
result(Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(format!("Hello!"))))))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Different return types (Either)
|
||||
|
||||
Sometimes, you need to return different types of responses. For example,
|
||||
you can error check and return errors, return async responses, or any result that requires two different types.
|
||||
|
||||
For this case, the [*Either*](../../actix-web/actix_web/enum.Either.html) type can be used.
|
||||
`Either` allows combining two different responder types into a single type.
|
||||
|
||||
```rust
|
||||
use futures::future::{Future, result};
|
||||
use actix_web::{Either, Error, HttpResponse};
|
||||
|
||||
type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
||||
|
||||
fn index(req: HttpRequest) -> impl Responder {
|
||||
if is_a_variant() { // <- choose variant A
|
||||
Either::A(
|
||||
HttpResponse::BadRequest().body("Bad data"))
|
||||
} else {
|
||||
Either::B( // <- variant B
|
||||
result(Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(format!("Hello!")))).responder())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tokio core handle
|
||||
|
||||
Any `actix-web` handler runs within a properly configured
|
||||
[actix system](https://actix.github.io/actix/actix/struct.System.html)
|
||||
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
|
||||
You can always get access to the tokio handle via the
|
||||
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
|
||||
method.
|
@ -1,45 +0,0 @@
|
||||
---
|
||||
title: HTTP/2.0
|
||||
menu: docs_proto
|
||||
weight: 250
|
||||
---
|
||||
|
||||
如果可能`actix-web`自动升级到*HTTP/2.0*的连接。
|
||||
|
||||
# 协议
|
||||
|
||||
*HTTP/2.0* protocol over tls without prior knowledge requires [tls alpn](https://tools.ietf.org/html/rfc7301).
|
||||
|
||||
> 目前,只有`rust-openssl`支持
|
||||
|
||||
`alpn`协议需要启用该功能。启用后,HttpServer提供 serve_tls方法。
|
||||
[serve_tls](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.serve_tls) method.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["alpn"] }
|
||||
openssl = { version = "0.10", features = ["v110"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use actix_web::*;
|
||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||
|
||||
fn main() {
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
||||
|
||||
HttpServer::new(
|
||||
|| App::new()
|
||||
.resource("/index.html", |r| r.f(index)))
|
||||
.bind("127.0.0.1:8080").unwrap();
|
||||
.serve_ssl(builder).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
不支持升级到[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) 节中描述的HTTP/2.0模式 。明文连接和tls连接都支持*HTTP/2* with prior knowledge启动,[rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
||||
|
||||
查看具体示例[examples/tls](https://github.com/actix/examples/tree/master/tls).
|
@ -1,250 +0,0 @@
|
||||
---
|
||||
title: Middlewares
|
||||
menu: docs_advanced
|
||||
weight: 220
|
||||
---
|
||||
|
||||
# Middleware
|
||||
|
||||
Actix's middleware system allows us to add additional behavior to request/response processing.
|
||||
Middleware can hook into an incoming request process, enabling us to modify requests
|
||||
as well as halt request processing to return a response early.
|
||||
|
||||
Middleware can also hook into response processing.
|
||||
|
||||
Typically, middleware is involved in the following actions:
|
||||
|
||||
* Pre-process the Request
|
||||
* Post-process a Response
|
||||
* Modify application state
|
||||
* Access external services (redis, logging, sessions)
|
||||
|
||||
Middleware is registered for each application and executed in same order as
|
||||
registration. In general, a *middleware* is a type that implements the
|
||||
[*Middleware trait*](../../actix-web/actix_web/middleware/trait.Middleware.html).
|
||||
Each method in this trait has a default implementation. Each method can return
|
||||
a result immediately or a *future* object.
|
||||
|
||||
The following demonstrates using middleware to add request and response headers:
|
||||
|
||||
```rust
|
||||
use http::{header, HttpTryFrom};
|
||||
use actix_web::{App, HttpRequest, HttpResponse, Result};
|
||||
use actix_web::middleware::{Middleware, Started, Response};
|
||||
|
||||
struct Headers; // <- Our middleware
|
||||
|
||||
/// Middleware implementation, middlewares are generic over application state,
|
||||
/// so you can access state with `HttpRequest::state()` method.
|
||||
impl<S> Middleware<S> for Headers {
|
||||
|
||||
/// Method is called when request is ready. It may return
|
||||
/// future, which should resolve before next middleware get called.
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
||||
Ok(Started::Done)
|
||||
}
|
||||
|
||||
/// Method is called when handler returns response,
|
||||
/// but before sending http message to peer.
|
||||
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse)
|
||||
-> Result<Response>
|
||||
{
|
||||
resp.headers_mut().insert(
|
||||
header::HeaderName::try_from("X-VERSION").unwrap(),
|
||||
header::HeaderValue::from_static("0.2"));
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
// Register middleware, this method can be called multiple times
|
||||
.middleware(Headers)
|
||||
.resource("/", |r| r.f(|_| HttpResponse::Ok()));
|
||||
}
|
||||
```
|
||||
|
||||
> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc.
|
||||
|
||||
# Logging
|
||||
|
||||
Logging is implemented as a middleware.
|
||||
It is common to register a logging middleware as the first middleware for the application.
|
||||
Logging middleware must be registered for each application.
|
||||
|
||||
The `Logger` middleware uses the standard log crate to log information. You should enable logger
|
||||
for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/)
|
||||
or similar).
|
||||
|
||||
## Usage
|
||||
|
||||
Create `Logger` middleware with the specified `format`.
|
||||
Default `Logger` can be created with `default` method, it uses the default format:
|
||||
|
||||
```ignore
|
||||
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
```
|
||||
|
||||
```rust
|
||||
extern crate env_logger;
|
||||
use actix_web::App;
|
||||
use actix_web::middleware::Logger;
|
||||
|
||||
fn main() {
|
||||
std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
env_logger::init();
|
||||
|
||||
App::new()
|
||||
.middleware(Logger::default())
|
||||
.middleware(Logger::new("%a %{User-Agent}i"))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
The following is an example of the default logging format:
|
||||
|
||||
```
|
||||
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
|
||||
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
|
||||
```
|
||||
|
||||
## Format
|
||||
|
||||
`%%` The percent sign
|
||||
|
||||
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
||||
|
||||
`%t` Time when the request was started to process
|
||||
|
||||
`%P` The process ID of the child that serviced the request
|
||||
|
||||
`%r` First line of request
|
||||
|
||||
`%s` Response status code
|
||||
|
||||
`%b` Size of response in bytes, including HTTP headers
|
||||
|
||||
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
|
||||
|
||||
`%D` Time taken to serve the request, in milliseconds
|
||||
|
||||
`%{FOO}i` request.headers['FOO']
|
||||
|
||||
`%{FOO}o` response.headers['FOO']
|
||||
|
||||
`%{FOO}e` os.environ['FOO']
|
||||
|
||||
## Default headers
|
||||
|
||||
To set default response headers, the `DefaultHeaders` middleware can be used. The
|
||||
*DefaultHeaders* middleware does not set the header if response headers already contain
|
||||
a specified header.
|
||||
|
||||
```rust
|
||||
use actix_web::{http, middleware, App, HttpResponse};
|
||||
|
||||
fn main() {
|
||||
let app = App::new()
|
||||
.middleware(
|
||||
middleware::DefaultHeaders::new()
|
||||
.header("X-Version", "0.2"))
|
||||
.resource("/test", |r| {
|
||||
r.method(http::Method::GET).f(|req| HttpResponse::Ok());
|
||||
r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed());
|
||||
})
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
## User sessions
|
||||
|
||||
Actix provides a general solution for session management. The
|
||||
[**SessionStorage**](../../actix-web/actix_web/middleware/session/struct.SessionStorage.html) middleware can be
|
||||
used with different backend types to store session data in different backends.
|
||||
|
||||
> By default, only cookie session backend is implemented. Other backend implementations
|
||||
> can be added.
|
||||
|
||||
[**CookieSessionBackend**](../../actix-web/actix_web/middleware/session/struct.CookieSessionBackend.html)
|
||||
uses cookies as session storage. `CookieSessionBackend` creates sessions which
|
||||
are limited to storing fewer than 4000 bytes of data, as the payload must fit into a
|
||||
single cookie. An internal server error is generated if a session contains more than 4000 bytes.
|
||||
|
||||
A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
|
||||
|
||||
A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client.
|
||||
|
||||
The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost.
|
||||
|
||||
In general, you create a
|
||||
`SessionStorage` middleware and initialize it with specific backend implementation,
|
||||
such as a `CookieSessionBackend`. To access session data,
|
||||
[*HttpRequest::session()*](../../actix-web/actix_web/middleware/session/trait.RequestSession.html#tymethod.session)
|
||||
must be used. This method returns a
|
||||
[*Session*](../../actix-web/actix_web/middleware/session/struct.Session.html) object, which allows us to get or set
|
||||
session data.
|
||||
|
||||
```rust
|
||||
use actix_web::{server, App, HttpRequest, Result};
|
||||
use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||
|
||||
fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
// access session data
|
||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
||||
println!("SESSION value: {}", count);
|
||||
req.session().set("counter", count+1)?;
|
||||
} else {
|
||||
req.session().set("counter", 1)?;
|
||||
}
|
||||
|
||||
Ok("Welcome!")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sys = actix::System::new("basic-example");
|
||||
server::new(
|
||||
|| App::new().middleware(
|
||||
SessionStorage::new(
|
||||
CookieSessionBackend::signed(&[0; 32])
|
||||
.secure(false)
|
||||
)))
|
||||
.bind("127.0.0.1:59880").unwrap()
|
||||
.start();
|
||||
let _ = sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
# Error handlers
|
||||
|
||||
`ErrorHandlers` middleware allows us to provide custom handlers for responses.
|
||||
|
||||
You can use the `ErrorHandlers::handler()` method to register a custom error handler
|
||||
for a specific status code. You can modify an existing response or create a completly new
|
||||
one. The error handler can return a response immediately or return a future that resolves
|
||||
into a response.
|
||||
|
||||
```rust
|
||||
use actix_web::{
|
||||
App, HttpRequest, HttpResponse, Result,
|
||||
http, middleware::Response, middleware::ErrorHandlers};
|
||||
|
||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
let mut builder = resp.into_builder();
|
||||
builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||
Ok(Response::Done(builder.into()))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new()
|
||||
.middleware(
|
||||
ErrorHandlers::new()
|
||||
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
||||
.resource("/test", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
})
|
||||
.finish();
|
||||
}
|
||||
```
|
@ -1,207 +0,0 @@
|
||||
---
|
||||
title: Requests
|
||||
menu: docs_advanced
|
||||
weight: 200
|
||||
---
|
||||
|
||||
# Content Encoding
|
||||
|
||||
Actix automatically *decompresses* payloads. The following codecs are supported:
|
||||
|
||||
* Brotli
|
||||
* Gzip
|
||||
* Deflate
|
||||
* Identity
|
||||
|
||||
If request headers contain a `Content-Encoding` header, the request payload is decompressed
|
||||
according to the header value. Multiple codecs are not supported,
|
||||
i.e: `Content-Encoding: br, gzip`.
|
||||
|
||||
# JSON Request
|
||||
|
||||
There are several options for json body deserialization.
|
||||
|
||||
The first option is to use *Json* extractor. First, you define a handler function
|
||||
that accepts `Json<T>` as a parameter, then, you use the `.with()` method for registering
|
||||
this handler. It is also possible to accept arbitrary valid json object by
|
||||
using `serde_json::Value` as a type `T`.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, Json, Result, http};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Info {
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// extract `Info` using serde
|
||||
fn index(info: Json<Info>) -> Result<String> {
|
||||
Ok(format!("Welcome {}!", info.username))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().resource(
|
||||
"/index.html",
|
||||
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||
}
|
||||
```
|
||||
|
||||
Another option is to use *HttpRequest::json()*. This method returns a
|
||||
[*JsonBody*](../../actix-web/actix_web/dev/struct.JsonBody.html) object which resolves into
|
||||
the deserialized value.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MyObj {
|
||||
name: String,
|
||||
number: i32,
|
||||
}
|
||||
|
||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
req.json().from_err()
|
||||
.and_then(|val: MyObj| {
|
||||
println!("model: {:?}", val);
|
||||
Ok(HttpResponse::Ok().json(val)) // <- send response
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
```
|
||||
|
||||
You may also manually load the payload into memory and then deserialize it.
|
||||
|
||||
In the following example, we will deserialize a *MyObj* struct. We need to load the request
|
||||
body first and then deserialize the json into an object.
|
||||
|
||||
```rust
|
||||
extern crate serde_json;
|
||||
use futures::{Future, Stream};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct MyObj {name: String, number: i32}
|
||||
|
||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
// `concat2` will asynchronously read each chunk of the request body and
|
||||
// return a single, concatenated, chunk
|
||||
req.concat2()
|
||||
// `Future::from_err` acts like `?` in that it coerces the error type from
|
||||
// the future into the final error type
|
||||
.from_err()
|
||||
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
||||
// synchronous workflow
|
||||
.and_then(|body| {
|
||||
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
||||
Ok(HttpResponse::Ok().json(obj))
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
```
|
||||
|
||||
> A complete example for both options is available in
|
||||
> [examples directory](https://github.com/actix/examples/tree/master/json/).
|
||||
|
||||
# Chunked transfer encoding
|
||||
|
||||
Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains
|
||||
the decoded byte stream. If the request payload is compressed with one of the supported
|
||||
compression codecs (br, gzip, deflate), then the byte stream is decompressed.
|
||||
|
||||
# Multipart body
|
||||
|
||||
Actix provides multipart stream support.
|
||||
[*Multipart*](../../actix-web/actix_web/multipart/struct.Multipart.html) is implemented as
|
||||
a stream of multipart items. Each item can be a
|
||||
[*Field*](../../actix-web/actix_web/multipart/struct.Field.html) or a nested
|
||||
*Multipart* stream.`HttpResponse::multipart()` returns the *Multipart* stream
|
||||
for the current request.
|
||||
|
||||
The following demonstrates multipart stream handling for a simple form:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
|
||||
fn index(req: HttpRequest) -> Box<Future<...>> {
|
||||
// get multipart and iterate over multipart items
|
||||
req.multipart()
|
||||
.and_then(|item| {
|
||||
match item {
|
||||
multipart::MultipartItem::Field(field) => {
|
||||
println!("==== FIELD ==== {:?} {:?}",
|
||||
field.headers(),
|
||||
field.content_type());
|
||||
Either::A(
|
||||
field.map(|chunk| {
|
||||
println!("-- CHUNK: \n{}",
|
||||
std::str::from_utf8(&chunk).unwrap());})
|
||||
.fold((), |_, _| result(Ok(()))))
|
||||
},
|
||||
multipart::MultipartItem::Nested(mp) => {
|
||||
Either::B(result(Ok(())))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> A full example is available in the
|
||||
> [examples directory](https://github.com/actix/examples/tree/master/multipart/).
|
||||
|
||||
# Urlencoded body
|
||||
|
||||
Actix provides support for *application/x-www-form-urlencoded* encoded bodies.
|
||||
`HttpResponse::urlencoded()` returns a
|
||||
[*UrlEncoded*](../../actix-web/actix_web/dev/struct.UrlEncoded.html) future, which resolves
|
||||
to the deserialized instance. The type of the instance must implement the
|
||||
`Deserialize` trait from *serde*.
|
||||
|
||||
The *UrlEncoded* future can resolve into an error in several cases:
|
||||
|
||||
* content type is not `application/x-www-form-urlencoded`
|
||||
* transfer encoding is `chunked`.
|
||||
* content-length is greater than 256k
|
||||
* payload terminates with error.
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::*;
|
||||
use futures::future::{Future, ok};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FormData {
|
||||
username: String,
|
||||
}
|
||||
|
||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
req.urlencoded::<FormData>() // <- get UrlEncoded future
|
||||
.from_err()
|
||||
.and_then(|data| { // <- deserialized instance
|
||||
println!("USERNAME: {:?}", data.username);
|
||||
ok(HttpResponse::Ok().into())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
# Streaming request
|
||||
|
||||
*HttpRequest* is a stream of `Bytes` objects. It can be used to read the request
|
||||
body payload.
|
||||
|
||||
In the following example, we read and print the request payload chunk by chunk:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use futures::{Future, Stream};
|
||||
|
||||
|
||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
req.from_err()
|
||||
.fold((), |_, chunk| {
|
||||
println!("Chunk: {:?}", chunk);
|
||||
result::<_, error::PayloadError>(Ok(()))
|
||||
})
|
||||
.map(|_| HttpResponse::Ok().finish())
|
||||
.responder()
|
||||
}
|
||||
```
|
@ -1,140 +0,0 @@
|
||||
---
|
||||
title: Responses
|
||||
menu: docs_advanced
|
||||
weight: 210
|
||||
---
|
||||
|
||||
# Response
|
||||
|
||||
A builder-like pattern is used to construct an instance of `HttpResponse`.
|
||||
`HttpResponse` provides several methods that return a `HttpResponseBuilder` instance,
|
||||
which implements various convenience methods for building responses.
|
||||
|
||||
> Check the [documentation](../../actix-web/actix_web/dev/struct.HttpResponseBuilder.html)
|
||||
> for type descriptions.
|
||||
|
||||
The methods `.body`, `.finish`, and `.json` finalize response creation and
|
||||
return a constructed *HttpResponse* instance. If this methods is called on the same
|
||||
builder instance multiple times, the builder will panic.
|
||||
|
||||
```rust
|
||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_encoding(ContentEncoding::Br)
|
||||
.content_type("plain/text")
|
||||
.header("X-Hdr", "sample")
|
||||
.body("data")
|
||||
}
|
||||
```
|
||||
|
||||
# Content encoding
|
||||
|
||||
Actix automatically *compresses* payloads. The following codecs are supported:
|
||||
|
||||
* Brotli
|
||||
* Gzip
|
||||
* Deflate
|
||||
* Identity
|
||||
|
||||
Response payload is compressed based on the *content_encoding* parameter.
|
||||
By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected,
|
||||
then the compression depends on the request's `Accept-Encoding` header.
|
||||
|
||||
> `ContentEncoding::Identity` can be used to disable compression.
|
||||
> If another content encoding is selected, the compression is enforced for that codec.
|
||||
|
||||
For example, to enable `brotli` use `ContentEncoding::Br`:
|
||||
|
||||
```rust
|
||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_encoding(ContentEncoding::Br)
|
||||
.body("data")
|
||||
}
|
||||
```
|
||||
|
||||
In this case we explicitly disable content compression
|
||||
by setting content encoding to a `Identity` value:
|
||||
|
||||
```rust
|
||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
// v- disable compression
|
||||
.content_encoding(ContentEncoding::Identity)
|
||||
.body("data")
|
||||
}
|
||||
```
|
||||
|
||||
Also it is possible to set default content encoding on application level, by
|
||||
default `ContentEncoding::Auto` is used, which implies automatic content compression
|
||||
negotiation.
|
||||
|
||||
```rust
|
||||
use actix_web::{App, HttpRequest, HttpResponse, http::ContentEncoding};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.body("data")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new()
|
||||
// v- disable compression for all routes
|
||||
.default_encoding(ContentEncoding::Identity)
|
||||
.resource("/index.html", |r| r.with(index));
|
||||
}
|
||||
```
|
||||
|
||||
# JSON Response
|
||||
|
||||
The `Json` type allows to respond with well-formed JSON data: simply return a value of
|
||||
type Json<T> where `T` is the type of a structure to serialize into *JSON*.
|
||||
The type `T` must implement the `Serialize` trait from *serde*.
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::{App, HttpRequest, Json, Result, http::Method};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MyObj {
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||
Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
# Chunked transfer encoding
|
||||
|
||||
Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`.
|
||||
This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
|
||||
If the response payload compression is enabled and a streaming body is used, chunked encoding
|
||||
is enabled automatically.
|
||||
|
||||
> Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::once;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.chunked()
|
||||
.body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data"))))))
|
||||
}
|
||||
```
|
@ -1,61 +0,0 @@
|
||||
---
|
||||
title: Sentry
|
||||
menu: docs_patterns
|
||||
weight: 1020
|
||||
---
|
||||
|
||||
# Sentry崩溃报告
|
||||
|
||||
[Sentry](https://sentry.io/)是一个崩溃报告系统,它支持基于actix错误报告的failure crate。使用Sentry中间件,可以自动将服务器错误报告给Sentry。
|
||||
|
||||
|
||||
# Sentry中间件
|
||||
|
||||
该中间件捕获服务器错误范围(500-599)中的任何错误,并通过其堆栈跟踪将附加的错误发送给哨兵。
|
||||
|
||||
要使用中间件,需要初始化和配置Sentry,并且需要添加sentry-actix中间件。此外,注册panic处理程序以通知困难panic也是有意义的。
|
||||
|
||||
```rust
|
||||
extern crate sentry;
|
||||
extern crate sentry_actix;
|
||||
|
||||
use sentry_actix::SentryMiddleware;
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
sentry::init("SENTRY_DSN_GOES_HERE");
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
sentry::integrations::panic::register_panic_handler();
|
||||
|
||||
let mut app = App::with_state(state)
|
||||
.middleware(SentryMiddleware::new())
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
# Reusing the Hub
|
||||
|
||||
如果使用这种集成,默认的sentry hub(Hub::current())通常是错误的。要获得特定的请求,您需要使用ActixWebHubExt trait:
|
||||
|
||||
```rust
|
||||
use sentry::{Hub, Level};
|
||||
use sentry_actix::ActixWebHubExt;
|
||||
|
||||
let hub = Hub::from_request(req);
|
||||
hub.capture_message("Something is not well", Level::Warning);
|
||||
```
|
||||
|
||||
The hub can also be made current for the duration of a call. Then Hub::current() works correctly until the end of the run block.
|
||||
|
||||
|
||||
|
||||
```rust
|
||||
use sentry::{Hub, Level};
|
||||
use sentry_actix::ActixWebHubExt;
|
||||
|
||||
let hub = Hub::from_request(req);
|
||||
Hub::run(hub, || {
|
||||
sentry::capture_message("Something is not well", Level::Warning);
|
||||
});
|
||||
```
|
@ -1,101 +0,0 @@
|
||||
---
|
||||
title: 服务器
|
||||
menu: docs_basics
|
||||
weight: 150
|
||||
---
|
||||
|
||||
# HTTP服务器
|
||||
|
||||
该[**HttpServer**](../../actix-web/actix_web/server/struct.HttpServer.html)类型负责服务的HTTP请求。
|
||||
|
||||
`HttpServer`接受应用程序工厂作为参数,并且应用程序工厂必须具有Send+ Sync边界。p
|
||||
要绑定到特定的套接字地址, [`bind()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind) 必须使用,并且可能会多次调用它。绑定ssl套接字使用[`bind_ssl()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind_ssl)或[`bind_tls()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind_tls)。启动http服务器,启动方法之一是:
|
||||
|
||||
- use [`start()`](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.start)
|
||||
for a server
|
||||
|
||||
`HttpServer`是一位actix actor。它必须在正确配置的actix系统中初始化:
|
||||
|
||||
{{< include-example example="server" section="main" >}}
|
||||
|
||||
> 可以使用该run()方法在单独的线程中启动服务器。在这种情况下,服务器会产生一个新线程并在其中创建一个新的actix系统。要停止此服务器,请发送`StopServer`消息。
|
||||
|
||||
`HttpServer`被实施为actix actor。可以通过消息传递系统与服务器进行通信。启动方法,例如`start()`,返回启动的http服务器的地址。它接受几种消息:
|
||||
|
||||
- PauseServer - 暂停接受传入连接
|
||||
- ResumeServer - 继续接受传入连接
|
||||
- StopServer - 停止传入连接处理,停止所有workers并退出
|
||||
|
||||
{{< include-example example="server" file="signals.rs" section="signals" >}}
|
||||
|
||||
## 多线程
|
||||
|
||||
`HttpServer`自动启动一些http worker,默认情况下这个数量等于系统中逻辑CPU的数量。该数量可以用该[`HttpServer::workers()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.workers)方法覆盖 。
|
||||
|
||||
|
||||
{{< include-example example="server" file="workers.rs" section="workers" >}}
|
||||
|
||||
服务器为每个创建的worker创建一个单独的应用实例。应用程序状态不在线程之间共享。分享状态,可以使用Arc。
|
||||
|
||||
>应用程序状态并不需要是Send和Sync,但是工厂必须是Send+ Sync。
|
||||
|
||||
## SSL
|
||||
|
||||
有两种功能的ssl服务器:`tls`和`alpn`。该tls功能由native-tls集成,alpn由openssl。
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["alpn"] }
|
||||
```
|
||||
|
||||
{{< include-example example="server" file="ssl.rs" section="ssl" >}}
|
||||
|
||||
》 **注意**:HTTP / 2.0协议需要[tls alpn](https://tools.ietf.org/html/rfc7301)。目前,只有openssl有alpn支持。完整示例,请查看[examples/tls](https://github.com/actix/examples/tree/master/tls).
|
||||
|
||||
要创建key.pem和cert.pem,请使用以下命令。**Fill in your own subject**
|
||||
|
||||
```bash
|
||||
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
|
||||
-days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd"
|
||||
```
|
||||
|
||||
要删除密码,请将nopass.pem复制到key.pem
|
||||
|
||||
```bash
|
||||
$ openssl rsa -in key.pem -out nopass.pem
|
||||
```
|
||||
|
||||
## Keep-Alive
|
||||
|
||||
Actix可以等待keep-alive的请求。
|
||||
|
||||
》 *keep-alive*连接行为由服务器设置定义。
|
||||
|
||||
- `75`, `Some(75)`, `KeepAlive::Timeout(75)` - 75秒keep alive定时器。
|
||||
- `None` or `KeepAlive::Disabled` - 禁用 *keep alive*.
|
||||
- `KeepAlive::Tcp(75)` - 使用 `SO_KEEPALIVE` socket 选项.
|
||||
|
||||
{{< include-example example="server" file="ka.rs" section="ka" >}}
|
||||
|
||||
如果选择第一个选项,则*keep alive*状态根据响应的*connection-type*计算。默认情况下`HttpResponse::connection_type`未定义。在这种情况下, *keep alive* 状态由请求的http版本定义。
|
||||
|
||||
> *keep alive* 是 **关闭** 对于 *HTTP/1.0* 然而是 **打开** 对于 *HTTP/1.1* 和 *HTTP/2.0*.
|
||||
|
||||
*Connection type*可以用`HttpResponseBuilder::connection_type()`方法改变。
|
||||
|
||||
{{< include-example example="server" file="ka_tp.rs" section="example" >}}
|
||||
|
||||
## 优雅的关机
|
||||
|
||||
`HttpServer`支持优雅的关机。收到停止信号后,workers会有特定的时间完成服务请求。任何在超时后仍然活着的workers(工作线程)都会被迫停止。默认情况下,关机超时设置为30秒。您可以使用[`HttpServer::shutdown_timeout()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.shutdown_timeout)方法更改此参数 。
|
||||
|
||||
您可以使用服务器地址向服务器发送停止消息,并指定是否要进行正常关机。[`start()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.start)方法返回服务器的地址。
|
||||
|
||||
`HttpServer`处理几个OS信号。所有操作系统都提供CTRL-C,其他信号在unix系统上可用。
|
||||
|
||||
- *SIGINT* - 强制关闭工作线程
|
||||
- *SIGTERM* - 优雅的停止工作线程
|
||||
- *SIGQUIT* - 制关闭 workers工作线程
|
||||
|
||||
> 可以用[`HttpServer::disable_signals()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.disable_signals)
|
||||
方法禁用信号处理 。
|
@ -1,46 +0,0 @@
|
||||
---
|
||||
title: 静态文件
|
||||
menu: docs_advanced
|
||||
weight: 230
|
||||
---
|
||||
|
||||
# 单文件
|
||||
|
||||
可以使用自定义路径模式和NamedFile来提供静态文件服务。要匹配路径尾部,我们可以使用[.*]正则表达式。
|
||||
|
||||
```rust
|
||||
use std::path::PathBuf;
|
||||
use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile};
|
||||
|
||||
fn index(req: HttpRequest) -> Result<NamedFile> {
|
||||
let path: PathBuf = req.match_info().query("tail")?;
|
||||
Ok(NamedFile::open(path)?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
# 目录
|
||||
|
||||
StaticFiles可以用作特定目录和子目录文件服务。 StaticFiles必须注册一个App::handler()方法,否则它将无法服务子路径。
|
||||
|
||||
```rust
|
||||
use actix_web::{App, fs};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.handler(
|
||||
"/static",
|
||||
fs::StaticFiles::new(".")
|
||||
.show_files_listing())
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
该参数是根目录。默认情况下,子目录的文件列表被禁用。尝试加载目录列表将返回404 Not Found响应。要启用文件列表,请使用[* StaticFiles :: show_files_listing()*](https://actix.rs/actix-web/actix_web/fs/struct.StaticFiles.html#method.show_files_listing) 方法。
|
||||
|
||||
与其显示目录的文件列表,宁一种方法是重定向到特定的index文件。使用[*StaticFiles::index_file()*](https://actix.rs/actix-web/actix_web/fs/struct.StaticFiles.html#method.index_file)方法来配置此重定向。
|
@ -1,236 +0,0 @@
|
||||
---
|
||||
title: Testing
|
||||
menu: docs_advanced
|
||||
weight: 210
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
Every application should be well tested. Actix provides tools to perform unit and
|
||||
integration tests.
|
||||
|
||||
# Unit Tests
|
||||
|
||||
For unit testing, actix provides a request builder type and a simple handler runner.
|
||||
[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html)
|
||||
implements a builder-like pattern.
|
||||
You can generate a `HttpRequest` instance with `finish()`, or you can
|
||||
run your handler with `run()` or `run_async()`.
|
||||
|
||||
```rust
|
||||
use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
return HttpResponse::Ok().into()
|
||||
}
|
||||
}
|
||||
HttpResponse::BadRequest().into()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let resp = test::TestRequest::with_header("content-type", "text/plain")
|
||||
.run(index)
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||
|
||||
let resp = test::TestRequest::default()
|
||||
.run(index)
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
|
||||
}
|
||||
```
|
||||
|
||||
# Integration tests
|
||||
|
||||
There are several methods for testing your application. Actix provides
|
||||
[*TestServer*](../../actix-web/actix_web/test/struct.TestServer.html), which can be used
|
||||
to run the application with specific handlers in a real http server.
|
||||
|
||||
`TestServer::get()`, `TestServer::post()`, and `TestServer::client()`
|
||||
methods can be used to send requests to the test server.
|
||||
|
||||
A simple form `TestServer` can be configured to use a handler.
|
||||
`TestServer::new` method accepts a configuration function, and the only argument
|
||||
for this function is a *test application* instance.
|
||||
|
||||
> Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html)
|
||||
> for more information.
|
||||
|
||||
```rust
|
||||
use actix_web::{HttpRequest, HttpMessage};
|
||||
use actix_web::test::TestServer;
|
||||
use std::str;
|
||||
|
||||
fn index(req: HttpRequest) -> &'static str {
|
||||
"Hello world!"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// start new test server
|
||||
let mut srv = TestServer::new(|app| app.handler(index));
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let body = str::from_utf8(&bytes).unwrap();
|
||||
assert_eq!(body, "Hello world!");
|
||||
}
|
||||
```
|
||||
|
||||
The other option is to use an application factory. In this case, you need to pass the factory
|
||||
function the same way as you would for real http server configuration.
|
||||
|
||||
```rust
|
||||
use actix_web::{http, test, App, HttpRequest, HttpResponse};
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok().into()
|
||||
}
|
||||
|
||||
/// This function get called by http server.
|
||||
fn create_app() -> App {
|
||||
App::new()
|
||||
.resource("/test", |r| r.h(index))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut srv = test::TestServer::with_factory(create_app);
|
||||
|
||||
let request = srv.client(
|
||||
http::Method::GET, "/test").finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
```
|
||||
|
||||
If you need more complex application configuration, use the `TestServer::build_with_state()`
|
||||
method. For example, you may need to initialize application state or start `SyncActor`'s for diesel
|
||||
interation. This method accepts a closure that constructs the application state,
|
||||
and it runs when the actix system is configured. Thus, you can initialize any additional actors.
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test() {
|
||||
let srv = TestServer::build_with_state(|| {
|
||||
// we can start diesel actors
|
||||
let addr = SyncArbiter::start(3, || {
|
||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||
});
|
||||
// then we can construct custom state, or it could be `()`
|
||||
MyState{addr: addr}
|
||||
})
|
||||
|
||||
// register server handlers and start test server
|
||||
.start(|app| {
|
||||
app.resource(
|
||||
"/{username}/index.html", |r| r.with(
|
||||
|p: Path<PParam>| format!("Welcome {}!", p.username)));
|
||||
});
|
||||
|
||||
// now we can run our test code
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
# Stream response tests
|
||||
|
||||
If you need to test stream it would be enough to convert a [*ClientResponse*](../../actix-web/actix_web/client/struct.ClientResponse.html) to future and execute it.
|
||||
For example of testing [*Server Sent Events*](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).
|
||||
|
||||
```rust
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate actix_web;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::stream::poll_fn;
|
||||
use futures::{Async, Poll, Stream};
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, Error};
|
||||
use actix_web::http::{ContentEncoding, StatusCode};
|
||||
use actix_web::test::TestServer;
|
||||
|
||||
|
||||
fn sse(_req: HttpRequest) -> HttpResponse {
|
||||
let mut counter = 5usize;
|
||||
// yields `data: N` where N in [5; 1]
|
||||
let server_events = poll_fn(move || -> Poll<Option<Bytes>, Error> {
|
||||
if counter == 0 {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
let payload = format!("data: {}\n\n", counter);
|
||||
counter -= 1;
|
||||
Ok(Async::Ready(Some(Bytes::from(payload))))
|
||||
});
|
||||
|
||||
HttpResponse::build(StatusCode::OK)
|
||||
.content_encoding(ContentEncoding::Identity)
|
||||
.content_type("text/event-stream")
|
||||
.streaming(server_events)
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
// start new test server
|
||||
let mut srv = TestServer::new(|app| app.handler(sse));
|
||||
|
||||
// request stream
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// convert ClientResponse to future, start read body and wait first chunk
|
||||
let (bytes, response) = srv.execute(response.into_future()).unwrap();
|
||||
assert_eq!(bytes.unwrap(), Bytes::from("data: 5\n\n"));
|
||||
|
||||
// next chunk
|
||||
let (bytes, _) = srv.execute(response.into_future()).unwrap();
|
||||
assert_eq!(bytes.unwrap(), Bytes::from("data: 4\n\n"));
|
||||
}
|
||||
```
|
||||
|
||||
# WebSocket server tests
|
||||
|
||||
It is possible to register a *handler* with `TestApp::handler()`, which
|
||||
initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to
|
||||
the websocket server and returns ws reader and writer objects. *TestServer* also
|
||||
provides an `execute()` method, which runs future objects to completion and returns
|
||||
result of the future computation.
|
||||
|
||||
The following example demonstrates how to test a websocket handler:
|
||||
|
||||
```rust
|
||||
use actix_web::*;
|
||||
use futures::Stream;
|
||||
|
||||
struct Ws; // <- WebSocket actor
|
||||
|
||||
impl Actor for Ws {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
}
|
||||
|
||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req| ws::start(req, Ws)));
|
||||
|
||||
let (reader, mut writer) = srv.ws().unwrap();
|
||||
writer.text("text");
|
||||
|
||||
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
||||
}
|
||||
```
|
@ -1,450 +0,0 @@
|
||||
---
|
||||
title: URL Dispatch
|
||||
menu: docs_advanced
|
||||
weight: 190
|
||||
---
|
||||
|
||||
# URL Dispatch
|
||||
|
||||
URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern
|
||||
matching language. If one of the patterns matches the path information associated with a request,
|
||||
a particular handler object is invoked.
|
||||
|
||||
> A handler is a specific object that implements the
|
||||
> `Handler` trait, defined in your application, that receives the request and returns
|
||||
> a response object. More information is available in the
|
||||
> [handler section](sec-4-handler.html).
|
||||
|
||||
# Resource configuration
|
||||
|
||||
Resource configuration is the act of adding a new resources to an application.
|
||||
A resource has a name, which acts as an identifier to be used for URL generation.
|
||||
The name also allows developers to add routes to existing resources.
|
||||
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*.
|
||||
It does not match against the *QUERY* portion (the portion following the scheme and
|
||||
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
|
||||
|
||||
The [*App::route()*](../../actix-web/actix_web/struct.App.html#method.route) method provides
|
||||
simple way of registering routes. This method adds a single route to application
|
||||
routing table. This method accepts a *path pattern*,
|
||||
*http method* and a handler function. `route()` method could be called multiple times
|
||||
for the same path, in that case, multiple routes register for the same resource path.
|
||||
|
||||
{{< include-example example="url-dispatch" section="main" >}}
|
||||
|
||||
While *App::route()* provides simple way of registering routes, to access
|
||||
complete resource configuration, different method has to be used.
|
||||
The [*App::resource()*](../../actix-web/actix_web/struct.App.html#method.resource) method
|
||||
adds a single resource to application routing table. This method accepts a *path pattern*
|
||||
and a resource configuration function.
|
||||
|
||||
{{< include-example example="url-dispatch" file="resource.rs" section="resource" >}}
|
||||
|
||||
The *Configuration function* has the following type:
|
||||
|
||||
```rust
|
||||
FnOnce(&mut Resource<_>) -> ()
|
||||
```
|
||||
|
||||
The *Configuration function* can set a name and register specific routes.
|
||||
If a resource does not contain any route or does not have any matching routes, it
|
||||
returns *NOT FOUND* http response.
|
||||
|
||||
## Configuring a Route
|
||||
|
||||
Resource contains a set of routes. Each route in turn has a set of predicates and a handler.
|
||||
New routes can be created with `Resource::route()` method which returns a reference
|
||||
to new *Route* instance. By default the *route* does not contain any predicates, so matches
|
||||
all requests and the default handler is `HttpNotFound`.
|
||||
|
||||
The application routes incoming requests based on route criteria which are defined during
|
||||
resource registration and route registration. Resource matches all routes it contains in
|
||||
the order the routes were registered via `Resource::route()`.
|
||||
|
||||
> A *Route* can contain any number of *predicates* but only one handler.
|
||||
|
||||
{{< include-example example="url-dispatch" file="cfg.rs" section="cfg" >}}
|
||||
|
||||
In this example, `HttpResponse::Ok()` is returned for *GET* requests.
|
||||
If a request contains `Content-Type` header, the value of this header is *text/plain*,
|
||||
and path equals to `/path`, Resource calls handle of the first matching route.
|
||||
|
||||
If a resource can not match any route, a "NOT FOUND" response is returned.
|
||||
|
||||
[*ResourceHandler::route()*](../../actix-web/actix_web/dev/struct.ResourceHandler.html#method.route) returns a
|
||||
[*Route*](../../actix-web/actix_web/dev/struct.Route.html) object. Route can be configured with a
|
||||
builder-like pattern. Following configuration methods are available:
|
||||
|
||||
* [*Route::filter()*](../../actix-web/actix_web/dev/struct.Route.html#method.filter)
|
||||
registers a new predicate. Any number of predicates can be registered for each route.
|
||||
* [*Route::f()*](../../actix-web/actix_web/dev/struct.Route.html#method.f) registers
|
||||
handler function for this route. Only one handler can be registered.
|
||||
Usually handler registration
|
||||
is the last config operation. Handler function can be a function or closure
|
||||
and has the type
|
||||
`Fn(HttpRequest<S>) -> R + 'static`
|
||||
* [*Route::h()*](../../actix-web/actix_web/dev/struct.Route.html#method.h) registers
|
||||
a handler object that implements the `Handler` trait. This is
|
||||
similar to `f()` method - only one handler can
|
||||
be registered. Handler registration is the last config operation.
|
||||
* [*Route::a()*](../../actix-web/actix_web/dev/struct.Route.html#method.a) registers
|
||||
an async handler function for this route. Only one handler can be registered.
|
||||
Handler registration is the last config operation. Handler function can
|
||||
be a function or closure and has the type
|
||||
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
|
||||
|
||||
# Route matching
|
||||
|
||||
The main purpose of route configuration is to match (or not match) the request's `path`
|
||||
against a URL path pattern. `path` represents the path portion of the URL that was requested.
|
||||
|
||||
The way that *actix* does this is very simple. When a request enters the system,
|
||||
for each resource configuration declaration present in the system, actix checks
|
||||
the request's path against the pattern declared. This checking happens in the order that
|
||||
the routes were declared via `App::resource()` method. If resource can not be found,
|
||||
the *default resource* is used as the matched resource.
|
||||
|
||||
When a route configuration is declared, it may contain route predicate arguments. All route
|
||||
predicates associated with a route declaration must be `true` for the route configuration to
|
||||
be used for a given request during a check. If any predicate in the set of route predicate
|
||||
arguments provided to a route configuration returns `false` during a check, that route is
|
||||
skipped and route matching continues through the ordered set of routes.
|
||||
|
||||
If any route matches, the route matching process stops and the handler associated with
|
||||
the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned.
|
||||
|
||||
# Resource pattern syntax
|
||||
|
||||
The syntax of the pattern matching language used by actix in the pattern
|
||||
argument is straightforward.
|
||||
|
||||
The pattern used in route configuration may start with a slash character. If the pattern
|
||||
does not start with a slash character, an implicit slash will be prepended
|
||||
to it at matching time. For example, the following patterns are equivalent:
|
||||
|
||||
```
|
||||
{foo}/bar/baz
|
||||
```
|
||||
|
||||
and:
|
||||
|
||||
```
|
||||
/{foo}/bar/baz
|
||||
```
|
||||
|
||||
A *variable part* (replacement marker) is specified in the form *{identifier}*,
|
||||
where this means "accept any characters up to the next slash character and use this
|
||||
as the name in the `HttpRequest.match_info()` object".
|
||||
|
||||
A replacement marker in a pattern matches the regular expression `[^{}/]+`.
|
||||
|
||||
A match_info is the `Params` object representing the dynamic parts extracted from a
|
||||
*URL* based on the routing pattern. It is available as *request.match_info*. For example, the
|
||||
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
|
||||
|
||||
```
|
||||
foo/{baz}/{bar}
|
||||
```
|
||||
|
||||
The above pattern will match these URLs, generating the following match information:
|
||||
|
||||
```
|
||||
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
|
||||
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
|
||||
```
|
||||
|
||||
It will not match the following patterns however:
|
||||
|
||||
```
|
||||
foo/1/2/ -> No match (trailing slash)
|
||||
bar/abc/def -> First segment literal mismatch
|
||||
```
|
||||
|
||||
The match for a segment replacement marker in a segment will be done only up to
|
||||
the first non-alphanumeric character in the segment in the pattern. So, for instance,
|
||||
if this route pattern was used:
|
||||
|
||||
```
|
||||
foo/{name}.html
|
||||
```
|
||||
|
||||
The literal path */foo/biz.html* will match the above route pattern, and the match result
|
||||
will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match,
|
||||
because it does not contain a literal *.html* at the end of the segment represented
|
||||
by *{name}.html* (it only contains biz, not biz.html).
|
||||
|
||||
To capture both segments, two replacement markers can be used:
|
||||
|
||||
```
|
||||
foo/{name}.{ext}
|
||||
```
|
||||
|
||||
The literal path */foo/biz.html* will match the above route pattern, and the match
|
||||
result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a
|
||||
literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*.
|
||||
|
||||
Replacement markers can optionally specify a regular expression which will be used to decide
|
||||
whether a path segment should match the marker. To specify that a replacement marker should
|
||||
match only a specific set of characters as defined by a regular expression, you must use a
|
||||
slightly extended form of replacement marker syntax. Within braces, the replacement marker
|
||||
name must be followed by a colon, then directly thereafter, the regular expression. The default
|
||||
regular expression associated with a replacement marker *[^/]+* matches one or more characters
|
||||
which are not a slash. For example, under the hood, the replacement marker *{foo}* can more
|
||||
verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression
|
||||
to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits.
|
||||
|
||||
Segments must contain at least one character in order to match a segment replacement marker.
|
||||
For example, for the URL */abc/*:
|
||||
|
||||
* */abc/{foo}* will not match.
|
||||
* */{foo}/* will match.
|
||||
|
||||
> **Note**: path will be URL-unquoted and decoded into valid unicode string before
|
||||
> matching pattern and values representing matched path segments will be URL-unquoted too.
|
||||
|
||||
So for instance, the following pattern:
|
||||
|
||||
```
|
||||
foo/{bar}
|
||||
```
|
||||
|
||||
When matching the following URL:
|
||||
|
||||
```
|
||||
http://example.com/foo/La%20Pe%C3%B1a
|
||||
```
|
||||
|
||||
The matchdict will look like so (the value is URL-decoded):
|
||||
|
||||
```
|
||||
Params{'bar': 'La Pe\xf1a'}
|
||||
```
|
||||
|
||||
Literal strings in the path segment should represent the decoded value of the
|
||||
path provided to actix. You don't want to use a URL-encoded value in the pattern.
|
||||
For example, rather than this:
|
||||
|
||||
```
|
||||
/Foo%20Bar/{baz}
|
||||
```
|
||||
|
||||
You'll want to use something like this:
|
||||
|
||||
```
|
||||
/Foo Bar/{baz}
|
||||
```
|
||||
|
||||
It is possible to get "tail match". For this purpose custom regex has to be used.
|
||||
|
||||
```
|
||||
foo/{bar}/{tail:.*}
|
||||
```
|
||||
|
||||
The above pattern will match these URLs, generating the following match information:
|
||||
|
||||
```
|
||||
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
|
||||
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
|
||||
```
|
||||
|
||||
# Scoping Routes
|
||||
|
||||
Scoping helps you organize routes sharing common root paths. You can nest
|
||||
scopes within scopes.
|
||||
|
||||
Suppose that you want to organize paths to endpoints used to manage a "Project",
|
||||
consisting of "Tasks". Such paths may include:
|
||||
|
||||
- /project
|
||||
- /project/{project_id}
|
||||
- /project/{project_id}/task
|
||||
- /project/{project_id}/task/{task_id}
|
||||
|
||||
|
||||
A scoped layout of these paths would appear as follows
|
||||
|
||||
{{< include-example example="url-dispatch" file="scope.rs" section="scope" >}}
|
||||
|
||||
A *scoped* path can contain variable path segments as resources. Consistent with
|
||||
unscoped paths.
|
||||
|
||||
You can get variable path segments from `HttpRequest::match_info()`.
|
||||
`Path` extractor also is able to extract scope level variable segments.
|
||||
|
||||
# Match information
|
||||
|
||||
All values representing matched path segments are available in
|
||||
[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info).
|
||||
Specific values can be retrieved with
|
||||
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get).
|
||||
|
||||
Any matched parameter can be deserialized into a specific type if the type
|
||||
implements the `FromParam` trait. For example most standard integer types
|
||||
the trait, i.e.:
|
||||
|
||||
{{< include-example example="url-dispatch" file="minfo.rs" section="minfo" >}}
|
||||
|
||||
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
|
||||
|
||||
It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is
|
||||
percent-decoded. If a segment is equal to "..", the previous segment (if
|
||||
any) is skipped.
|
||||
|
||||
For security purposes, if a segment meets any of the following conditions,
|
||||
an `Err` is returned indicating the condition met:
|
||||
|
||||
* Decoded segment starts with any of: `.` (except `..`), `*`
|
||||
* Decoded segment ends with any of: `:`, `>`, `<`
|
||||
* Decoded segment contains any of: `/`
|
||||
* On Windows, decoded segment contains any of: '\'
|
||||
* Percent-encoding results in invalid UTF8.
|
||||
|
||||
As a result of these conditions, a `PathBuf` parsed from request path parameter is
|
||||
safe to interpolate within, or use as a suffix of, a path without additional checks.
|
||||
|
||||
{{< include-example example="url-dispatch" file="pbuf.rs" section="pbuf" >}}
|
||||
|
||||
List of `FromParam` implementations can be found in
|
||||
[api docs](../../actix-web/actix_web/dev/trait.FromParam.html#foreign-impls)
|
||||
|
||||
## Path information extractor
|
||||
|
||||
Actix provides functionality for type safe path information extraction.
|
||||
[*Path*](../../actix-web/actix_web/struct.Path.html) extracts information, destination type
|
||||
could be defined in several different forms. Simplest approach is to use
|
||||
`tuple` type. Each element in tuple must correpond to one element from
|
||||
path pattern. i.e. you can match path pattern `/{id}/{username}/` against
|
||||
`Path<(u32, String)>` type, but `Path<(String, String, String)>` type will
|
||||
always fail.
|
||||
|
||||
{{< include-example example="url-dispatch" file="path.rs" section="path" >}}
|
||||
|
||||
It also possible to extract path pattern information to a struct. In this case,
|
||||
this struct must implement *serde's *`Deserialize` trait.
|
||||
|
||||
{{< include-example example="url-dispatch" file="path2.rs" section="path" >}}
|
||||
|
||||
[*Query*](../../actix-web/actix_web/struct.Query.html) provides similar
|
||||
functionality for request query parameters.
|
||||
|
||||
# Generating resource URLs
|
||||
|
||||
Use the [*HttpRequest.url_for()*](../../actix-web/actix_web/struct.HttpRequest.html#method.url_for)
|
||||
method to generate URLs based on resource patterns. For example, if you've configured a
|
||||
resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this:
|
||||
|
||||
{{< include-example example="url-dispatch" file="urls.rs" section="url" >}}
|
||||
|
||||
This would return something like the string *http://example.com/test/1/2/3* (at least if
|
||||
the current protocol and hostname implied http://example.com).
|
||||
`url_for()` method returns [*Url object*](https://docs.rs/url/1.7.0/url/struct.Url.html) so you
|
||||
can modify this url (add query parameters, anchor, etc).
|
||||
`url_for()` could be called only for *named* resources otherwise error get returned.
|
||||
|
||||
# External resources
|
||||
|
||||
Resources that are valid URLs, can be registered as external resources. They are useful
|
||||
for URL generation purposes only and are never considered for matching at request time.
|
||||
|
||||
{{< include-example example="url-dispatch" file="url_ext.rs" section="ext" >}}
|
||||
|
||||
# Path normalization and redirecting to slash-appended routes
|
||||
|
||||
By normalizing it means:
|
||||
|
||||
* Add a trailing slash to the path.
|
||||
* Double slashes are replaced by one.
|
||||
|
||||
The handler returns as soon as it finds a path that resolves
|
||||
correctly. The order if all enable is 1) merge, 2) both merge and append
|
||||
and 3) append. If the path resolves with
|
||||
at least one of those conditions, it will redirect to the new path.
|
||||
|
||||
If *append* is *true*, append slash when needed. If a resource is
|
||||
defined with trailing slash and the request doesn't have one, it will
|
||||
be appended automatically.
|
||||
|
||||
If *merge* is *true*, merge multiple consecutive slashes in the path into one.
|
||||
|
||||
This handler designed to be used as a handler for application's *default resource*.
|
||||
|
||||
{{< include-example example="url-dispatch" file="norm.rs" section="norm" >}}
|
||||
|
||||
In this example `/resource`, `//resource///` will be redirected to `/resource/`.
|
||||
|
||||
In this example, the path normalization handler is registered for all methods,
|
||||
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
|
||||
slash-appending *Not Found* will turn a *POST* request into a GET, losing any
|
||||
*POST* data in the original request.
|
||||
|
||||
It is possible to register path normalization only for *GET* requests only:
|
||||
|
||||
{{< include-example example="url-dispatch" file="norm2.rs" section="norm" >}}
|
||||
|
||||
## Using an Application Prefix to Compose Applications
|
||||
|
||||
The `App::prefix()` method allows to set a specific application prefix.
|
||||
This prefix represents a resource prefix that will be prepended to all resource patterns added
|
||||
by the resource configuration. This can be used to help mount a set of routes at a different
|
||||
location than the included callable's author intended while still maintaining the same
|
||||
resource names.
|
||||
|
||||
For example:
|
||||
|
||||
{{< include-example example="url-dispatch" file="prefix.rs" section="prefix" >}}
|
||||
|
||||
In the above example, the *show_users* route will have an effective route pattern of
|
||||
*/users/show* instead of */show* because the application's prefix argument will be prepended
|
||||
to the pattern. The route will then only match if the URL path is */users/show*,
|
||||
and when the `HttpRequest.url_for()` function is called with the route name show_users,
|
||||
it will generate a URL with that same path.
|
||||
|
||||
# Custom route predicates
|
||||
|
||||
You can think of a predicate as a simple function that accepts a *request* object reference
|
||||
and returns *true* or *false*. Formally, a predicate is any object that implements the
|
||||
[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides
|
||||
several predicates, you can check
|
||||
[functions section](../../actix-web/actix_web/pred/index.html#functions) of api docs.
|
||||
|
||||
Here is a simple predicate that check that a request contains a specific *header*:
|
||||
|
||||
{{< include-example example="url-dispatch" file="pred.rs" section="pred" >}}
|
||||
|
||||
In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header.
|
||||
|
||||
Predicates have access to the application's state via `HttpRequest::state()`.
|
||||
Also predicates can store extra information in
|
||||
[request extensions](../../actix-web/actix_web/struct.HttpRequest.html#method.extensions).
|
||||
|
||||
## Modifying predicate values
|
||||
|
||||
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
|
||||
For example, if you want to return "METHOD NOT ALLOWED" response for all methods
|
||||
except "GET":
|
||||
|
||||
{{< include-example example="url-dispatch" file="pred2.rs" section="pred" >}}
|
||||
|
||||
The `Any` predicate accepts a list of predicates and matches if any of the supplied
|
||||
predicates match. i.e:
|
||||
|
||||
```rust
|
||||
pred::Any(pred::Get()).or(pred::Post())
|
||||
```
|
||||
|
||||
The `All` predicate accepts a list of predicates and matches if all of the supplied
|
||||
predicates match. i.e:
|
||||
|
||||
```rust
|
||||
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
|
||||
```
|
||||
|
||||
# Changing the default Not Found response
|
||||
|
||||
If the path pattern can not be found in the routing table or a resource can not find matching
|
||||
route, the default resource is used. The default response is *NOT FOUND*.
|
||||
It is possible to override the *NOT FOUND* response with `App::default_resource()`.
|
||||
This method accepts a *configuration function* same as normal resource configuration
|
||||
with `App::resource()` method.
|
||||
|
||||
{{< include-example example="url-dispatch" file="dhandler.rs" section="default" >}}
|
@ -1,44 +0,0 @@
|
||||
---
|
||||
title: Websockets
|
||||
menu: docs_proto
|
||||
weight: 240
|
||||
---
|
||||
|
||||
Actix支持WebSockets开箱即用。可以使用[ws :: WsStream](https://actix.rs/actix-web/actix_web/ws/struct.WsStream.html)将请求的Payload转换为[ws :: Message](https://actix.rs/actix-web/actix_web/ws/enum.Message.html)流,然后使用流组合器来处理实际的消息,但处理websocket通信使用http actor更简单。
|
||||
|
||||
以下是一个简单的websocket echo server的例子:
|
||||
|
||||
```rust
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
|
||||
/// Define http actor
|
||||
struct Ws;
|
||||
|
||||
impl Actor for Ws {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
}
|
||||
|
||||
/// Handler for ws::Message message
|
||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws)))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
||||
[websocket directory](https://github.com/actix/examples/tree/master/websocket/)提供了一个简单的websocket echo server示例 。
|
||||
|
||||
[websocket-chat directory](https://github.com/actix/examples/tree/master/websocket-chat/)提供了一个聊天服务器,可以通过websocket或tcp连接进行聊天
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: 何为Actix
|
||||
menu: docs_intro
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Actix 指多种事情
|
||||
|
||||
Actix是一个强大的Rust的actor系统, 在它之上是actix-web框架。这是你在工作中大多使用的东西。Actix-web给你提供了一个有趣且快速的Web开发框架。
|
||||
|
||||
我们称actix-web为小而务实的框架。对于所有的意图和目的来说,这是一个有少许曲折的微框架。如果你已经是一个Rust程序员,你可能会很快熟悉它,但即使你是来自另一种编程语言,你应该会发现actix-web很容易上手。
|
||||
|
||||
使用actix-web开发的应用程序将在本机可执行文件中包含HTTP服务器。你可以把它放在另一个像nginx这样的HTTP服务器上。即使完全不存在另一个HTTP服务器的情况下,actix-web也足以提供HTTP 1和HTTP 2支持以及SSL/TLS。这对于构建小型服务分发非常有用。
|
||||
|
||||
最重要的是:actix-web可以稳定发布。
|
10
i18n/cn.toml
10
i18n/cn.toml
@ -1,10 +0,0 @@
|
||||
[home]
|
||||
other = "首页"
|
||||
[docs]
|
||||
other = "文档"
|
||||
[blog]
|
||||
other = "博客"
|
||||
[community]
|
||||
other = "社区"
|
||||
[code]
|
||||
other = "源码"
|
@ -1,63 +0,0 @@
|
||||
{{ partial "header" . }}
|
||||
{{ $currentURL := .URL }}
|
||||
|
||||
<div class="actix-pageheader">
|
||||
<div class="container">
|
||||
<h1 id="blog-title" class="display-4">{{ .Title }}</h1>
|
||||
{{ if .Description }}
|
||||
<p class="lead">{{ .Description }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container actix-blog">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
|
||||
<button class="btn doctoggle" type="button" data-toggle="collapse" data-target="#collapsing-docnav" aria-expanded="false" aria-controls="collapsing-docnav">
|
||||
Toggle navigation
|
||||
</button>
|
||||
|
||||
<nav class="leftnav collapse show" id="collapsing-docnav">
|
||||
<div class="" id="">
|
||||
<details>
|
||||
<summary>Introduction</summary>
|
||||
<div>
|
||||
<ul class="nav">
|
||||
{{ range .Site.Menus.blog_intro }}
|
||||
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary>2018</summary>
|
||||
<div>
|
||||
<ul class="nav">
|
||||
{{ range .Site.Menus.blog_2018 }}
|
||||
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="actix-content">
|
||||
{{ block "main" . }}{{ end }}
|
||||
<!-- Yes, we have to use *Prev* to get the next content, because the
|
||||
weights are sorted inversely to how they are actually displayed... -->
|
||||
{{ with .PrevInSection }}<div class="actix-next"><b>Next up</b>: <a href = {{ .URL }}>{{ end }}
|
||||
{{ with .PrevInSection }} {{ .Title }}</a></div>{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ partial "footer" . }}
|
@ -1,3 +0,0 @@
|
||||
{{ define "main" }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
@ -1,3 +0,0 @@
|
||||
{{ define "main" }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
@ -1,149 +0,0 @@
|
||||
{{ partial "header" . }}
|
||||
|
||||
<div id="act-home">
|
||||
<div class="jumbotron">
|
||||
<div class="actix-jumbotron">
|
||||
<img src="/img/logo-large.png" class="align-middle actix-logo" alt="">
|
||||
<p class="lead">Rust强大的actor系统和有趣的web框架</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container actix-home">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="actix-features">
|
||||
<h2>
|
||||
<i class="fa fa-fw fa-shield" aria-hidden="true"></i>
|
||||
类型安全
|
||||
</h2>
|
||||
<p>忘记关于字符串类型的对象,从请求到响应,一切都有类型,异步。</p>
|
||||
|
||||
<h2>
|
||||
<i class="fa fa-fw fa-battery-full" aria-hidden="true"></i>
|
||||
特性丰富
|
||||
</h2>
|
||||
<p>Actix提供了丰富的特性开箱即用。WebSockets,HTTP/2,流,管道,SSL,异步HTTTP客户端等一应俱全.</p>
|
||||
|
||||
<h2>
|
||||
<i class="fa fa-fw fa-puzzle-piece" aria-hidden="true"></i>
|
||||
扩展性强
|
||||
</h2>
|
||||
<p>轻松创建任何基于Actix应用的自己的特色库。</p>
|
||||
|
||||
<h2>
|
||||
<i class="fa fa-fw fa-dashboard" aria-hidden="true"></i>
|
||||
速度极快
|
||||
</h2>
|
||||
<p>Actix 具有顶级的速度. <a href="https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext">Check yourself</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="actix-content">
|
||||
{{ highlight `extern crate actix_web;
|
||||
use actix_web::{http::Method, server, App, Path, Responder};
|
||||
|
||||
fn index(info: Path<(u32, String)>) -> impl Responder {
|
||||
format!("Hello {}! id:{}", info.1, info.0)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
server::new(
|
||||
|| App::new()
|
||||
.route("/{id}/{name}/index.html", Method::GET, index))
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.run();
|
||||
}` "rust" "" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="actix-showcase">
|
||||
<div class="col-md-9">
|
||||
<div class="actix-feature" id="responders">
|
||||
<h2>灵活的请求响应</h2>
|
||||
<p>
|
||||
Actix中的Handler函数可以返回实现该<code>Respondert</code> rait的各种对象。这使得从API返回一致的响应变得轻而易举。
|
||||
</p>
|
||||
{{ highlight `#[derive(Serialize)]
|
||||
struct Measurement {
|
||||
temperature: f32,
|
||||
}
|
||||
|
||||
fn hello_world() -> impl Responder {
|
||||
"Hello World!"
|
||||
}
|
||||
|
||||
fn current_temperature(_req: HttpRequest) -> impl Responder {
|
||||
Json(Measurement { temperature: 42.3 })
|
||||
}` "rust" "" }}
|
||||
</div>
|
||||
<div class="actix-feature" id="extractors">
|
||||
<h2>强大的Extractors</h2>
|
||||
<p>
|
||||
Actix提供了一个强大的提取器系统,可以从传入的HTTP请求中提取数据并将其传递给您的视图函数。这不仅可以创建方便的API,
|
||||
而且还意味着您的视图函数可以是同步代码,并且仍然可以受益于异步IO处理。
|
||||
</p>
|
||||
{{ highlight `#[derive(Deserialize)]
|
||||
struct Event {
|
||||
timestamp: f64,
|
||||
kind: String,
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
fn capture_event(evt: Json<Event>) -> impl Responder {
|
||||
let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags);
|
||||
format!("got event {}", id)
|
||||
}` "rust" "" }}
|
||||
</div>
|
||||
<div class="actix-feature" id="forms">
|
||||
<h2>轻松处理表单</h2>
|
||||
<p>
|
||||
处理multipart/ urlencoded表单数据很容易。只需定义一个可以反序列化的结构,actix就可以处理剩下的部分。
|
||||
</p>
|
||||
{{ highlight `#[derive(Deserialize)]
|
||||
struct Register {
|
||||
username: String,
|
||||
country: String,
|
||||
}
|
||||
|
||||
fn register(data: Form<Register>) -> impl Responder {
|
||||
format!("Hello {} from {}!", data.username, data.country)
|
||||
}` "rust" "" }}
|
||||
</div>
|
||||
<div class="actix-feature" id="routing">
|
||||
<h2>请求路由</h2>
|
||||
<p>
|
||||
一个actix应用程序带有一个URL路由系统,可以让你在URL上匹配并调用单个处理程序。为了获得额外的灵活性,可以使用域。
|
||||
</p>
|
||||
{{ highlight `fn index(req: HttpRequest) -> impl Responder {
|
||||
"Hello from the index page"
|
||||
}
|
||||
|
||||
fn hello(path: Path<String>) -> impl Responder {
|
||||
format!("Hello {}!", *path)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.resource("/", |r| r.method(Method::Get).with(index))
|
||||
.resource("/hello/{name}", |r| r.method(Method::Get).with(hello))
|
||||
.finish();
|
||||
}` "rust" "" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 actix-feature-selectors">
|
||||
<ul class="act-menu">
|
||||
<li class="actix-feature-selector"><a href="#responders">灵活的请求响应</a></li>
|
||||
<li class="actix-feature-selector"><a href="#extractors">强大的Extractors</a></li>
|
||||
<li class="actix-feature-selector"><a href="#forms">轻松处理表单</a></li>
|
||||
<li class="actix-feature-selector"><a href="#routing">请求路由</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{ partial "footer" . }}
|
@ -13,7 +13,7 @@
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js" integrity="sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8" crossorigin="anonymous"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/actix.js" async></script>
|
||||
<script src="/js/actix.js"></script>
|
||||
{{ template "_internal/google_analytics.html" . }}
|
||||
</body>
|
||||
</html>
|
@ -36,9 +36,6 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ "/docs/" | absLangURL }}">{{ T "docs" }}</a>
|
||||
</li>
|
||||
<li class="nav-item" id="nav-blog">
|
||||
<a class="nav-link" href="{{ "/blog/" | absLangURL }}">{{ T "blog" }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ "/community/" | absLangURL }}">{{ T "community" }}</a>
|
||||
</li>
|
||||
@ -51,6 +48,7 @@
|
||||
{{ range $.Site.Home.AllTranslations }}
|
||||
<li class="submenu-item"><a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a></li>
|
||||
{{ end }}
|
||||
<li ><a href="https://actix-cn.github.io/">中文</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -171,9 +171,7 @@ img {
|
||||
padding-top: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navbar-nav #nav-blog {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-nav .language-selector {
|
||||
position: relative;
|
||||
}
|
||||
@ -405,22 +403,6 @@ img {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* ===== blog =====
|
||||
*
|
||||
*/
|
||||
#collapsing-docnav details {
|
||||
border: none;
|
||||
}
|
||||
#collapsing-docnav details summary {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
#collapsing-docnav details ul {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
|
@ -37,9 +37,3 @@
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
window.onload = function(){
|
||||
if (window.location.href.search("cn") != -1) {
|
||||
document.getElementById("nav-blog").style.display = "inline"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user