From 2da5ff93fe9567c262be7018d2836f5b923d9caf Mon Sep 17 00:00:00 2001 From: krircc Date: Fri, 22 Jun 2018 23:18:14 +0800 Subject: [PATCH 01/27] actix.rs/cn --- config.toml | 33 ++- content/_index.cn.md | 3 + content/code/_index.cn.md | 19 ++ content/code/license.cn.md | 201 +++++++++++++ content/community/_index.cn.md | 22 ++ content/community/coc.cn.md | 49 ++++ content/docs/Installation.cn.md | 40 +++ content/docs/_index.cn.md | 14 + content/docs/application.cn.md | 75 +++++ content/docs/autoreload.cn.md | 65 +++++ content/docs/databases.cn.md | 119 ++++++++ content/docs/errors.cn.md | 238 +++++++++++++++ content/docs/extractors.cn.md | 284 ++++++++++++++++++ content/docs/getting-started.cn.md | 40 +++ content/docs/handlers.cn.md | 292 +++++++++++++++++++ content/docs/http2.cn.md | 45 +++ content/docs/middleware.cn.md | 250 ++++++++++++++++ content/docs/request.cn.md | 207 +++++++++++++ content/docs/response.cn.md | 140 +++++++++ content/docs/sentry.cn.md | 61 ++++ content/docs/server.cn.md | 101 +++++++ content/docs/static-files.cn.md | 46 +++ content/docs/testing.cn.md | 236 +++++++++++++++ content/docs/url-dispatch.cn.md | 450 +++++++++++++++++++++++++++++ content/docs/websockets.cn.md | 44 +++ content/docs/whatis.cn.md | 15 + 26 files changed, 3081 insertions(+), 8 deletions(-) create mode 100644 content/_index.cn.md create mode 100644 content/code/_index.cn.md create mode 100644 content/code/license.cn.md create mode 100644 content/community/_index.cn.md create mode 100644 content/community/coc.cn.md create mode 100644 content/docs/Installation.cn.md create mode 100644 content/docs/_index.cn.md create mode 100644 content/docs/application.cn.md create mode 100644 content/docs/autoreload.cn.md create mode 100644 content/docs/databases.cn.md create mode 100644 content/docs/errors.cn.md create mode 100644 content/docs/extractors.cn.md create mode 100644 content/docs/getting-started.cn.md create mode 100644 content/docs/handlers.cn.md create mode 100644 content/docs/http2.cn.md create mode 100644 content/docs/middleware.cn.md create mode 100644 content/docs/request.cn.md create mode 100644 content/docs/response.cn.md create mode 100644 content/docs/sentry.cn.md create mode 100644 content/docs/server.cn.md create mode 100644 content/docs/static-files.cn.md create mode 100644 content/docs/testing.cn.md create mode 100644 content/docs/url-dispatch.cn.md create mode 100644 content/docs/websockets.cn.md create mode 100644 content/docs/whatis.cn.md diff --git a/config.toml b/config.toml index f791a39..8cbca92 100644 --- a/config.toml +++ b/config.toml @@ -1,11 +1,28 @@ -baseurl = "https://actix.rs" -title = "actix" -languageCode = "en-us" -canonifyURLs = true -googleAnalytics = "UA-110322332-1" -pygmentsUseClasses = true -pygmentsCodeFences = true +defaultContentLanguageInSubdir = true +enableRobotsTXT = true + +DefaultContentLanguage = "en" + +[languages] + [languages.en] + baseURL = "https://actix.rs" + title = "actix" + languageCode = "en-us" + canonifyURLs = true + googleAnalytics = "UA-110322332-1" + pygmentsUseClasses = true + pygmentsCodeFences = true + weight = 1 + [languages.cn] + baseURL = "https://actix.rs/cn" + title = "actix" + languageCode = "zh-cn" + canonifyURLs = true + googleAnalytics = "UA-110322332-1" + pygmentsUseClasses = true + pygmentsCodeFences = true + weight = 2 [params] actixVersion = "0.5" -actixWebVersion = "0.6" +actixWebVersion = "0.6" \ No newline at end of file diff --git a/content/_index.cn.md b/content/_index.cn.md new file mode 100644 index 0000000..2a3865f --- /dev/null +++ b/content/_index.cn.md @@ -0,0 +1,3 @@ +--- +title: Actix中文社区 +--- diff --git a/content/code/_index.cn.md b/content/code/_index.cn.md new file mode 100644 index 0000000..3ac9194 --- /dev/null +++ b/content/code/_index.cn.md @@ -0,0 +1,19 @@ +--- +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) + +欢迎共建此中文网站 Github [actix-cn-website](https://github.com/actix-cn/actix-cn-website) + +Actix在MIT和Apache 2下获得双重许可。[许可证文本](license/). diff --git a/content/code/license.cn.md b/content/code/license.cn.md new file mode 100644 index 0000000..a99e01a --- /dev/null +++ b/content/code/license.cn.md @@ -0,0 +1,201 @@ +--- +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. diff --git a/content/community/_index.cn.md b/content/community/_index.cn.md new file mode 100644 index 0000000..af7aeaa --- /dev/null +++ b/content/community/_index.cn.md @@ -0,0 +1,22 @@ +--- +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)的actor系统 +和 [actix-web](https://github.com/actix/actix-web) +高水平Web框架。 + +我们是一个热情的社区,所以不要害怕参与. [我们遵守的行为准则](coc/). + +# 案例 +如果你还没有准备好,这里有一些案例 + +- [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. \ No newline at end of file diff --git a/content/community/coc.cn.md b/content/community/coc.cn.md new file mode 100644 index 0000000..065498b --- /dev/null +++ b/content/community/coc.cn.md @@ -0,0 +1,49 @@ +--- +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/ diff --git a/content/docs/Installation.cn.md b/content/docs/Installation.cn.md new file mode 100644 index 0000000..ceecbe4 --- /dev/null +++ b/content/docs/Installation.cn.md @@ -0,0 +1,40 @@ +--- +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 +``` diff --git a/content/docs/_index.cn.md b/content/docs/_index.cn.md new file mode 100644 index 0000000..eed9c17 --- /dev/null +++ b/content/docs/_index.cn.md @@ -0,0 +1,14 @@ +--- +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稳定的角度出发。 diff --git a/content/docs/application.cn.md b/content/docs/application.cn.md new file mode 100644 index 0000000..a7fa1d6 --- /dev/null +++ b/content/docs/application.cn.md @@ -0,0 +1,75 @@ +--- +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" >}} diff --git a/content/docs/autoreload.cn.md b/content/docs/autoreload.cn.md new file mode 100644 index 0000000..66a80c4 --- /dev/null +++ b/content/docs/autoreload.cn.md @@ -0,0 +1,65 @@ +--- +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 +``` diff --git a/content/docs/databases.cn.md b/content/docs/databases.cn.md new file mode 100644 index 0000000..ddca95d --- /dev/null +++ b/content/docs/databases.cn.md @@ -0,0 +1,119 @@ +--- +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; +} +``` + +这是我们actor的定义。现在,我们必须定义创建用户消息和响应。 + +```rust +struct CreateUser { + name: String, +} + +impl Message for CreateUser { + type Result = Result; +} +``` + +我们可以向演员发送`CreateUser`消息`DbExecutor` actor,因此我们将收到一个 `User`实例。接下来,我们必须为此消息定义处理程序实现。 + +```rust +impl Handler for DbExecutor { + type Result = Result; + + 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::(&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, +} + +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) -> Box> { + 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)文档中找到 。 diff --git a/content/docs/errors.cn.md b/content/docs/errors.cn.md new file mode 100644 index 0000000..4cb1404 --- /dev/null +++ b/content/docs/errors.cn.md @@ -0,0 +1,238 @@ +--- +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> Responder for Result +``` + +`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 { + 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 diff --git a/content/docs/extractors.cn.md b/content/docs/extractors.cn.md new file mode 100644 index 0000000..489e4f6 --- /dev/null +++ b/content/docs/extractors.cn.md @@ -0,0 +1,284 @@ +--- +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)) -> 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::::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 Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&mut self, req: HttpRequest) -> Self::Result { + let params = Path::<(String, String)>::extract(&req); + let info = Json::::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 { + 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) -> Result { + 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) -> 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) -> Result { + 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) -> Result { + 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) -> Result { + 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)) -> 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. diff --git a/content/docs/getting-started.cn.md b/content/docs/getting-started.cn.md new file mode 100644 index 0000000..b90feaa --- /dev/null +++ b/content/docs/getting-started.cn.md @@ -0,0 +1,40 @@ +--- +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/)。 diff --git a/content/docs/handlers.cn.md b/content/docs/handlers.cn.md new file mode 100644 index 0000000..9b7148a --- /dev/null +++ b/content/docs/handlers.cn.md @@ -0,0 +1,292 @@ +--- +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> { + ... +} +``` + +*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 Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&mut self, req: HttpRequest) -> 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); + +impl Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&mut self, req: HttpRequest) -> 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(self, req: &HttpRequest) -> Result { + 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> { + + result(Ok(HttpResponse::Ok() + .content_type("text/html") + .body(format!("Hello!")))) + .responder() +} + +fn index2(req: HttpRequest) -> Box> { + 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`, 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>, 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>>; + +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. diff --git a/content/docs/http2.cn.md b/content/docs/http2.cn.md new file mode 100644 index 0000000..19be03c --- /dev/null +++ b/content/docs/http2.cn.md @@ -0,0 +1,45 @@ +--- +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). diff --git a/content/docs/middleware.cn.md b/content/docs/middleware.cn.md new file mode 100644 index 0000000..e93a16a --- /dev/null +++ b/content/docs/middleware.cn.md @@ -0,0 +1,250 @@ +--- +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 Middleware 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) -> Result { + 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, mut resp: HttpResponse) + -> Result + { + 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::("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(_: &mut HttpRequest, resp: HttpResponse) -> Result { + 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(); +} +``` diff --git a/content/docs/request.cn.md b/content/docs/request.cn.md new file mode 100644 index 0000000..ba7b29c --- /dev/null +++ b/content/docs/request.cn.md @@ -0,0 +1,207 @@ +--- +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` 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) -> Result { + 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> { + 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> { + // `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::(&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> { + // 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> { + req.urlencoded::() // <- 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> { + req.from_err() + .fold((), |_, chunk| { + println!("Chunk: {:?}", chunk); + result::<_, error::PayloadError>(Ok(())) + }) + .map(|_| HttpResponse::Ok().finish()) + .responder() +} +``` diff --git a/content/docs/response.cn.md b/content/docs/response.cn.md new file mode 100644 index 0000000..6bceded --- /dev/null +++ b/content/docs/response.cn.md @@ -0,0 +1,140 @@ +--- +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 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> { + 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")))))) +} +``` diff --git a/content/docs/sentry.cn.md b/content/docs/sentry.cn.md new file mode 100644 index 0000000..0a36903 --- /dev/null +++ b/content/docs/sentry.cn.md @@ -0,0 +1,61 @@ +--- +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); +}); +``` \ No newline at end of file diff --git a/content/docs/server.cn.md b/content/docs/server.cn.md new file mode 100644 index 0000000..e1baddb --- /dev/null +++ b/content/docs/server.cn.md @@ -0,0 +1,101 @@ +--- +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) +方法禁用信号处理 。 diff --git a/content/docs/static-files.cn.md b/content/docs/static-files.cn.md new file mode 100644 index 0000000..f94ef00 --- /dev/null +++ b/content/docs/static-files.cn.md @@ -0,0 +1,46 @@ +--- +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 { + 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)方法来配置此重定向。 diff --git a/content/docs/testing.cn.md b/content/docs/testing.cn.md new file mode 100644 index 0000000..3fb0f83 --- /dev/null +++ b/content/docs/testing.cn.md @@ -0,0 +1,236 @@ +--- +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| 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, 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; +} + +impl StreamHandler 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()))); +} +``` diff --git a/content/docs/url-dispatch.cn.md b/content/docs/url-dispatch.cn.md new file mode 100644 index 0000000..b69a7a7 --- /dev/null +++ b/content/docs/url-dispatch.cn.md @@ -0,0 +1,450 @@ +--- +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) -> 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) -> Future + '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" >}} diff --git a/content/docs/websockets.cn.md b/content/docs/websockets.cn.md new file mode 100644 index 0000000..43e0b5e --- /dev/null +++ b/content/docs/websockets.cn.md @@ -0,0 +1,44 @@ +--- +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; +} + +/// Handler for ws::Message message +impl StreamHandler 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连接进行聊天 diff --git a/content/docs/whatis.cn.md b/content/docs/whatis.cn.md new file mode 100644 index 0000000..7d67719 --- /dev/null +++ b/content/docs/whatis.cn.md @@ -0,0 +1,15 @@ +--- +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可以稳定发布。 From 29171e9b92140cbf4a993cbf743acd56c4034043 Mon Sep 17 00:00:00 2001 From: krircc Date: Fri, 22 Jun 2018 23:38:52 +0800 Subject: [PATCH 02/27] Update config.toml --- config.toml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config.toml b/config.toml index 8cbca92..64189fc 100644 --- a/config.toml +++ b/config.toml @@ -2,10 +2,8 @@ defaultContentLanguageInSubdir = true enableRobotsTXT = true DefaultContentLanguage = "en" - -[languages] - [languages.en] - baseURL = "https://actix.rs" +[languages.en] + baseURL = "https://actix.rs/en" title = "actix" languageCode = "en-us" canonifyURLs = true @@ -13,7 +11,7 @@ DefaultContentLanguage = "en" pygmentsUseClasses = true pygmentsCodeFences = true weight = 1 - [languages.cn] +[languages.cn] baseURL = "https://actix.rs/cn" title = "actix" languageCode = "zh-cn" @@ -22,7 +20,8 @@ DefaultContentLanguage = "en" pygmentsUseClasses = true pygmentsCodeFences = true weight = 2 + [params] actixVersion = "0.5" -actixWebVersion = "0.6" \ No newline at end of file +actixWebVersion = "0.6" From a0b1f54a3f4d70e451f3e92c8927bf3adb45d3d6 Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 06:12:56 +0800 Subject: [PATCH 03/27] let actix.rs for en --- config.toml | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/config.toml b/config.toml index 8cbca92..fb2aa1d 100644 --- a/config.toml +++ b/config.toml @@ -3,25 +3,24 @@ enableRobotsTXT = true DefaultContentLanguage = "en" -[languages] - [languages.en] - baseURL = "https://actix.rs" - title = "actix" - languageCode = "en-us" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true - weight = 1 - [languages.cn] - baseURL = "https://actix.rs/cn" - title = "actix" - languageCode = "zh-cn" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true - weight = 2 +[languages.en] + baseURL = "https://actix.rs" + title = "actix" + languageCode = "en-us" + canonifyURLs = true + googleAnalytics = "UA-110322332-1" + pygmentsUseClasses = true + pygmentsCodeFences = true + weight = 1 +[languages.cn] + baseURL = "https://actix.rs/cn" + title = "actix" + languageCode = "zh-cn" + canonifyURLs = true + googleAnalytics = "UA-110322332-1" + pygmentsUseClasses = true + pygmentsCodeFences = true + weight = 2 [params] actixVersion = "0.5" From 746541e2183051cf85cad370ce9557c85db74dbd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 23 Jun 2018 00:27:02 +0200 Subject: [PATCH 04/27] Remove en into the root --- config.toml | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/config.toml b/config.toml index 64189fc..9b0c96e 100644 --- a/config.toml +++ b/config.toml @@ -1,26 +1,19 @@ -defaultContentLanguageInSubdir = true -enableRobotsTXT = true - DefaultContentLanguage = "en" +enableRobotsTXT = true +canonifyURLs = true +googleAnalytics = "UA-110322332-1" +pygmentsUseClasses = true +pygmentsCodeFences = true +baseURL = "https://actix.rs/" + [languages.en] - baseURL = "https://actix.rs/en" title = "actix" languageCode = "en-us" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true weight = 1 [languages.cn] - baseURL = "https://actix.rs/cn" title = "actix" languageCode = "zh-cn" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true weight = 2 - [params] actixVersion = "0.5" From c41a02fc0a5f0d8a99d3f991c9dbdbe715c90d56 Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 07:43:29 +0800 Subject: [PATCH 05/27] let actix.rs for en --- .deploy.sh | 1 + config.toml | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.deploy.sh b/.deploy.sh index 562e6cf..2d37277 100644 --- a/.deploy.sh +++ b/.deploy.sh @@ -1,6 +1,7 @@ #!/bin/bash if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" ]]; then cp CNAME public + mv public/en/* public && rm -R public/en git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -b master -r https://"$GH_TOKEN"@github.com/actix/actix.github.io.git public && echo "Uploaded documentation" diff --git a/config.toml b/config.toml index fb2aa1d..2d3b708 100644 --- a/config.toml +++ b/config.toml @@ -1,25 +1,23 @@ +title = "actix" +languageCode = "en-us" +canonifyURLs = true +googleAnalytics = "UA-110322332-1" +pygmentsUseClasses = true +pygmentsCodeFences = true + defaultContentLanguageInSubdir = true enableRobotsTXT = true DefaultContentLanguage = "en" +[languages] [languages.en] baseURL = "https://actix.rs" - title = "actix" - languageCode = "en-us" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true + languageName = "English" weight = 1 [languages.cn] baseURL = "https://actix.rs/cn" - title = "actix" - languageCode = "zh-cn" - canonifyURLs = true - googleAnalytics = "UA-110322332-1" - pygmentsUseClasses = true - pygmentsCodeFences = true + languageName = "Chinese" weight = 2 [params] From 4349263ff5ae225b23e732d3740408537a6f576b Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 11:36:37 +0800 Subject: [PATCH 06/27] cn home page for cn --- content/_index.cn.md | 2 +- layouts/index.html | 416 +++++++++++++++++++++++------------ layouts/partials/footer.html | 13 ++ 3 files changed, 294 insertions(+), 137 deletions(-) diff --git a/content/_index.cn.md b/content/_index.cn.md index 2a3865f..b10170b 100644 --- a/content/_index.cn.md +++ b/content/_index.cn.md @@ -1,3 +1,3 @@ --- -title: Actix中文社区 +title: Actix中文 --- diff --git a/layouts/index.html b/layouts/index.html index 10ea459..7ee8f05 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -1,154 +1,298 @@ {{ partial "header" . }} -
-
- -

rust's powerful actor system and most fun web framework

-
-
- -
-
-
-
-

- - Type Safe -

-

Forget about stringly typed objects, from request to response, everything has types.

- -

- - Feature Rich -

-

Actix provides a lot of features out of box. WebSockets, HTTP/2, pipelining etc.

- -

- - Extensible -

-

Easily create your own libraries that any Actix application can use.

- -

- - Blazingly Fast -

-

Actix is blazingly fast. Check yourself.

+
+
+
+ +

Rust的actor系统和有趣的web框架

-
-
- {{ highlight `extern crate actix_web; -use actix_web::{server, App, HttpRequest, Responder}; - -fn greet(req: HttpRequest) -> impl Responder { - let to = req.match_info().get("name").unwrap_or("World"); - format!("Hello {}!", to) -} - -fn main() { - server::new(|| { + +
+
+
+
+

+ + 类型安全 +

+

忘记关于字符串类型的对象,从请求到响应,一切都有类型,异步。

+ +

+ + 特性丰富 +

+

Actix提供了丰富的特性开箱即用。WebSockets,HTTP/2,流,管道,SSL,异步HTTTP客户端等一应俱全.

+ +

+ + 扩展性强 +

+

轻松创建任何基于Actix应用的自己的特色库。

+ +

+ + 速度极快 +

+

Actix 具有顶级的速度. Check yourself.

+
+
+
+
+ {{ 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" "" }} +
+
+
+
+
+
+

灵活的请求响应

+

+ Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。 +

+ {{ 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" "" }} +
+
+

强大的Extractors

+

+ Actix提供了一个强大的提取器系统,可以从传入的HTTP请求中提取数据并将其传递给您的视图函数。这不仅可以创建方便的API, + 而且还意味着您的视图函数可以是同步代码,并且仍然可以受益于异步IO处理。 +

+ {{ highlight `#[derive(Deserialize)] + struct Event { + timestamp: f64, + kind: String, + tags: Vec, + } + + fn capture_event(evt: Json) -> impl Responder { + let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); + format!("got event {}", id) + }` "rust" "" }} +
+
+

轻松处理表单

+

+ 处理multipart/ urlencoded表单数据很容易。只需定义一个可以反序列化的结构,actix就可以处理剩下的部分。 +

+ {{ highlight `#[derive(Deserialize)] + struct Register { + username: String, + country: String, + } + + fn register(data: Form) -> impl Responder { + format!("Hello {} from {}!", data.username, data.country) + }` "rust" "" }} +
+
+

请求路由

+

+ 一个actix应用程序带有一个URL路由系统,可以让你在URL上匹配并调用单个处理程序。为了获得额外的灵活性,可以使用域。 +

+ {{ highlight `fn index(req: HttpRequest) -> impl Responder { + "Hello from the index page" + } + + fn hello(path: Path) -> impl Responder { + format!("Hello {}!", *path) + } + + fn main() { App::new() - .resource("/", |r| r.f(greet)) - .resource("/{name}", |r| r.f(greet)) - }) - .bind("127.0.0.1:8000") - .expect("Can not bind to port 8000") - .run(); -}` "rust" "" }} + .resource("/", |r| r.method(Method::Get).with(index)) + .resource("/hello/{name}", |r| r.method(Method::Get).with(hello)) + .finish(); + }` "rust" "" }} +
+
+
-
-
-
-

Flexible Responders

-

- Handler functions in actix can return a wide range of objects that - implement the Responder trait. This makes it a breeze - to return consistent responses from your APIs. -

- {{ highlight `#[derive(Serialize)] -struct Measurement { - temperature: f32, -} -fn hello_world() -> impl Responder { - "Hello World!" -} +
+
+
+ +

rust's powerful actor system and most fun web framework

+
+
-fn current_temperature(_req: HttpRequest) -> impl Responder { - Json(Measurement { temperature: 42.3 }) -}` "rust" "" }} +
+
+ -
-

Powerful Extractors

-

- Actix comes with a powerful extractor system that extracts data - from the incoming HTTP request and passes it to your view functions. - Not only does this make for a convenient API but it also means that - your view functions can be synchronous code and still benefit - from asynchronous IO handling. -

- {{ highlight `#[derive(Deserialize)] -struct Event { - timestamp: f64, - kind: String, - tags: Vec, -} +
+
+ {{ highlight `extern crate actix_web; + use actix_web::{server, App, HttpRequest, Responder}; -fn capture_event(evt: Json) -> impl Responder { - let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); - format!("got event {}", id) -}` "rust" "" }} -
-
-

Easy Form Handling

-

- Handling multipart/urlencoded form data is easy. Just define - a structure that can be deserialized and actix will handle - the rest. -

- {{ highlight `#[derive(Deserialize)] -struct Register { - username: String, - country: String, -} + fn greet(req: HttpRequest) -> impl Responder { + let to = req.match_info().get("name").unwrap_or("World"); + format!("Hello {}!", to) + } -fn register(data: Form) -> impl Responder { - format!("Hello {} from {}!", data.username, data.country) -}` "rust" "" }} -
-
-

Request Routing

-

- An actix app comes with a URL routing system that lets you match on - URLs and invoke individual handlers. For extra flexibility, scopes - can be used. -

- {{ highlight `fn index(req: HttpRequest) -> impl Responder { - "Hello from the index page" -} - -fn hello(path: Path) -> 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" "" }} + fn main() { + server::new(|| { + App::new() + .resource("/", |r| r.f(greet)) + .resource("/{name}", |r| r.f(greet)) + }) + .bind("127.0.0.1:8000") + .expect("Can not bind to port 8000") + .run(); + }` "rust" "" }} +
-
- +
+
+
+

Flexible Responders

+

+ Handler functions in actix can return a wide range of objects that + implement the Responder trait. This makes it a breeze + to return consistent responses from your APIs. +

+ {{ 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" "" }} +
+
+

Powerful Extractors

+

+ Actix comes with a powerful extractor system that extracts data + from the incoming HTTP request and passes it to your view functions. + Not only does this make for a convenient API but it also means that + your view functions can be synchronous code and still benefit + from asynchronous IO handling. +

+ {{ highlight `#[derive(Deserialize)] + struct Event { + timestamp: f64, + kind: String, + tags: Vec, + } + + fn capture_event(evt: Json) -> impl Responder { + let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); + format!("got event {}", id) + }` "rust" "" }} +
+
+

Easy Form Handling

+

+ Handling multipart/urlencoded form data is easy. Just define + a structure that can be deserialized and actix will handle + the rest. +

+ {{ highlight `#[derive(Deserialize)] + struct Register { + username: String, + country: String, + } + + fn register(data: Form) -> impl Responder { + format!("Hello {} from {}!", data.username, data.country) + }` "rust" "" }} +
+
+

Request Routing

+

+ An actix app comes with a URL routing system that lets you match on + URLs and invoke individual handlers. For extra flexibility, scopes + can be used. +

+ {{ highlight `fn index(req: HttpRequest) -> impl Responder { + "Hello from the index page" + } + + fn hello(path: Path) -> 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" "" }} +
+
+
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index 6593ca4..435d3cc 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -15,5 +15,18 @@ {{ template "_internal/google_analytics.html" . }} + + From 88300abd1f4a0608727c0469c562e8cd46b13448 Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 11:58:06 +0800 Subject: [PATCH 07/27] fix typo --- layouts/index.html | 285 ++++++++++++++++++----------------- layouts/partials/footer.html | 4 +- 2 files changed, 145 insertions(+), 144 deletions(-) diff --git a/layouts/index.html b/layouts/index.html index 7ee8f05..ea96734 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -1,147 +1,5 @@ {{ partial "header" . }} -
-
-
- -

Rust的actor系统和有趣的web框架

-
-
- -
-
- -
-
- {{ 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" "" }} -
-
-
-
-
-
-

灵活的请求响应

-

- Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。 -

- {{ 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" "" }} -
-
-

强大的Extractors

-

- Actix提供了一个强大的提取器系统,可以从传入的HTTP请求中提取数据并将其传递给您的视图函数。这不仅可以创建方便的API, - 而且还意味着您的视图函数可以是同步代码,并且仍然可以受益于异步IO处理。 -

- {{ highlight `#[derive(Deserialize)] - struct Event { - timestamp: f64, - kind: String, - tags: Vec, - } - - fn capture_event(evt: Json) -> impl Responder { - let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); - format!("got event {}", id) - }` "rust" "" }} -
-
-

轻松处理表单

-

- 处理multipart/ urlencoded表单数据很容易。只需定义一个可以反序列化的结构,actix就可以处理剩下的部分。 -

- {{ highlight `#[derive(Deserialize)] - struct Register { - username: String, - country: String, - } - - fn register(data: Form) -> impl Responder { - format!("Hello {} from {}!", data.username, data.country) - }` "rust" "" }} -
-
-

请求路由

-

- 一个actix应用程序带有一个URL路由系统,可以让你在URL上匹配并调用单个处理程序。为了获得额外的灵活性,可以使用域。 -

- {{ highlight `fn index(req: HttpRequest) -> impl Responder { - "Hello from the index page" - } - - fn hello(path: Path) -> 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" "" }} -
-
- -
-
-
-
@@ -297,4 +155,147 @@
+
+
+
+ +

Rust的actor系统和有趣的web框架

+
+
+ +
+
+ +
+
+ {{ 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" "" }} +
+
+
+
+
+
+

灵活的请求响应

+

+ Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。 +

+ {{ 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" "" }} +
+
+

强大的Extractors

+

+ Actix提供了一个强大的提取器系统,可以从传入的HTTP请求中提取数据并将其传递给您的视图函数。这不仅可以创建方便的API, + 而且还意味着您的视图函数可以是同步代码,并且仍然可以受益于异步IO处理。 +

+ {{ highlight `#[derive(Deserialize)] + struct Event { + timestamp: f64, + kind: String, + tags: Vec, + } + + fn capture_event(evt: Json) -> impl Responder { + let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); + format!("got event {}", id) + }` "rust" "" }} +
+
+

轻松处理表单

+

+ 处理multipart/ urlencoded表单数据很容易。只需定义一个可以反序列化的结构,actix就可以处理剩下的部分。 +

+ {{ highlight `#[derive(Deserialize)] + struct Register { + username: String, + country: String, + } + + fn register(data: Form) -> impl Responder { + format!("Hello {} from {}!", data.username, data.country) + }` "rust" "" }} +
+
+

请求路由

+

+ 一个actix应用程序带有一个URL路由系统,可以让你在URL上匹配并调用单个处理程序。为了获得额外的灵活性,可以使用域。 +

+ {{ highlight `fn index(req: HttpRequest) -> impl Responder { + "Hello from the index page" + } + + fn hello(path: Path) -> 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" "" }} +
+
+ +
+
+
+ + {{ partial "footer" . }} diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index 435d3cc..ff5f9c3 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -18,11 +18,11 @@ - + {{ template "_internal/google_analytics.html" . }} - - diff --git a/static/css/actix.css b/static/css/actix.css index 34db1a0..3c0537d 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -600,3 +600,27 @@ h5:hover a { width: 88%; } } + + +#act-cn-tabs { + padding: 2rem 1.5rem 0.5rem 2rem; + margin: 2rem auto; + background:#dceaea; +} + +.act-menu{ + width: 24%; + float: right; + border-right:#cccccc solid 1px; +} +.act-menu li{ + text-align:center; + line-height:44px; + font-size:15px; + overflow:hidden; +} +.act-menu li.off{ + background:#FFFFFF; + color:#000000; + font-weight:bold; +} \ No newline at end of file diff --git a/static/js/actix.js b/static/js/actix.js index ef2cb66..44b1c8b 100644 --- a/static/js/actix.js +++ b/static/js/actix.js @@ -1,3 +1,30 @@ +window.onload = function(){ + if (window.location.href.search("cn") != -1) { + var actix_home = document.getElementById("act-home") + actix_home.style.display = "none" + }else{ + var actix_home_cn = document.getElementById("act-home-cn") + actix_home_cn.style.display = "none" + } +} + +function setTab(name,cursel){ + let tlinks = document.getElementById("act-cn-tabs").getElementsByTagName('li') + for(var i=1; i<=tlinks.length; i++){ + var menu = document.getElementById(name+i); + var menudiv = document.getElementById("con_"+name+"_"+i); + if(i==cursel){ + menu.className="off"; + menudiv.style.display="block"; + } + else{ + menu.className=""; + menudiv.style.display="none"; + } + } +} + + (function() { function activateFeature(sel) { $('div.actix-feature').hide(); @@ -36,3 +63,5 @@ initFeatureSelector(); }); })(); + + From 82a23727837f15f97baef30f9d8b3de7b2efbee9 Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 16:30:43 +0800 Subject: [PATCH 11/27] fix index page --- content/code/_index.cn.md | 3 +-- layouts/partials/footer.html | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/content/code/_index.cn.md b/content/code/_index.cn.md index 3ac9194..2a9db60 100644 --- a/content/code/_index.cn.md +++ b/content/code/_index.cn.md @@ -13,7 +13,6 @@ on github](https://github.com/actix) * [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) - -欢迎共建此中文网站 Github [actix-cn-website](https://github.com/actix-cn/actix-cn-website) +* [this website](https://github.com/actix/actix-website) Actix在MIT和Apache 2下获得双重许可。[许可证文本](license/). diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index 956482b..72f0434 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -16,4 +16,4 @@ {{ template "_internal/google_analytics.html" . }} - + \ No newline at end of file From 2900da086cad35a264c4f4a5af08f82119063547 Mon Sep 17 00:00:00 2001 From: krircc Date: Sat, 23 Jun 2018 22:31:17 +0800 Subject: [PATCH 12/27] fix mobile style in cn home --- layouts/index.html | 16 ++++++++-------- static/css/actix.css | 22 ++++++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/layouts/index.html b/layouts/index.html index 4391657..62a2de2 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -214,12 +214,7 @@
-
    -
  • 灵活的请求响应
  • -
  • 强大的Extractors
  • -
  • 轻松处理表单
  • -
  • 请求路由
  • -
+

灵活的请求响应

@@ -293,10 +288,15 @@ }` "rust" "" }}
+ +
    +
  • 灵活的请求响应
  • +
  • 强大的Extractors
  • +
  • 轻松处理表单
  • +
  • 请求路由
  • +
- -
diff --git a/static/css/actix.css b/static/css/actix.css index 3c0537d..f02d0e7 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -603,16 +603,10 @@ h5:hover a { #act-cn-tabs { - padding: 2rem 1.5rem 0.5rem 2rem; + padding: 2rem 2rem 1rem 2rem; margin: 2rem auto; background:#dceaea; } - -.act-menu{ - width: 24%; - float: right; - border-right:#cccccc solid 1px; -} .act-menu li{ text-align:center; line-height:44px; @@ -620,7 +614,19 @@ h5:hover a { overflow:hidden; } .act-menu li.off{ + padding: 0 1.5rem; background:#FFFFFF; - color:#000000; + color:#589c9e; font-weight:bold; +} + +@media (min-width: 768px) { + #act-cn-tabs { + display: flex; + flex-flow: row; + padding: 2rem 1rem 1rem 2rem; + } + #act-cn-tabs #content { + width: 77%; + } } \ No newline at end of file From 1cd36888c23c2b2115f38b8331bb039a7c7c790a Mon Sep 17 00:00:00 2001 From: krircc Date: Sun, 24 Jun 2018 03:14:26 +0800 Subject: [PATCH 13/27] add language option at nav --- layouts/index.html | 2 +- layouts/partials/header.html | 7 +++++++ static/css/actix.css | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/layouts/index.html b/layouts/index.html index 62a2de2..31889eb 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -159,7 +159,7 @@
-

Rust的actor系统和有趣的web框架

+

Rust强大的actor系统和有趣的web框架

diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 698d50c..e36222f 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -42,6 +42,13 @@
+
diff --git a/static/css/actix.css b/static/css/actix.css index f02d0e7..a7a8656 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -169,8 +169,19 @@ img { line-height: 60px; height: 60px; padding-top: 0; + list-style: none; +} +.navbar-nav #language ul { + display: none; +} +.navbar-nav #language:hover .subitem { + margin: -1rem 0 0 -6.6rem; + display: block; + background-color: #e8f9fc; +} +.navbar-nav #language ul li { + display: block; } - .doctoggle { margin: -1rem 0 2rem 0; display: none; @@ -560,6 +571,11 @@ h5:hover a { .actix-footer-social a .fa-github { margin-right: 1rem; } + .navbar-nav #language:hover .subitem { + margin: 0 -2rem 0 -1rem; + display: block; + background-color: #e8f9fc; + } } @media (min-width: 480px) and (max-width: 576px) { header .nav { From ada7b80fe95904bfd0c7ac31c92e5d37617a4db8 Mon Sep 17 00:00:00 2001 From: krircc Date: Sun, 24 Jun 2018 10:00:12 +0800 Subject: [PATCH 14/27] let nav i18n --- layouts/partials/header.html | 28 ++++++++++++++++++++++++++-- static/css/actix.css | 6 +++++- static/img/i18n.jpeg | Bin 0 -> 54977 bytes static/js/actix.js | 15 +++++++++++---- 4 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 static/img/i18n.jpeg diff --git a/layouts/partials/header.html b/layouts/partials/header.html index e36222f..4b9a8c8 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -26,7 +26,7 @@
diff --git a/static/css/actix.css b/static/css/actix.css index a7a8656..066d506 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -171,13 +171,17 @@ img { padding-top: 0; list-style: none; } +.navbar-nav #language img { + width: 25px; + height:22px; +} .navbar-nav #language ul { display: none; } .navbar-nav #language:hover .subitem { margin: -1rem 0 0 -6.6rem; display: block; - background-color: #e8f9fc; + background-color: #dcfaf7; } .navbar-nav #language ul li { display: block; diff --git a/static/img/i18n.jpeg b/static/img/i18n.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..43b9a49b32224324373d4339e708ccad417208e3 GIT binary patch literal 54977 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 Date: Sun, 24 Jun 2018 22:31:09 +0800 Subject: [PATCH 15/27] add example project and blog nav --- .../blog/2018-06-01-old-blog-example.cn.md | 8 +++ .../blog/2018-06-03-new-blog-example.cn.md | 8 +++ content/blog/_index.cn.md | 10 +++ content/community/_index.cn.md | 21 ++++--- layouts/blog/baseof.html | 63 +++++++++++++++++++ layouts/blog/blog.html | 3 + layouts/blog/single.html | 3 + layouts/partials/header.html | 11 ++-- static/css/actix.css | 17 +++++ 9 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 content/blog/2018-06-01-old-blog-example.cn.md create mode 100644 content/blog/2018-06-03-new-blog-example.cn.md create mode 100644 content/blog/_index.cn.md create mode 100644 layouts/blog/baseof.html create mode 100644 layouts/blog/blog.html create mode 100644 layouts/blog/single.html diff --git a/content/blog/2018-06-01-old-blog-example.cn.md b/content/blog/2018-06-01-old-blog-example.cn.md new file mode 100644 index 0000000..15ce249 --- /dev/null +++ b/content/blog/2018-06-01-old-blog-example.cn.md @@ -0,0 +1,8 @@ +--- +title: 旧 +description: 2018-01-01 +menu: blog_2018 +weight: 999 +--- + +# Title diff --git a/content/blog/2018-06-03-new-blog-example.cn.md b/content/blog/2018-06-03-new-blog-example.cn.md new file mode 100644 index 0000000..529d42d --- /dev/null +++ b/content/blog/2018-06-03-new-blog-example.cn.md @@ -0,0 +1,8 @@ +--- +title: 新 +description: 2018-06-11 +menu: blog_2018 +weight: 998 +--- + +# Title diff --git a/content/blog/_index.cn.md b/content/blog/_index.cn.md new file mode 100644 index 0000000..20835d3 --- /dev/null +++ b/content/blog/_index.cn.md @@ -0,0 +1,10 @@ +--- +title: 博客 +description: 快乐的Actix之旅 +menu: + blog_intro: + name: 欢迎 +weight: 1 +--- + +欢迎开始快乐的Actix之旅, 你可以从左侧的时间点选择开始。 \ No newline at end of file diff --git a/content/community/_index.cn.md b/content/community/_index.cn.md index af7aeaa..91870c6 100644 --- a/content/community/_index.cn.md +++ b/content/community/_index.cn.md @@ -5,18 +5,25 @@ description: 人生中最美好的事物就是分享 # 加入我们 -想与其他人讨论问题吗?该Actix的[gitter](https://gitter.im/actix/actix)频道或 [reddit](https://www.reddit.com/r/actix/)社区和中文社区QQ群:570065685 是你最好的起点。 +想与其他人讨论问题吗?Actix的你最好的起点是: -如果你认为你发现了一个bug,最好直接去 -[github](https://github.com/actix) . 这里有2个主要仓库. [actix](https://github.com/actix/actix)的actor系统 -和 [actix-web](https://github.com/actix/actix-web) -高水平Web框架。 +- [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/). # 案例 如果你还没有准备好,这里有一些案例 -- [muro](https://github.com/OUISRC/muro) : The interest and community for internet .(reddit clone) +- [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. \ No newline at end of file +- [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) + - [yinyanlv/partner-client](https://github.com/yinyanlv/partner-client) \ No newline at end of file diff --git a/layouts/blog/baseof.html b/layouts/blog/baseof.html new file mode 100644 index 0000000..4e0dd65 --- /dev/null +++ b/layouts/blog/baseof.html @@ -0,0 +1,63 @@ +{{ partial "header" . }} +{{ $currentURL := .URL }} + +
+
+

{{ .Title }}

+ {{ if .Description }} +

{{ .Description }}

+ {{ end }} +
+
+ +
+
+
+ + + + + +
+
+
+ {{ block "main" . }}{{ end }} + + {{ with .PrevInSection }}{{ end }} +
+
+
+
+ +{{ partial "footer" . }} diff --git a/layouts/blog/blog.html b/layouts/blog/blog.html new file mode 100644 index 0000000..e0e8308 --- /dev/null +++ b/layouts/blog/blog.html @@ -0,0 +1,3 @@ +{{ define "main" }} + {{ .Content }} +{{ end }} diff --git a/layouts/blog/single.html b/layouts/blog/single.html new file mode 100644 index 0000000..e0e8308 --- /dev/null +++ b/layouts/blog/single.html @@ -0,0 +1,3 @@ +{{ define "main" }} + {{ .Content }} +{{ end }} diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 4b9a8c8..e1dfc6b 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -45,8 +45,8 @@ @@ -60,6 +60,9 @@ + @@ -69,8 +72,8 @@ diff --git a/static/css/actix.css b/static/css/actix.css index 066d506..3ebb6de 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -387,6 +387,23 @@ img { color: #333!important; 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; +} /* * From 9858d88aa7e67755b33a8c30edfa7c08272471c0 Mon Sep 17 00:00:00 2001 From: krircc Date: Sun, 24 Jun 2018 22:52:18 +0800 Subject: [PATCH 16/27] project info --- content/community/_index.cn.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/community/_index.cn.md b/content/community/_index.cn.md index 91870c6..13c4402 100644 --- a/content/community/_index.cn.md +++ b/content/community/_index.cn.md @@ -25,5 +25,5 @@ description: 人生中最美好的事物就是分享 - [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) - - [yinyanlv/partner-client](https://github.com/yinyanlv/partner-client) \ No newline at end of file +- [yinyanlv/partner](https://github.com/yinyanlv/partner) : 私人生活辅助系统,前后端分离,前段Angular6+,后端Actix-web + redis + mysql + - [yinyanlv/partner-client](https://github.com/yinyanlv/partner-client) : partner的web客户端 \ No newline at end of file From 83556270bc1c0258b4eeac7a0557af886b25e9ff Mon Sep 17 00:00:00 2001 From: krircc Date: Sun, 24 Jun 2018 22:54:30 +0800 Subject: [PATCH 17/27] fix detail --- layouts/partials/header.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layouts/partials/header.html b/layouts/partials/header.html index e1dfc6b..f80692e 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -45,7 +45,7 @@ @@ -73,7 +73,7 @@ From 513ef1378428f90ec715d46a6f8ac14472797965 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 24 Jun 2018 21:57:20 +0200 Subject: [PATCH 18/27] Fixed indentation on frontpage --- layouts/index.html | 212 ++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/layouts/index.html b/layouts/index.html index 31889eb..90f6ed0 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -40,23 +40,23 @@
{{ highlight `extern crate actix_web; - use actix_web::{server, App, HttpRequest, Responder}; +use actix_web::{server, App, HttpRequest, Responder}; - fn greet(req: HttpRequest) -> impl Responder { - let to = req.match_info().get("name").unwrap_or("World"); - format!("Hello {}!", to) - } +fn greet(req: HttpRequest) -> impl Responder { + let to = req.match_info().get("name").unwrap_or("World"); + format!("Hello {}!", to) +} - fn main() { - server::new(|| { - App::new() - .resource("/", |r| r.f(greet)) - .resource("/{name}", |r| r.f(greet)) - }) - .bind("127.0.0.1:8000") - .expect("Can not bind to port 8000") - .run(); - }` "rust" "" }} +fn main() { + server::new(|| { + App::new() + .resource("/", |r| r.f(greet)) + .resource("/{name}", |r| r.f(greet)) + }) + .bind("127.0.0.1:8000") + .expect("Can not bind to port 8000") + .run(); +}` "rust" "" }}
@@ -70,17 +70,17 @@ to return consistent responses from your APIs.

{{ highlight `#[derive(Serialize)] - struct Measurement { - temperature: f32, - } +struct Measurement { + temperature: f32, +} - fn hello_world() -> impl Responder { - "Hello World!" - } +fn hello_world() -> impl Responder { + "Hello World!" +} - fn current_temperature(_req: HttpRequest) -> impl Responder { - Json(Measurement { temperature: 42.3 }) - }` "rust" "" }} +fn current_temperature(_req: HttpRequest) -> impl Responder { + Json(Measurement { temperature: 42.3 }) +}` "rust" "" }}

Powerful Extractors

@@ -92,16 +92,16 @@ from asynchronous IO handling.

{{ highlight `#[derive(Deserialize)] - struct Event { - timestamp: f64, - kind: String, - tags: Vec, - } +struct Event { + timestamp: f64, + kind: String, + tags: Vec, +} - fn capture_event(evt: Json) -> impl Responder { - let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); - format!("got event {}", id) - }` "rust" "" }} +fn capture_event(evt: Json) -> impl Responder { + let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); + format!("got event {}", id) +}` "rust" "" }}

Easy Form Handling

@@ -111,14 +111,14 @@ the rest.

{{ highlight `#[derive(Deserialize)] - struct Register { - username: String, - country: String, - } +struct Register { + username: String, + country: String, +} - fn register(data: Form) -> impl Responder { - format!("Hello {} from {}!", data.username, data.country) - }` "rust" "" }} +fn register(data: Form) -> impl Responder { + format!("Hello {} from {}!", data.username, data.country) +}` "rust" "" }}

Request Routing

@@ -128,19 +128,19 @@ can be used.

{{ highlight `fn index(req: HttpRequest) -> impl Responder { - "Hello from the index page" - } + "Hello from the index page" +} - fn hello(path: Path) -> impl Responder { - format!("Hello {}!", *path) - } +fn hello(path: Path) -> 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" "" }} +fn main() { + App::new() + .resource("/", |r| r.method(Method::Get).with(index)) + .resource("/hello/{name}", |r| r.method(Method::Get).with(hello)) + .finish(); +}` "rust" "" }}
@@ -195,19 +195,19 @@
{{ 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" "" }} +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" "" }}
@@ -222,17 +222,17 @@ Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。

{{ 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" "" }} + struct Measurement { + temperature: f32, +} + +fn hello_world() -> impl Responder { + "Hello World!" +} + +fn current_temperature(_req: HttpRequest) -> impl Responder { + Json(Measurement { temperature: 42.3 }) +}` "rust" "" }}
From 20888bc58b8441bf399a83d612b18e9e3067daf4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 24 Jun 2018 23:03:40 +0200 Subject: [PATCH 19/27] Attempt to clean up the multilingual setup --- config.toml | 15 ++-- i18n/cn.toml | 8 ++ i18n/en.toml | 8 ++ layouts/index.cn.html | 149 +++++++++++++++++++++++++++++++++++ layouts/index.html | 148 +--------------------------------- layouts/partials/header.html | 50 +++--------- static/css/actix.css | 56 ++++++++----- static/js/actix.js | 36 --------- 8 files changed, 221 insertions(+), 249 deletions(-) create mode 100644 i18n/cn.toml create mode 100644 i18n/en.toml create mode 100644 layouts/index.cn.html diff --git a/config.toml b/config.toml index 154dd23..e574e1c 100644 --- a/config.toml +++ b/config.toml @@ -3,20 +3,19 @@ canonifyURLs = true googleAnalytics = "UA-110322332-1" pygmentsUseClasses = true pygmentsCodeFences = true - -defaultContentLanguageInSubdir = true +defaultContentLanguageInSubdir = false enableRobotsTXT = true - +enableMissingTranslationPlaceholders = true DefaultContentLanguage = "en" +baseURL = "https://actix.rs" -[languages] [languages.en] - baseURL = "https://actix.rs" - languageCode = "en-us" + languageCode = "en-US" + languageName = "English" weight = 1 [languages.cn] - baseURL = "https://actix.rs/cn" - languageCode = "zh-cn" + languageCode = "zh-CN" + languageName = "中文" weight = 2 [params] diff --git a/i18n/cn.toml b/i18n/cn.toml new file mode 100644 index 0000000..86f2acf --- /dev/null +++ b/i18n/cn.toml @@ -0,0 +1,8 @@ +[home] +other = "首页" +[docs] +other = "文档" +[community] +other = "社区" +[code] +other = "源码" diff --git a/i18n/en.toml b/i18n/en.toml new file mode 100644 index 0000000..9fddc1f --- /dev/null +++ b/i18n/en.toml @@ -0,0 +1,8 @@ +[home] +other = "Home" +[docs] +other = "Documentation" +[community] +other = "Community" +[code] +other = "Code" diff --git a/layouts/index.cn.html b/layouts/index.cn.html new file mode 100644 index 0000000..bb5e631 --- /dev/null +++ b/layouts/index.cn.html @@ -0,0 +1,149 @@ +{{ partial "header" . }} + +
+
+
+ +

Rust强大的actor系统和有趣的web框架

+
+
+ +
+
+
+
+

+ + 类型安全 +

+

忘记关于字符串类型的对象,从请求到响应,一切都有类型,异步。

+ +

+ + 特性丰富 +

+

Actix提供了丰富的特性开箱即用。WebSockets,HTTP/2,流,管道,SSL,异步HTTTP客户端等一应俱全.

+ +

+ + 扩展性强 +

+

轻松创建任何基于Actix应用的自己的特色库。

+ +

+ + 速度极快 +

+

Actix 具有顶级的速度. Check yourself.

+
+
+
+
+ {{ 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" "" }} +
+
+
+ + +
+
+
+

灵活的请求响应

+

+ Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。 +

+ {{ 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" "" }} +
+
+

强大的Extractors

+

+ Actix提供了一个强大的提取器系统,可以从传入的HTTP请求中提取数据并将其传递给您的视图函数。这不仅可以创建方便的API, + 而且还意味着您的视图函数可以是同步代码,并且仍然可以受益于异步IO处理。 +

+ {{ highlight `#[derive(Deserialize)] +struct Event { + timestamp: f64, + kind: String, + tags: Vec, +} + +fn capture_event(evt: Json) -> impl Responder { + let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); + format!("got event {}", id) +}` "rust" "" }} +
+
+

轻松处理表单

+

+ 处理multipart/ urlencoded表单数据很容易。只需定义一个可以反序列化的结构,actix就可以处理剩下的部分。 +

+ {{ highlight `#[derive(Deserialize)] +struct Register { + username: String, + country: String, +} + +fn register(data: Form) -> impl Responder { + format!("Hello {} from {}!", data.username, data.country) +}` "rust" "" }} +
+
+

请求路由

+

+ 一个actix应用程序带有一个URL路由系统,可以让你在URL上匹配并调用单个处理程序。为了获得额外的灵活性,可以使用域。 +

+ {{ highlight `fn index(req: HttpRequest) -> impl Responder { + "Hello from the index page" +} + +fn hello(path: Path) -> 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" "" }} +
+
+ + +
+
+
+ + +{{ partial "footer" . }} diff --git a/layouts/index.html b/layouts/index.html index 90f6ed0..55c5bbd 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -34,7 +34,7 @@ Blazingly Fast -

Actix is blazingly fast. Check yourself.

+

Actix is blazingly fast. See for yourself.

@@ -155,150 +155,4 @@ fn main() {
-
-
-
- -

Rust强大的actor系统和有趣的web框架

-
-
- -
-
-
-
-

- - 类型安全 -

-

忘记关于字符串类型的对象,从请求到响应,一切都有类型,异步。

- -

- - 特性丰富 -

-

Actix提供了丰富的特性开箱即用。WebSockets,HTTP/2,流,管道,SSL,异步HTTTP客户端等一应俱全.

- -

- - 扩展性强 -

-

轻松创建任何基于Actix应用的自己的特色库。

- -

- - 速度极快 -

-

Actix 具有顶级的速度. Check yourself.

-
-
-
-
- {{ 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" "" }} -
-
-
- - -
- -
-
-

灵活的请求响应

-

- Actix中的Handler函数可以返回实现该Respondert rait的各种对象。这使得从API返回一致的响应变得轻而易举。 -

- {{ 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" "" }} -
- - - -
- -
    -
  • 灵活的请求响应
  • -
  • 强大的Extractors
  • -
  • 轻松处理表单
  • -
  • 请求路由
  • -
-
- -
-
- - {{ partial "footer" . }} diff --git a/layouts/partials/header.html b/layouts/partials/header.html index f80692e..54467fe 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -28,55 +28,29 @@ diff --git a/static/css/actix.css b/static/css/actix.css index 3ebb6de..277b5c4 100644 --- a/static/css/actix.css +++ b/static/css/actix.css @@ -171,30 +171,45 @@ img { padding-top: 0; list-style: none; } -.navbar-nav #language img { - width: 25px; - height:22px; + +.navbar-nav .language-selector { + position: relative; } -.navbar-nav #language ul { - display: none; + +.navbar-nav .language-selector ul.subitem { + display: none; + position: absolute; + right: 0; } -.navbar-nav #language:hover .subitem { - margin: -1rem 0 0 -6.6rem; - display: block; - background-color: #dcfaf7; + +.navbar-nav .language-selector:hover .subitem { + margin: 0; + padding: 0; + display: block; + background-color: #dcfaf7; } -.navbar-nav #language ul li { - display: block; + +.navbar-nav .language-selector ul li { + padding: 0; + margin: 0; + height: auto; + line-height: 1; } + +.navbar-nav .language-selector ul li a { + display: block; + padding: 1em; +} + .doctoggle { - margin: -1rem 0 2rem 0; - display: none; - } + margin: -1rem 0 2rem 0; + display: none; +} - .leftnav { - margin: 0 -1rem; - padding: 0 1rem; - } +.leftnav { + margin: 0 -1rem; + padding: 0 1rem; +} .leftnav li { margin: 1rem 0rem; @@ -592,12 +607,13 @@ h5:hover a { .actix-footer-social a .fa-github { margin-right: 1rem; } - .navbar-nav #language:hover .subitem { + .navbar-nav .language-selector:hover .subitem { margin: 0 -2rem 0 -1rem; display: block; background-color: #e8f9fc; } } + @media (min-width: 480px) and (max-width: 576px) { header .nav { width: 100%; @@ -666,4 +682,4 @@ h5:hover a { #act-cn-tabs #content { width: 77%; } -} \ No newline at end of file +} diff --git a/static/js/actix.js b/static/js/actix.js index 0aa4782..ef2cb66 100644 --- a/static/js/actix.js +++ b/static/js/actix.js @@ -1,37 +1,3 @@ -window.onload = function(){ - if (window.location.href.search("cn") != -1) { - if (document.getElementById("act-home")){ - document.getElementById("act-home").style.display = "none" - } - }else{ - if (document.getElementById("act-home-cn")){ - document.getElementById("act-home-cn").style.display = "none" - } - } - if (window.location.href.search("cn") != -1) { - document.getElementById("ul-en").style.display = "none" - }else{ - document.getElementById("ul-zh").style.display = "none" - } -} - -function setTab(name,cursel){ - let tlinks = document.getElementById("act-cn-tabs").getElementsByTagName('li') - for(var i=1; i<=tlinks.length; i++){ - var menu = document.getElementById(name+i); - var menudiv = document.getElementById("con_"+name+"_"+i); - if(i==cursel){ - menu.className="off"; - menudiv.style.display="block"; - } - else{ - menu.className=""; - menudiv.style.display="none"; - } - } -} - - (function() { function activateFeature(sel) { $('div.actix-feature').hide(); @@ -70,5 +36,3 @@ function setTab(name,cursel){ initFeatureSelector(); }); })(); - - From 60163cac7f84c9a84484014576f824ce756fbdc1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 24 Jun 2018 23:04:52 +0200 Subject: [PATCH 20/27] Killed ugly earth --- static/img/i18n.jpeg | Bin 54977 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 static/img/i18n.jpeg diff --git a/static/img/i18n.jpeg b/static/img/i18n.jpeg deleted file mode 100644 index 43b9a49b32224324373d4339e708ccad417208e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54977 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 Date: Sun, 24 Jun 2018 23:08:27 +0200 Subject: [PATCH 21/27] Fixed deploy script --- .deploy.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.deploy.sh b/.deploy.sh index 2d37277..562e6cf 100644 --- a/.deploy.sh +++ b/.deploy.sh @@ -1,7 +1,6 @@ #!/bin/bash if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" ]]; then cp CNAME public - mv public/en/* public && rm -R public/en git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -b master -r https://"$GH_TOKEN"@github.com/actix/actix.github.io.git public && echo "Uploaded documentation" From 758da23eafe745c53b5a1e21df9964dfbb88c988 Mon Sep 17 00:00:00 2001 From: yinyanlv Date: Mon, 25 Jun 2018 09:03:53 +0800 Subject: [PATCH 22/27] change partner project description --- content/community/_index.cn.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/community/_index.cn.md b/content/community/_index.cn.md index 13c4402..ad49c75 100644 --- a/content/community/_index.cn.md +++ b/content/community/_index.cn.md @@ -25,5 +25,5 @@ description: 人生中最美好的事物就是分享 - [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+,后端Actix-web + redis + mysql - - [yinyanlv/partner-client](https://github.com/yinyanlv/partner-client) : partner的web客户端 \ No newline at end of file +- [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项目的前端仓库 \ No newline at end of file From 50ff9244c9cba6ccda9655c0143fe1679f417b55 Mon Sep 17 00:00:00 2001 From: krircc Date: Mon, 25 Jun 2018 09:41:51 +0800 Subject: [PATCH 23/27] add blog nav in cn site --- i18n/cn.toml | 2 ++ layouts/partials/header.html | 5 ++++- static/js/actix.js | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/i18n/cn.toml b/i18n/cn.toml index 86f2acf..f1dafde 100644 --- a/i18n/cn.toml +++ b/i18n/cn.toml @@ -2,6 +2,8 @@ other = "首页" [docs] other = "文档" +[blog] +other = "博客" [community] other = "社区" [code] diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 54467fe..60ea690 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -26,7 +26,7 @@