Update website
7
.deploy.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" ]]; then
|
||||||
|
cp CNAME public
|
||||||
|
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 _site &&
|
||||||
|
echo "Uploaded documentation"
|
||||||
|
fi
|
6
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
.DS_Store
|
public
|
||||||
|
tmp
|
||||||
build/
|
|
||||||
_site/
|
|
||||||
|
24
.travis.yml
@ -1,15 +1,17 @@
|
|||||||
language: rust
|
language: rust
|
||||||
sudo: false
|
sudo: false
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
# build website
|
install:
|
||||||
|
- curl -L https://github.com/gohugoio/hugo/releases/download/v0.40.3/hugo_0.40.3_Linux-64bit.tar.gz |
|
||||||
|
tar xzvf -
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- export PATH=$PATH:~/.cargo/bin
|
- ./hugo_0.40.3_Linux-64bit/hugo_0.18.1_linux_amd64
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" ]]; then
|
deploy:
|
||||||
curl -sL https://github.com/cobalt-org/cobalt.rs/releases/download/v0.12.1/cobalt-v0.12.1-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin &&
|
provider: script
|
||||||
cobalt build && cp CNAME ./_site/ &&
|
script: bash .deploy.sh
|
||||||
git clone https://github.com/davisp/ghp-import.git &&
|
skip_cleanup: true
|
||||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -b master -r https://"$GH_TOKEN"@github.com/actix/actix.github.io.git _site &&
|
on:
|
||||||
echo "Uploaded documentation"
|
branch: master
|
||||||
fi
|
condition: $TRAVIS_PULL_REQUEST = "false"
|
||||||
|
201
LICENSE
@ -1,201 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2017-NOW Actix Organization and Contributors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2018 Carl Lerche
|
||||||
|
|
||||||
|
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.
|
35
README.md
@ -1,14 +1,27 @@
|
|||||||
# actix-theme
|
# Actix Website
|
||||||
The Actix website template made with [Cobalt](https://cobalt-org.github.io/)
|
|
||||||
|
|
||||||
|
The work in progress website for the actix project based on tokio's website.
|
||||||
|
|
||||||
# Contribute.
|
## Getting Started
|
||||||
So you want make actix-website more beautiful.
|
|
||||||
```bash
|
Building the website depends on [Hugo](http://gohugo.io). So, first make sure
|
||||||
$ cobalt -V // Cobalt 0.11.1
|
that you have it installed. If on OS X and using Homebrew, run the following:
|
||||||
$ git clone https://github.com/actix/actix-website.git
|
|
||||||
$ cd actix-website
|
```sh
|
||||||
// modify index.liquid or tutorials/community/more markdown file.
|
brew update && brew install hugo
|
||||||
// also add or modify markdown files in docs/blog DIR.
|
|
||||||
$ cobalt serve // it will get '_site' DIR, so only need put files in this '_site' DIR to [actix.github.io](https://github.com/actix/actix.github.io)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then, get the website running locally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/mitsuhiko/actix-website
|
||||||
|
cd website
|
||||||
|
hugo server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit [http://localhost:1313](http://localhost:1313).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Pretty murky. Right now a massive clone of the tokio website. Will get this
|
||||||
|
figured out as we go along.
|
||||||
|
39
_cobalt.yml
@ -1,39 +0,0 @@
|
|||||||
# Build options
|
|
||||||
source: "."
|
|
||||||
template_extensions:
|
|
||||||
- md
|
|
||||||
- liquid
|
|
||||||
include_drafts: false
|
|
||||||
ignore:
|
|
||||||
- .git/*
|
|
||||||
- build/*
|
|
||||||
- README.md
|
|
||||||
destination: _site
|
|
||||||
|
|
||||||
# Site options
|
|
||||||
site:
|
|
||||||
title: ~
|
|
||||||
description: ~
|
|
||||||
base_url: ~
|
|
||||||
data: ~
|
|
||||||
|
|
||||||
# Page/Post options
|
|
||||||
default:
|
|
||||||
excerpt_separator: "\n\n"
|
|
||||||
is_draft: false
|
|
||||||
syntax_highlight:
|
|
||||||
theme: "base16-ocean.dark"
|
|
||||||
assets:
|
|
||||||
sass:
|
|
||||||
style: Nested
|
|
||||||
pages:
|
|
||||||
default: {}
|
|
||||||
posts:
|
|
||||||
title: ~
|
|
||||||
description: ~
|
|
||||||
dir: posts
|
|
||||||
drafts_dir: _drafts
|
|
||||||
order: Desc
|
|
||||||
rss: ~
|
|
||||||
jsonfeed: ~
|
|
||||||
default: {}
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-110322332-1"></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
|
|
||||||
gtag('config', 'UA-110322332-1');
|
|
||||||
</script>
|
|
@ -1,7 +0,0 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1", maximum-scale=1,user-scalable=no,shrink-to-fit=no">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<title>{{ page.title }}</title>
|
|
||||||
<link rel="shortcut icon" href="/public/imgs/favicon.ico">
|
|
||||||
<link rel="stylesheet" href="/public/css/styles.css">
|
|
||||||
<script src="/public/js/nav.js" async="async"></script>
|
|
@ -1,14 +0,0 @@
|
|||||||
<div id="mnav">
|
|
||||||
<div id="lnav">
|
|
||||||
<div id="line"></div>
|
|
||||||
<h1><a href="/"><img src="/public/imgs/ACTIX.png"/></a><a id="home" href="/">Actix</a></h1>
|
|
||||||
<label ><a href="#" id="menu">menu</a></label>
|
|
||||||
</div>
|
|
||||||
<div id="rnav">
|
|
||||||
<li> <a href="https://github.com/actix/actix-web" title="GitHub">github</a></li>
|
|
||||||
<li> <a href="https://actix.rs/book/actix-web/" title="Guide">guide</a></li>
|
|
||||||
<li> <a href="https://actix.rs/actix-web/actix_web/" title="api">api</a></li>
|
|
||||||
<li> <a href="https://gitter.im/actix/actix" title="chat">gitter chat</a></li>
|
|
||||||
<li> <a href="https://crates.io/crates/actix-web" title="crates">crates.io</a></li>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% include "head.liquid" %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
{% include "header.liquid" %}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section class="docs">
|
|
||||||
<div class="inner">
|
|
||||||
<aside>
|
|
||||||
<p>2018</p>
|
|
||||||
<ul>
|
|
||||||
<li><a {%if page.permalink == "Blog-example"%}class="active"{%endif%} href="/blog/2018/Blog-example.html">Blog Example</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
<hr id="hr">
|
|
||||||
<main>
|
|
||||||
{{ page.content }}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{% include "footer.liquid" %}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% include "head.liquid" %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
{% include "header.liquid" %}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
{{ page.content }}
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{% include "footer.liquid" %}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,42 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% include "head.liquid" %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
{% include "header.liquid" %}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section class="blog">
|
|
||||||
<div class="inner">
|
|
||||||
<aside>
|
|
||||||
<p>Basics</p>
|
|
||||||
<ul>
|
|
||||||
<li><a {%if page.permalink == "Basics-example"%}class="active"{%endif%} href="/docs/Basics/Basics-example.html">Basics Example</a></li>
|
|
||||||
</ul>
|
|
||||||
<p>Reference</p>
|
|
||||||
<ul>
|
|
||||||
<li><a {%if page.permalink == "Reference-example"%}class="active"{%endif%} href="/docs/Reference/Reference-example.html">Reference Example</a></li>
|
|
||||||
</ul>
|
|
||||||
<p>Advance</p>
|
|
||||||
<ul>
|
|
||||||
<li><a {%if page.permalink == "Advance-example"%}class="active"{%endif%} href="/docs/Advance/Advance-example.html">Advance Example</a></li>
|
|
||||||
</ul>
|
|
||||||
<p>API</p>
|
|
||||||
<ul>
|
|
||||||
<li><a {%if page.permalink == "API-example"%}class="active"{%endif%} href="/docs/API/API-example.html">API Example</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
<hr id="hr">
|
|
||||||
<main>
|
|
||||||
{{ page.content }}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{% include "footer.liquid" %}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% include "head.liquid" %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
{% include "header.liquid" %}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
{{ content }}
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{% include "footer.liquid" %}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
BIN
artwork/logo.afdesign
Normal file
@ -1,8 +0,0 @@
|
|||||||
title: Blog::Blog Example
|
|
||||||
layout: blog.liquid
|
|
||||||
permalink: /blog/2018/Blog-example.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Basics Example
|
|
||||||
|
|
||||||
The words is comming.
|
|
@ -1,13 +0,0 @@
|
|||||||
title: Blog
|
|
||||||
layout: blog.liquid
|
|
||||||
permalink: /blog/index.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Welcome to the Actix-web Blog!
|
|
||||||
|
|
||||||
If you feel like contributing further, you can fork that repo,
|
|
||||||
branch from `source`, and submit a pull request.
|
|
||||||
|
|
||||||
Contributions are always welcome!
|
|
15
community.md
@ -1,15 +0,0 @@
|
|||||||
title: Community
|
|
||||||
layout: default.liquid
|
|
||||||
permalink: /community.html
|
|
||||||
---
|
|
||||||
<section >
|
|
||||||
<div >
|
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
The words is comming.
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
11
config.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
baseurl = "https://actix.rs"
|
||||||
|
title = "actix"
|
||||||
|
languageCode = "en-us"
|
||||||
|
canonifyURLs = true
|
||||||
|
googleAnalytics = "UA-110322332-1"
|
||||||
|
pygmentsUseClasses = true
|
||||||
|
pygmentsCodeFences = true
|
||||||
|
|
||||||
|
[params]
|
||||||
|
actixVersion = "0.5"
|
||||||
|
actixWebVersion = "0.6"
|
3
content/_index.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Welcome
|
||||||
|
---
|
19
content/code/_index.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: Code
|
||||||
|
description: Browse and download the sources
|
||||||
|
---
|
||||||
|
|
||||||
|
# Browse the Code
|
||||||
|
|
||||||
|
All of actix is open source and can be found on our github organization: [actix
|
||||||
|
on github](https://github.com/actix)
|
||||||
|
|
||||||
|
Here are the most important projects and the link to their github repositories
|
||||||
|
and related resources:
|
||||||
|
|
||||||
|
* [actix](https://github.com/actix/actix) ([issues](https://github.com/actix/actix/issues), [ci](https://travis-ci.org/actix/actix), [crate](https://crates.io/crates/actix), [api docs](https://docs.rs/actix))
|
||||||
|
* [actix-web](https://github.com/actix/actix-web) ([issues](https://github.com/actix/actix-web/issues), [ci](https://travis-ci.org/actix/actix-web), [crate](https://crates.io/crates/actix-web), [api docs](https://docs.rs/actix-web))
|
||||||
|
* [example code](https://github.com/actix/examples)
|
||||||
|
* [this website](https://github.com/actix/actix-website)
|
||||||
|
|
||||||
|
Actix is dual licensed under the MIT and Apache 2 licenses. [Read license text](license/).
|
201
content/code/license.md
Normal file
@ -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.
|
18
content/community/_index.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: Community
|
||||||
|
description: The best things in life are to be shared
|
||||||
|
---
|
||||||
|
|
||||||
|
# Join us
|
||||||
|
|
||||||
|
Want to talk to others about questions? The [actix gitter
|
||||||
|
channel](https://gitter.im/actix/actix) is your best starting point.
|
||||||
|
|
||||||
|
If you think you found a bug it's best to go to the
|
||||||
|
[github](https://github.com/actix) directly. There are two repositories
|
||||||
|
that you might want to report against. [actix](https://github.com/actix/actix)
|
||||||
|
for issues with the actor framework or [actix-web](https://github.com/actix/actix-web)
|
||||||
|
for the high level web framework.
|
||||||
|
|
||||||
|
We're a welcoming community so don't be afraid to engage. Interactions
|
||||||
|
are [goverend by our code of conduct](coc/).
|
49
content/community/coc.md
Normal file
@ -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/
|
24
content/docs/_index.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Documentation
|
||||||
|
description: Guiding you through building apps with actix
|
||||||
|
menu:
|
||||||
|
docs_intro:
|
||||||
|
name: Welcome
|
||||||
|
weight: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome to Actix
|
||||||
|
|
||||||
|
Actix is your door to developing web services with Rust and this documentation
|
||||||
|
is going to guide you.
|
||||||
|
|
||||||
|
This documentation currently covers mostly the `actix-web` part which is the
|
||||||
|
high level web framework build on top of the `actix` actor framework and the
|
||||||
|
[Tokio](https://tokio.rs/) async IO system. This is the part that is from an
|
||||||
|
API stability point of view the most stable.
|
||||||
|
|
||||||
|
If you haven't used actix yet it's best to start with the [getting started
|
||||||
|
guide](getting-started/). If you already know your ways around and you need
|
||||||
|
specific information you might want to read the [actix-web API
|
||||||
|
docs](https://docs.rs/actix-web) (or the lower level [actix API
|
||||||
|
docs](https://docs.rs/actix)).
|
137
content/docs/application.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
title: Application
|
||||||
|
menu: docs_basics
|
||||||
|
weight: 140
|
||||||
|
---
|
||||||
|
|
||||||
|
# Writing an Application
|
||||||
|
|
||||||
|
`actix-web` provides various primitives to build web servers and applications with Rust.
|
||||||
|
It provides routing, middlewares, pre-processing of requests, post-processing of responses,
|
||||||
|
websocket protocol handling, multipart streams, etc.
|
||||||
|
|
||||||
|
All actix web servers are built around the `App` instance. It is used for
|
||||||
|
registering routes for resources and middlewares. It also stores application
|
||||||
|
state shared across all handlers within same application.
|
||||||
|
|
||||||
|
Applications act as a namespace for all routes, i.e all routes for a specific application
|
||||||
|
have the same url path prefix. The application prefix always contains a leading "/" slash.
|
||||||
|
If a supplied prefix does not contain leading slash, it is automatically inserted.
|
||||||
|
The prefix should consist of value path segments.
|
||||||
|
|
||||||
|
> For an application with prefix `/app`,
|
||||||
|
> any request with the paths `/app`, `/app/`, or `/app/test` would match;
|
||||||
|
> however, the path `/application` would not match.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = App::new()
|
||||||
|
.prefix("/app")
|
||||||
|
.resource("/index.html", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish()
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, an application with the `/app` prefix and a `index.html` resource
|
||||||
|
are created. This resource is available through the `/app/index.html` url.
|
||||||
|
|
||||||
|
> For more information, check the
|
||||||
|
> [URL Dispatch](./sec-6-url-dispatch.html#using-a-application-prefix-to-compose-applications) section.
|
||||||
|
|
||||||
|
Multiple applications can be served with one server:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| vec![
|
||||||
|
App::new()
|
||||||
|
.prefix("/app1")
|
||||||
|
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
||||||
|
App::new()
|
||||||
|
.prefix("/app2")
|
||||||
|
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All `/app1` requests route to the first application, `/app2` to the second, and all other to the third.
|
||||||
|
**Applications get matched based on registration order**. If an application with a more generic
|
||||||
|
prefix is registered before a less generic one, it would effectively block the less generic
|
||||||
|
application matching. For example, if an `App` with the prefix `"/"` was registered
|
||||||
|
as the first application, it would match all incoming requests.
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
Application state is shared with all routes and resources within the same application.
|
||||||
|
When using an http actor,state can be accessed with the `HttpRequest::state()` as read-only,
|
||||||
|
but interior mutability with `RefCell` can be used to achieve state mutability.
|
||||||
|
State is also available for route matching predicates and middlewares.
|
||||||
|
|
||||||
|
Let's write a simple application that uses shared state. We are going to store request count
|
||||||
|
in the state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::cell::Cell;
|
||||||
|
use actix_web::{App, HttpRequest, http};
|
||||||
|
|
||||||
|
// This struct represents state
|
||||||
|
struct AppState {
|
||||||
|
counter: Cell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest<AppState>) -> String {
|
||||||
|
let count = req.state().counter.get() + 1; // <- get count
|
||||||
|
req.state().counter.set(count); // <- store new count in state
|
||||||
|
|
||||||
|
format!("Request number: {}", count) // <- response with count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::with_state(AppState{counter: Cell::new(0)})
|
||||||
|
.resource("/", |r| r.method(http::Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: http server accepts an application factory rather than an application
|
||||||
|
> instance. Http server constructs an application instance for each thread, thus application state
|
||||||
|
> must be constructed multiple times. If you want to share state between different threads, a
|
||||||
|
> shared object should be used, e.g. `Arc`. Application state does not need to be `Send` and `Sync`,
|
||||||
|
> but the application factory must be `Send` + `Sync`.
|
||||||
|
|
||||||
|
## Combining applications with different state
|
||||||
|
|
||||||
|
Combining multiple applications with different state is possible as well.
|
||||||
|
|
||||||
|
[server::new](https://docs.rs/actix-web/*/actix_web/server/fn.new.html) requires the handler to have a single type.
|
||||||
|
|
||||||
|
This limitation can easily be overcome with the [App::boxed](https://docs.rs/actix-web/*/actix_web/struct.App.html#method.boxed) method, which converts an App into a boxed trait object.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server, App, HttpResponse};
|
||||||
|
|
||||||
|
struct State1;
|
||||||
|
struct State2;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| {
|
||||||
|
vec![
|
||||||
|
App::with_state(State1)
|
||||||
|
.prefix("/app1")
|
||||||
|
.resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||||
|
.boxed(),
|
||||||
|
App::with_state(State2)
|
||||||
|
.prefix("/app2")
|
||||||
|
.resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||||
|
.boxed()
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
```
|
76
content/docs/autoreload.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
title: Autoreloading
|
||||||
|
menu: docs_patterns
|
||||||
|
weight: 1000
|
||||||
|
---
|
||||||
|
|
||||||
|
# Auto-Reloading Development Server
|
||||||
|
|
||||||
|
During development it can be very handy to have cargo automatically recompile
|
||||||
|
the code on change. This can be accomplished by using
|
||||||
|
[cargo-watch](https://github.com/passcod/cargo-watch). Because an actix app
|
||||||
|
will typically bind to a port for listening for incoming HTTP requests it makes
|
||||||
|
sense to combine this with the [listenfd](https://crates.io/crates/listenfd)
|
||||||
|
crate and the [systemfd](https://github.com/mitsuhiko/systemfd) utility to
|
||||||
|
ensure the socket is kept open while the app is compiling and reloading.
|
||||||
|
|
||||||
|
`systemfd` will open a socket and pass it to `cargo-watch` which will watch for
|
||||||
|
changes and then invoke the compiler and run your actix app. The actix app
|
||||||
|
will then use `listenfd` to pick up the socket that `systemfd` opened.
|
||||||
|
|
||||||
|
## Binaries Necessary
|
||||||
|
|
||||||
|
For an automatic reloading experience you need to install `cargo-watch` and
|
||||||
|
`systemfd`. Both are written in rust and can be installed with `cargo install`:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo install systemfd cargo-watch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Changes
|
||||||
|
|
||||||
|
Additionally you need to slightly modify your actix app so that it can pick up
|
||||||
|
an external socket opened by `systemfd`. Add the listenfd dependency to your
|
||||||
|
app:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[dependencices]
|
||||||
|
listenfd = "0.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then modify your server code to only invoke `bind` as a fallback:
|
||||||
|
|
||||||
|
```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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Server
|
||||||
|
|
||||||
|
To now run the development server invoke this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
systemfd -s http::3000 -- cargo watch -x run
|
||||||
|
```
|
132
content/docs/databases.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
title: Databases
|
||||||
|
menu: docs_patterns
|
||||||
|
weight: 1010
|
||||||
|
---
|
||||||
|
|
||||||
|
# Diesel
|
||||||
|
|
||||||
|
At the moment, Diesel 1.0 does not support asynchronous operations,
|
||||||
|
but it possible to use the `actix` synchronous actor system as a database interface api.
|
||||||
|
|
||||||
|
Technically, sync actors are worker style actors. Multiple sync actors
|
||||||
|
can be run in parallel and process messages from same queue. Sync actors work in mpsc mode.
|
||||||
|
|
||||||
|
Let's create a simple database api that can insert a new user row into a SQLite table.
|
||||||
|
We must define a sync actor and a connection that this actor will use. The same approach
|
||||||
|
can be used for other databases.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
struct DbExecutor(SqliteConnection);
|
||||||
|
|
||||||
|
impl Actor for DbExecutor {
|
||||||
|
type Context = SyncContext<Self>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the definition of our actor. Now, we must define the *create user* message and response.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct CreateUser {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for CreateUser {
|
||||||
|
type Result = Result<User, Error>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a
|
||||||
|
`User` model instance. Next, we must define the handler implementation for this message.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl Handler<CreateUser> for DbExecutor {
|
||||||
|
type Result = Result<User, Error>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
||||||
|
{
|
||||||
|
use self::schema::users::dsl::*;
|
||||||
|
|
||||||
|
// Create insertion model
|
||||||
|
let uuid = format!("{}", uuid::Uuid::new_v4());
|
||||||
|
let new_user = models::NewUser {
|
||||||
|
id: &uuid,
|
||||||
|
name: &msg.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
// normal diesel operations
|
||||||
|
diesel::insert_into(users)
|
||||||
|
.values(&new_user)
|
||||||
|
.execute(&self.0)
|
||||||
|
.expect("Error inserting person");
|
||||||
|
|
||||||
|
let mut items = users
|
||||||
|
.filter(id.eq(&uuid))
|
||||||
|
.load::<models::User>(&self.0)
|
||||||
|
.expect("Error loading person");
|
||||||
|
|
||||||
|
Ok(items.pop().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware.
|
||||||
|
All we need is to start *DbExecutor* actors and store the address in a state where http handler
|
||||||
|
can access it.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// This is state where we will store *DbExecutor* address.
|
||||||
|
struct State {
|
||||||
|
db: Addr<Syn, DbExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("diesel-example");
|
||||||
|
|
||||||
|
// Start 3 parallel db executors
|
||||||
|
let addr = SyncArbiter::start(3, || {
|
||||||
|
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start http server
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::with_state(State{db: addr.clone()})
|
||||||
|
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start().unwrap();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We will use the address in a request handler. The handle returns a future object;
|
||||||
|
thus, we receive the message response asynchronously.
|
||||||
|
`Route::a()` must be used for async handler registration.
|
||||||
|
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Async handler
|
||||||
|
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
let name = &req.match_info()["name"];
|
||||||
|
|
||||||
|
// Send message to `DbExecutor` actor
|
||||||
|
req.state().db.send(CreateUser{name: name.to_owned()})
|
||||||
|
.from_err()
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
||||||
|
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> A full example is available in the
|
||||||
|
> [examples directory](https://github.com/actix/examples/tree/master/diesel/).
|
||||||
|
|
||||||
|
> More information on sync actors can be found in the
|
||||||
|
> [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).
|
238
content/docs/errors.md
Normal file
@ -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<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
||||||
|
```
|
||||||
|
|
||||||
|
`Error` in the code above is actix's error definition, and any errors that
|
||||||
|
implement `ResponseError` can be converted to one automatically.
|
||||||
|
|
||||||
|
Actix-web provides `ResponseError` implementations for some common non-actix
|
||||||
|
errors. For example, if a handler responds with an `io::Error`, that error is
|
||||||
|
converted into an `HttpInternalServerError`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
|
||||||
|
Ok(fs::NamedFile::open("static/index.html")?)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [the actix-web API documentation](responseerrorimpls) for a full list of
|
||||||
|
foreign implementations for `ResponseError`.
|
||||||
|
|
||||||
|
## An example of a custom error response
|
||||||
|
|
||||||
|
Here's an example implementation for `ResponseError`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
#[fail(display="my error")]
|
||||||
|
struct MyError {
|
||||||
|
name: &'static str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default implementation for `error_response()` method
|
||||||
|
impl error::ResponseError for MyError {}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
||||||
|
Err(MyError{name: "test"})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ResponseError` has a default implementation for `error_response()` that will
|
||||||
|
render a *500* (internal server error), and that's what will happen when the
|
||||||
|
`index` handler executes above.
|
||||||
|
|
||||||
|
Override `error_response()` to produce more useful results:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate failure;
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, http, error};
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
enum MyError {
|
||||||
|
#[fail(display="internal error")]
|
||||||
|
InternalError,
|
||||||
|
#[fail(display="bad request")]
|
||||||
|
BadClientData,
|
||||||
|
#[fail(display="timeout")]
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::ResponseError for MyError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match *self {
|
||||||
|
MyError::InternalError => HttpResponse::new(
|
||||||
|
http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
MyError::BadClientData => HttpResponse::new(
|
||||||
|
http::StatusCode::BAD_REQUEST),
|
||||||
|
MyError::Timeout => HttpResponse::new(
|
||||||
|
http::StatusCode::GATEWAY_TIMEOUT),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
||||||
|
Err(MyError::BadClientData)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Error helpers
|
||||||
|
|
||||||
|
Actix provides a set of error helper functions that are useful for generating
|
||||||
|
specific HTTP error codes from other errors. Here we convert `MyError`, which
|
||||||
|
doesn't implement the `ResponseError` trait, to a *400* (bad request) using
|
||||||
|
`map_err`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MyError {
|
||||||
|
name: &'static str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<&'static str> {
|
||||||
|
let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
|
||||||
|
|
||||||
|
Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [API documentation for actix-web's `error` module][errorhelpers] for a
|
||||||
|
full list of available error helpers.
|
||||||
|
|
||||||
|
# Compatibility with failure
|
||||||
|
|
||||||
|
Actix-web provides automatic compatibility with the [failure] library so that
|
||||||
|
errors deriving `fail` will be converted automatically to an actix error. Keep
|
||||||
|
in 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
|
227
content/docs/extractors.md
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 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 `/users/{userid}/{friend}` path
|
||||||
|
two segments could be deserialized, `userid` and `friend`. This segments
|
||||||
|
could be extracted to a `tuple`, i.e. `Path<(u32, String)>` or structure
|
||||||
|
that implementd `Deserialize` trait from *serde* crate.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, Path, Result, http};
|
||||||
|
|
||||||
|
/// extract path info from "/users/{userid}/{friend}" url
|
||||||
|
/// {userid} - - deserializes to a u32
|
||||||
|
/// {friend} - deserializes to a String
|
||||||
|
fn index(info: Path<(u32, String)>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}! {}", info.1, info.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/users/{userid}/{friend}", // <- define path parameters
|
||||||
|
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember! handler function that uses extractors has to be registered with
|
||||||
|
[*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 `Deserialize` trait from *serde*. Here is equivalent example that uses *serde*
|
||||||
|
instead of *tuple* type.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Path, Result, http};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
userid: u32,
|
||||||
|
friend: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// extract path info using serde
|
||||||
|
fn index(info: Path<Info>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", info.friend))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/users/{userid}/{friend}", // <- define path parameters
|
||||||
|
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Query
|
||||||
|
|
||||||
|
Same can be done with the request's query.
|
||||||
|
[*Query*](../../actix-web/actix_web/struct.Query.html)
|
||||||
|
type provides extraction functionality. Underneath it uses *serde_urlencoded* crate.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Query, http};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// this handler get called only if the request's query contains `username` field
|
||||||
|
fn index(info: Query<Info>) -> String {
|
||||||
|
format!("Welcome {}!", info.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/index.html",
|
||||||
|
|r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Json
|
||||||
|
|
||||||
|
[*Json*](../../actix-web/actix_web/struct.Json.html) allows to deserialize
|
||||||
|
request body to a struct. To extract typed information from request's body,
|
||||||
|
the type `T` must implement the `Deserialize` trait from *serde*.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Json, Result, http};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// deserialize `Info` from request's body
|
||||||
|
fn index(info: Json<Info>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", info.username))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/index.html",
|
||||||
|
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Some extractors provide a way to configure extraction process. Json extractor
|
||||||
|
[*JsonConfig*](../../actix-web/actix_web/dev/struct.JsonConfig.html) type for configuration.
|
||||||
|
When you register handler `Route::with()` returns configuration instance. In case of
|
||||||
|
*Json* extractor it returns *JsonConfig*. You can configure max size of the json
|
||||||
|
payload and custom error handler function.
|
||||||
|
|
||||||
|
Following example limits size of the payload to 4kb and uses custom error hander.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Json, HttpResponse, Result, http, error};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// deserialize `Info` from request's body, max payload size is 4kb
|
||||||
|
fn index(info: Json<Info>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", info.username))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/index.html", |r| {
|
||||||
|
r.method(http::Method::POST)
|
||||||
|
.with(index)
|
||||||
|
.limit(4096) // <- change json extractor configuration
|
||||||
|
.error_handler(|err, req| { // <- create custom error response
|
||||||
|
error::InternalError::from_response(
|
||||||
|
err, HttpResponse::Conflict().finish()).into()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Form
|
||||||
|
|
||||||
|
At the moment only url-encoded forms are supported. Url encoded body
|
||||||
|
could be extracted to a specific type. This type must implement
|
||||||
|
the `Deserialize` trait from *serde* crate.
|
||||||
|
|
||||||
|
[*FormConfig*](../../actix-web/actix_web/dev/struct.FormConfig.html) allows
|
||||||
|
to configure 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 get called only if content type is *x-www-form-urlencoded*
|
||||||
|
/// and content of the request could be deserialized to a `FormData` struct
|
||||||
|
fn index(form: Form<FormData>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", form.username))
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Multiple extractors
|
||||||
|
|
||||||
|
Actix provides extractor implementation for tuples (up to 10 elements)
|
||||||
|
which elements provide `FromRequest` impl.
|
||||||
|
|
||||||
|
For example we can use path extractor and 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(data: (Path<(u32, String)>, Query<Info>)) -> String {
|
||||||
|
let (path, query) = data;
|
||||||
|
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 if you need access to the request.
|
||||||
|
* *String* - You can convert 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 request's payload to a *Bytes*.
|
||||||
|
[*Example*](../../actix-web/actix_web/trait.FromRequest.html#example)
|
||||||
|
is available in doc strings.
|
67
content/docs/getting-started.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
menu: docs_basics
|
||||||
|
weight: 130
|
||||||
|
---
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
Let’s write our first actix web application!
|
||||||
|
|
||||||
|
## Hello, world!
|
||||||
|
|
||||||
|
Start by creating a new binary-based Cargo project and changing into the new directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo new hello-world --bin
|
||||||
|
cd hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, add `actix-web` as dependencies of your project by ensuring your `Cargo.toml`
|
||||||
|
contains the following:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "{{< actix-version "actix-web" >}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to implement a web server, we first need to create a request handler.
|
||||||
|
|
||||||
|
A request handler is a function that accepts an `HttpRequest` instance as its only parameter
|
||||||
|
and returns a type that can be converted into `HttpResponse`:
|
||||||
|
|
||||||
|
Filename: `src/main.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate actix_web;
|
||||||
|
use actix_web::{HttpRequest, Responder, App, server};
|
||||||
|
|
||||||
|
fn index(req: _HttpRequest) -> Responder {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, create an `Application` instance and register the request handler with
|
||||||
|
the application's `resource` on a particular *HTTP method* and *path* and
|
||||||
|
after that, the application instance can be used with `HttpServer` to listen
|
||||||
|
for incoming connections. The server accepts a function that should return an
|
||||||
|
`HttpHandler` instance. For simplicity `server::new` could be used, this
|
||||||
|
function is shortcut for `HttpServer::new`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
server::new(|| {
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(index))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Now, compile and run the program with `cargo run`.
|
||||||
|
Head over to ``http://localhost:8088/`` to see the results.
|
||||||
|
|
||||||
|
If you want you can have an automatic reloading server during development
|
||||||
|
that recompiles on demand. To see how this can be accomplished have a look
|
||||||
|
at the [autoreload pattern](../autoreload/).
|
292
content/docs/handlers.md
Normal file
@ -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<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Handler* trait is generic over *S*, which defines the application state's type.
|
||||||
|
Application state is accessible from the handler with the `HttpRequest::state()` method;
|
||||||
|
however, state is accessible as a read-only reference. If you need mutable access to state,
|
||||||
|
it must be implemented.
|
||||||
|
|
||||||
|
> **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes
|
||||||
|
> mutable reference to *self*. **Beware**, actix creates multiple copies
|
||||||
|
> of the application state and the handlers, unique for each thread. If you run your
|
||||||
|
> application in several threads, actix will create the same amount as number of threads
|
||||||
|
> of application state objects and handler objects.
|
||||||
|
|
||||||
|
Here is an example of a handler that stores the number of processed requests:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};
|
||||||
|
|
||||||
|
struct MyHandler(usize);
|
||||||
|
|
||||||
|
impl<S> Handler<S> for MyHandler {
|
||||||
|
type Result = HttpResponse;
|
||||||
|
|
||||||
|
/// Handle request
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
self.0 += 1;
|
||||||
|
HttpResponse::Ok().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Although this handler will work, `self.0` will be different depending on the number of threads and
|
||||||
|
number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
struct MyHandler(Arc<AtomicUsize>);
|
||||||
|
|
||||||
|
impl<S> Handler<S> for MyHandler {
|
||||||
|
type Result = HttpResponse;
|
||||||
|
|
||||||
|
/// Handle request
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
self.0.fetch_add(1, Ordering::Relaxed);
|
||||||
|
HttpResponse::Ok().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("example");
|
||||||
|
|
||||||
|
let inc = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
move || {
|
||||||
|
let cloned = inc.clone();
|
||||||
|
App::new()
|
||||||
|
.resource("/", move |r| r.h(MyHandler(cloned)))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8088");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Be careful with synchronization primitives like `Mutex` or `RwLock`. The `actix-web` framework
|
||||||
|
> handles requests asynchronously. By blocking thread execution, all concurrent
|
||||||
|
> request handling processes would block. If you need to share or update some state
|
||||||
|
> from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system.
|
||||||
|
|
||||||
|
## Response with custom type
|
||||||
|
|
||||||
|
To return a custom type directly from a handler function, the type needs to implement the `Responder` trait.
|
||||||
|
|
||||||
|
Let's create a response for a custom type that serializes to an `application/json` response:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct MyObj {
|
||||||
|
name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Responder
|
||||||
|
impl Responder for MyObj {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||||
|
let body = serde_json::to_string(&self)?;
|
||||||
|
|
||||||
|
// Create response and set content type
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
MyObj { name: "user" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("example");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
.resource("/", |r| r.method(http::Method::GET).f(index)))
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8088");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Async handlers
|
||||||
|
|
||||||
|
There are two different types of async handlers. Response objects can be generated asynchronously
|
||||||
|
or more precisely, any type that implements the [*Responder*](../../actix-web/actix_web/trait.Responder.html) trait.
|
||||||
|
|
||||||
|
In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::stream::once;
|
||||||
|
use futures::future::{Future, result};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
|
||||||
|
result(Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello!"))))
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index2(req: HttpRequest) -> Box<Future<Item=&'static str, Error=Error>> {
|
||||||
|
result(Ok("Welcome!"))
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/async", |r| r.route().a(index))
|
||||||
|
.resource("/", |r| r.route().a(index2))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or the response body can be generated asynchronously. In this case, body
|
||||||
|
must implement the stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::stream::once;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
let body = once(Ok(Bytes::from_static(b"test")));
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(Body::Streaming(Box::new(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/async", |r| r.f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Both methods can be combined. (i.e Async response with streaming body)
|
||||||
|
|
||||||
|
It is possible to return a `Result` where the `Result::Item` type can be `Future`.
|
||||||
|
In this example, the `index` handler can return an error immediately or return a
|
||||||
|
future that resolves to a `HttpResponse`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::stream::once;
|
||||||
|
use futures::future::{Future, result};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<Box<Future<Item=HttpResponse, Error=Error>>, Error> {
|
||||||
|
if is_error() {
|
||||||
|
Err(error::ErrorBadRequest("bad request"))
|
||||||
|
} else {
|
||||||
|
Ok(Box::new(
|
||||||
|
result(Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello!"))))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Different return types (Either)
|
||||||
|
|
||||||
|
Sometimes, you need to return different types of responses. For example,
|
||||||
|
you can error check and return errors, return async responses, or any result that requires two different types.
|
||||||
|
|
||||||
|
For this case, the [*Either*](../../actix-web/actix_web/enum.Either.html) type can be used.
|
||||||
|
`Either` allows combining two different responder types into a single type.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use futures::future::{Future, result};
|
||||||
|
use actix_web::{Either, Error, HttpResponse};
|
||||||
|
|
||||||
|
type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
if is_a_variant() { // <- choose variant A
|
||||||
|
Either::A(
|
||||||
|
HttpResponse::BadRequest().body("Bad data"))
|
||||||
|
} else {
|
||||||
|
Either::B( // <- variant B
|
||||||
|
result(Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello!")))).responder())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tokio core handle
|
||||||
|
|
||||||
|
Any `actix-web` handler runs within a properly configured
|
||||||
|
[actix system](https://actix.github.io/actix/actix/struct.System.html)
|
||||||
|
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
|
||||||
|
You can always get access to the tokio handle via the
|
||||||
|
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
|
||||||
|
method.
|
50
content/docs/http2.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: HTTP/2.0
|
||||||
|
menu: docs_proto
|
||||||
|
weight: 250
|
||||||
|
---
|
||||||
|
|
||||||
|
`actix-web` automatically upgrades connections to *HTTP/2.0* if possible.
|
||||||
|
|
||||||
|
# Negotiation
|
||||||
|
|
||||||
|
*HTTP/2.0* protocol over tls without prior knowledge requires
|
||||||
|
[tls alpn](https://tools.ietf.org/html/rfc7301).
|
||||||
|
|
||||||
|
> Currently, only `rust-openssl` has support.
|
||||||
|
|
||||||
|
`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the
|
||||||
|
[serve_tls](../../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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Upgrades to *HTTP/2.0* schema described in
|
||||||
|
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
|
||||||
|
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
|
||||||
|
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
||||||
|
|
||||||
|
> Check out [examples/tls](https://github.com/actix/examples/tree/master/tls)
|
||||||
|
> for a concrete example.
|
52
content/docs/installation.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
menu: docs_intro
|
||||||
|
weight: 110
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installing Rust
|
||||||
|
|
||||||
|
Since `actix-web` is a Rust framework you will need Rust to get started with it.
|
||||||
|
If you don't have it yet we recommend you use `rustup` to manage your Rust
|
||||||
|
installation. The [official rust
|
||||||
|
guide](https://doc.rust-lang.org/book/second-edition/ch01-01-installation.html)
|
||||||
|
has a wonderful section on getting started.
|
||||||
|
|
||||||
|
We currently require at least Rust 1.24 so make sure you run `rustup update`
|
||||||
|
to have the latest and greatest Rust version available. In particular this
|
||||||
|
guide will assume that you actually run Rust 1.26 or later.
|
||||||
|
|
||||||
|
# Installing `actix-web`
|
||||||
|
|
||||||
|
Thank's to Rust's `cargo` package manger you won't need to explicitly install
|
||||||
|
`actix-web`. Just depend on it and you're ready to go. For the unlikely
|
||||||
|
case that you want to use the development version of actix-web you can
|
||||||
|
depend on the git repository directly.
|
||||||
|
|
||||||
|
Release version:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "{{< actix-version "actix-web" >}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Development version:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[dependencies]
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web }
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diving In
|
||||||
|
|
||||||
|
There are two paths you can take here. You can follow the guide along or if
|
||||||
|
you are very impatient you might want to have a look at our
|
||||||
|
[extensive example repository](https://github.com/actix/examples) and run the
|
||||||
|
included examples. Here for instance is how you run the included `basics`
|
||||||
|
example:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/actix/examples
|
||||||
|
cd examples/basics
|
||||||
|
cargo run
|
||||||
|
```
|
250
content/docs/middleware.md
Normal file
@ -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<S> Middleware<S> for Headers {
|
||||||
|
|
||||||
|
/// Method is called when request is ready. It may return
|
||||||
|
/// future, which should resolve before next middleware get called.
|
||||||
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
req.headers_mut().insert(
|
||||||
|
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
||||||
|
Ok(Started::Done)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Method is called when handler returns response,
|
||||||
|
/// but before sending http message to peer.
|
||||||
|
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse)
|
||||||
|
-> Result<Response>
|
||||||
|
{
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::HeaderName::try_from("X-VERSION").unwrap(),
|
||||||
|
header::HeaderValue::from_static("0.2"));
|
||||||
|
Ok(Response::Done(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
// Register middleware, this method can be called multiple times
|
||||||
|
.middleware(Headers)
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc.
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
|
||||||
|
Logging is implemented as a middleware.
|
||||||
|
It is common to register a logging middleware as the first middleware for the application.
|
||||||
|
Logging middleware must be registered for each application.
|
||||||
|
|
||||||
|
The `Logger` middleware uses the standard log crate to log information. You should enable logger
|
||||||
|
for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/)
|
||||||
|
or similar).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create `Logger` middleware with the specified `format`.
|
||||||
|
Default `Logger` can be created with `default` method, it uses the default format:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate env_logger;
|
||||||
|
use actix_web::App;
|
||||||
|
use actix_web::middleware::Logger;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.middleware(Logger::default())
|
||||||
|
.middleware(Logger::new("%a %{User-Agent}i"))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following is an example of the default logging format:
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
|
||||||
|
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
|
||||||
|
```
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
`%%` The percent sign
|
||||||
|
|
||||||
|
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
||||||
|
|
||||||
|
`%t` Time when the request was started to process
|
||||||
|
|
||||||
|
`%P` The process ID of the child that serviced the request
|
||||||
|
|
||||||
|
`%r` First line of request
|
||||||
|
|
||||||
|
`%s` Response status code
|
||||||
|
|
||||||
|
`%b` Size of response in bytes, including HTTP headers
|
||||||
|
|
||||||
|
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
|
||||||
|
|
||||||
|
`%D` Time taken to serve the request, in milliseconds
|
||||||
|
|
||||||
|
`%{FOO}i` request.headers['FOO']
|
||||||
|
|
||||||
|
`%{FOO}o` response.headers['FOO']
|
||||||
|
|
||||||
|
`%{FOO}e` os.environ['FOO']
|
||||||
|
|
||||||
|
## Default headers
|
||||||
|
|
||||||
|
To set default response headers, the `DefaultHeaders` middleware can be used. The
|
||||||
|
*DefaultHeaders* middleware does not set the header if response headers already contain
|
||||||
|
a specified header.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{http, middleware, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.middleware(
|
||||||
|
middleware::DefaultHeaders::new()
|
||||||
|
.header("X-Version", "0.2"))
|
||||||
|
.resource("/test", |r| {
|
||||||
|
r.method(http::Method::GET).f(|req| HttpResponse::Ok());
|
||||||
|
r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed());
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## User sessions
|
||||||
|
|
||||||
|
Actix provides a general solution for session management. The
|
||||||
|
[**SessionStorage**](../../actix-web/actix_web/middleware/session/struct.SessionStorage.html) middleware can be
|
||||||
|
used with different backend types to store session data in different backends.
|
||||||
|
|
||||||
|
> By default, only cookie session backend is implemented. Other backend implementations
|
||||||
|
> can be added.
|
||||||
|
|
||||||
|
[**CookieSessionBackend**](../../actix-web/actix_web/middleware/session/struct.CookieSessionBackend.html)
|
||||||
|
uses cookies as session storage. `CookieSessionBackend` creates sessions which
|
||||||
|
are limited to storing fewer than 4000 bytes of data, as the payload must fit into a
|
||||||
|
single cookie. An internal server error is generated if a session contains more than 4000 bytes.
|
||||||
|
|
||||||
|
A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
|
||||||
|
|
||||||
|
A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client.
|
||||||
|
|
||||||
|
The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost.
|
||||||
|
|
||||||
|
In general, you create a
|
||||||
|
`SessionStorage` middleware and initialize it with specific backend implementation,
|
||||||
|
such as a `CookieSessionBackend`. To access session data,
|
||||||
|
[*HttpRequest::session()*](../../actix-web/actix_web/middleware/session/trait.RequestSession.html#tymethod.session)
|
||||||
|
must be used. This method returns a
|
||||||
|
[*Session*](../../actix-web/actix_web/middleware/session/struct.Session.html) object, which allows us to get or set
|
||||||
|
session data.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server, App, HttpRequest, Result};
|
||||||
|
use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||||
|
// access session data
|
||||||
|
if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
println!("SESSION value: {}", count);
|
||||||
|
req.session().set("counter", count+1)?;
|
||||||
|
} else {
|
||||||
|
req.session().set("counter", 1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok("Welcome!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("basic-example");
|
||||||
|
server::new(
|
||||||
|
|| App::new().middleware(
|
||||||
|
SessionStorage::new(
|
||||||
|
CookieSessionBackend::signed(&[0; 32])
|
||||||
|
.secure(false)
|
||||||
|
)))
|
||||||
|
.bind("127.0.0.1:59880").unwrap()
|
||||||
|
.start();
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Error handlers
|
||||||
|
|
||||||
|
`ErrorHandlers` middleware allows us to provide custom handlers for responses.
|
||||||
|
|
||||||
|
You can use the `ErrorHandlers::handler()` method to register a custom error handler
|
||||||
|
for a specific status code. You can modify an existing response or create a completly new
|
||||||
|
one. The error handler can return a response immediately or return a future that resolves
|
||||||
|
into a response.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{
|
||||||
|
App, HttpRequest, HttpResponse, Result,
|
||||||
|
http, middleware::Response, middleware::ErrorHandlers};
|
||||||
|
|
||||||
|
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||||
|
let mut builder = resp.into_builder();
|
||||||
|
builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||||
|
Ok(Response::Done(builder.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.middleware(
|
||||||
|
ErrorHandlers::new()
|
||||||
|
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
||||||
|
.resource("/test", |r| {
|
||||||
|
r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||||
|
r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
207
content/docs/request.md
Normal file
@ -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<T>` as a parameter, then, you use the `.with()` method for registering
|
||||||
|
this handler. It is also possible to accept arbitrary valid json object by
|
||||||
|
using `serde_json::Value` as a type `T`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Json, Result, http};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// extract `Info` using serde
|
||||||
|
fn index(info: Json<Info>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", info.username))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource(
|
||||||
|
"/index.html",
|
||||||
|
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Another option is to use *HttpRequest::json()*. This method returns a
|
||||||
|
[*JsonBody*](../../actix-web/actix_web/dev/struct.JsonBody.html) object which resolves into
|
||||||
|
the deserialized value.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct MyObj {
|
||||||
|
name: String,
|
||||||
|
number: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.json().from_err()
|
||||||
|
.and_then(|val: MyObj| {
|
||||||
|
println!("model: {:?}", val);
|
||||||
|
Ok(HttpResponse::Ok().json(val)) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also manually load the payload into memory and then deserialize it.
|
||||||
|
|
||||||
|
In the following example, we will deserialize a *MyObj* struct. We need to load the request
|
||||||
|
body first and then deserialize the json into an object.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate serde_json;
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct MyObj {name: String, number: i32}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
// `concat2` will asynchronously read each chunk of the request body and
|
||||||
|
// return a single, concatenated, chunk
|
||||||
|
req.concat2()
|
||||||
|
// `Future::from_err` acts like `?` in that it coerces the error type from
|
||||||
|
// the future into the final error type
|
||||||
|
.from_err()
|
||||||
|
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
||||||
|
// synchronous workflow
|
||||||
|
.and_then(|body| {
|
||||||
|
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
||||||
|
Ok(HttpResponse::Ok().json(obj))
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> A complete example for both options is available in
|
||||||
|
> [examples directory](https://github.com/actix/examples/tree/master/json/).
|
||||||
|
|
||||||
|
# Chunked transfer encoding
|
||||||
|
|
||||||
|
Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains
|
||||||
|
the decoded byte stream. If the request payload is compressed with one of the supported
|
||||||
|
compression codecs (br, gzip, deflate), then the byte stream is decompressed.
|
||||||
|
|
||||||
|
# Multipart body
|
||||||
|
|
||||||
|
Actix provides multipart stream support.
|
||||||
|
[*Multipart*](../../actix-web/actix_web/multipart/struct.Multipart.html) is implemented as
|
||||||
|
a stream of multipart items. Each item can be a
|
||||||
|
[*Field*](../../actix-web/actix_web/multipart/struct.Field.html) or a nested
|
||||||
|
*Multipart* stream.`HttpResponse::multipart()` returns the *Multipart* stream
|
||||||
|
for the current request.
|
||||||
|
|
||||||
|
The following demonstrates multipart stream handling for a simple form:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Box<Future<...>> {
|
||||||
|
// get multipart and iterate over multipart items
|
||||||
|
req.multipart()
|
||||||
|
.and_then(|item| {
|
||||||
|
match item {
|
||||||
|
multipart::MultipartItem::Field(field) => {
|
||||||
|
println!("==== FIELD ==== {:?} {:?}",
|
||||||
|
field.headers(),
|
||||||
|
field.content_type());
|
||||||
|
Either::A(
|
||||||
|
field.map(|chunk| {
|
||||||
|
println!("-- CHUNK: \n{}",
|
||||||
|
std::str::from_utf8(&chunk).unwrap());})
|
||||||
|
.fold((), |_, _| result(Ok(()))))
|
||||||
|
},
|
||||||
|
multipart::MultipartItem::Nested(mp) => {
|
||||||
|
Either::B(result(Ok(())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> A full example is available in the
|
||||||
|
> [examples directory](https://github.com/actix/examples/tree/master/multipart/).
|
||||||
|
|
||||||
|
# Urlencoded body
|
||||||
|
|
||||||
|
Actix provides support for *application/x-www-form-urlencoded* encoded bodies.
|
||||||
|
`HttpResponse::urlencoded()` returns a
|
||||||
|
[*UrlEncoded*](../../actix-web/actix_web/dev/struct.UrlEncoded.html) future, which resolves
|
||||||
|
to the deserialized instance. The type of the instance must implement the
|
||||||
|
`Deserialize` trait from *serde*.
|
||||||
|
|
||||||
|
The *UrlEncoded* future can resolve into an error in several cases:
|
||||||
|
|
||||||
|
* content type is not `application/x-www-form-urlencoded`
|
||||||
|
* transfer encoding is `chunked`.
|
||||||
|
* content-length is greater than 256k
|
||||||
|
* payload terminates with error.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::*;
|
||||||
|
use futures::future::{Future, ok};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FormData {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.urlencoded::<FormData>() // <- get UrlEncoded future
|
||||||
|
.from_err()
|
||||||
|
.and_then(|data| { // <- deserialized instance
|
||||||
|
println!("USERNAME: {:?}", data.username);
|
||||||
|
ok(HttpResponse::Ok().into())
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Streaming request
|
||||||
|
|
||||||
|
*HttpRequest* is a stream of `Bytes` objects. It can be used to read the request
|
||||||
|
body payload.
|
||||||
|
|
||||||
|
In the following example, we read and print the request payload chunk by chunk:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.from_err()
|
||||||
|
.fold((), |_, chunk| {
|
||||||
|
println!("Chunk: {:?}", chunk);
|
||||||
|
result::<_, error::PayloadError>(Ok(()))
|
||||||
|
})
|
||||||
|
.map(|_| HttpResponse::Ok().finish())
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
140
content/docs/response.md
Normal file
@ -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<T> where `T` is the type of a structure to serialize into *JSON*.
|
||||||
|
The type `T` must implement the `Serialize` trait from *serde*.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, HttpRequest, Json, Result, http::Method};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct MyObj {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||||
|
Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Chunked transfer encoding
|
||||||
|
|
||||||
|
Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`.
|
||||||
|
This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
|
||||||
|
If the response payload compression is enabled and a streaming body is used, chunked encoding
|
||||||
|
is enabled automatically.
|
||||||
|
|
||||||
|
> Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::stream::once;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.chunked()
|
||||||
|
.body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data"))))))
|
||||||
|
}
|
||||||
|
```
|
85
content/docs/sentry.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title: Sentry
|
||||||
|
menu: docs_patterns
|
||||||
|
weight: 1020
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sentry Crash Reporting
|
||||||
|
|
||||||
|
[Sentry](https://sentry.io/) is a crash reporting system that supports the
|
||||||
|
failure crate which is the base of the actix error reporting. With a
|
||||||
|
middleware it's possible to automatically report server errors to sentry.
|
||||||
|
|
||||||
|
# Middleware
|
||||||
|
|
||||||
|
This middleware captures any error in the server error range (500 - 599)
|
||||||
|
and sends the attached error to sentry with its stacktrace.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, Error};
|
||||||
|
use actix_web::middleware::{Middleware, Response};
|
||||||
|
use failure::Fail;
|
||||||
|
use sentry::with_client_and_scope;
|
||||||
|
use sentry::protocol::{Event, Level};
|
||||||
|
use sentry::integrations::failure::exception_from_single_fail;
|
||||||
|
|
||||||
|
/// Reports certain failures to sentry.
|
||||||
|
pub struct CaptureSentryError;
|
||||||
|
|
||||||
|
impl<S> Middleware<S> for CaptureSentryError {
|
||||||
|
fn response(&self, _: &mut HttpRequest<S>, mut resp: HttpResponse)
|
||||||
|
-> Result<Response, Error>
|
||||||
|
{
|
||||||
|
if resp.status().is_server_error() {
|
||||||
|
if let Some(error) = resp.error() {
|
||||||
|
report_actix_error_to_sentry(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Response::Done(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_actix_error_to_sentry(err: &Error) {
|
||||||
|
with_client_and_scope(|client, scope| {
|
||||||
|
let mut exceptions = vec![
|
||||||
|
exception_from_single_fail(err.cause(), Some(err.backtrace())),
|
||||||
|
];
|
||||||
|
let mut ptr: Option<&Fail> = err.cause().cause();
|
||||||
|
while let Some(cause) = ptr {
|
||||||
|
exceptions.push(exception_from_single_fail(cause, cause.backtrace()));
|
||||||
|
ptr = Some(cause);
|
||||||
|
}
|
||||||
|
exceptions.reverse();
|
||||||
|
client.capture_event(
|
||||||
|
Event {
|
||||||
|
exceptions: exceptions,
|
||||||
|
level: Level::Error,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(scope),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Middleware Usage
|
||||||
|
|
||||||
|
To use the middleware the [sentry crate](https://crates.io/crates/sentry) needs to be
|
||||||
|
initialized and configured. Additionally it makes sense to also register the panic handler
|
||||||
|
to be informed about hard panics.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate sentry;
|
||||||
|
|
||||||
|
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(CaptureSentryError)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
217
content/docs/server.md
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
title: Server
|
||||||
|
menu: docs_basics
|
||||||
|
weight: 150
|
||||||
|
---
|
||||||
|
|
||||||
|
# The HTTP Server
|
||||||
|
|
||||||
|
The [**HttpServer**](../../actix-web/actix_web/server/struct.HttpServer.html) type is responsible for
|
||||||
|
serving http requests.
|
||||||
|
|
||||||
|
`HttpServer` accepts an application factory as a parameter, and the
|
||||||
|
application factory must have `Send` + `Sync` boundaries. More about that in the
|
||||||
|
*multi-threading* section.
|
||||||
|
|
||||||
|
To bind to a specific socket address, `bind()` must be used, and it may be
|
||||||
|
called multiple times. To bind ssl socket `bind_ssl()` or `bind_tls()` should be used.
|
||||||
|
To start the http server, one of the start methods.
|
||||||
|
|
||||||
|
- use `start()` for a server
|
||||||
|
|
||||||
|
`HttpServer` is an actix actor. It must be initialized within a properly configured actix system:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server::HttpServer, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("guide");
|
||||||
|
|
||||||
|
HttpServer::new(|| {
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:59080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> It is possible to start a server in a separate thread with the `run()` method. In that
|
||||||
|
> case the server spawns a new thread and creates a new actix system in it. To stop
|
||||||
|
> this server, send a `StopServer` message.
|
||||||
|
|
||||||
|
`HttpServer` is implemented as an actix actor. It is possible to communicate with the server
|
||||||
|
via a messaging system. Start method, e.g. `start()`, returns the
|
||||||
|
address of the started http server. It accepts several messages:
|
||||||
|
|
||||||
|
- `PauseServer` - Pause accepting incoming connections
|
||||||
|
- `ResumeServer` - Resume accepting incoming connections
|
||||||
|
- `StopServer` - Stop incoming connection processing, stop all workers and exit
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use actix_web::{server, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let sys = actix::System::new("http-server");
|
||||||
|
let addr = server::new(|| {
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
|
||||||
|
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
|
||||||
|
.start();
|
||||||
|
let _ = tx.send(addr);
|
||||||
|
let _ = sys.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
let addr = rx.recv().unwrap();
|
||||||
|
let _ = addr.send(
|
||||||
|
server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-threading
|
||||||
|
|
||||||
|
`HttpServer` automatically starts an number of http workers, by default
|
||||||
|
this number is equal to number of logical CPUs in the system. This number
|
||||||
|
can be overridden with the `HttpServer::workers()` method.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpResponse, server::HttpServer};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
HttpServer::new(|| {
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.workers(4); // <- Start 4 workers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The server creates a separate application instance for each created worker. Application state
|
||||||
|
is not shared between threads. To share state, `Arc` could be used.
|
||||||
|
|
||||||
|
> Application state does not need to be `Send` and `Sync`,
|
||||||
|
> but factories must be `Send` + `Sync`.
|
||||||
|
|
||||||
|
## SSL
|
||||||
|
|
||||||
|
There are two features for ssl server: `tls` and `alpn`. The `tls` feature is
|
||||||
|
for `native-tls` integration and `alpn` is for `openssl`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["alpn"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::fs::File;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
server::new(|| {
|
||||||
|
App::new()
|
||||||
|
.resource("/index.html", |r| r.f(index))
|
||||||
|
})
|
||||||
|
.bind_ssl("127.0.0.1:8080", builder).unwrap()
|
||||||
|
.serve();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: the *HTTP/2.0* protocol requires
|
||||||
|
> [tls alpn](https://tools.ietf.org/html/rfc7301).
|
||||||
|
> At the moment, only `openssl` has `alpn` support.
|
||||||
|
> For a full example, check out
|
||||||
|
> [examples/tls](https://github.com/actix/examples/tree/master/tls).
|
||||||
|
|
||||||
|
To create the key.pem and cert.pem use the command. **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"
|
||||||
|
```
|
||||||
|
To remove the password, then copy nopass.pen to key.pem
|
||||||
|
```bash
|
||||||
|
$ openssl rsa -in key.pem -out nopass.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keep-Alive
|
||||||
|
|
||||||
|
Actix can wait for requests on a keep-alive connection.
|
||||||
|
|
||||||
|
> *keep alive* connection behavior is defined by server settings.
|
||||||
|
|
||||||
|
- `75`, `Some(75)`, `KeepAlive::Timeout(75)` - enable 75 second *keep alive* timer.
|
||||||
|
- `None` or `KeepAlive::Disabled` - disable *keep alive*.
|
||||||
|
- `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{server, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(||
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
||||||
|
.keep_alive(75); // <- Set keep-alive to 75 seconds
|
||||||
|
|
||||||
|
server::new(||
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
||||||
|
.keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option.
|
||||||
|
|
||||||
|
server::new(||
|
||||||
|
App::new()
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
||||||
|
.keep_alive(None); // <- Disable keep-alive
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the first option is selected, then *keep alive* state is
|
||||||
|
calculated based on the response's *connection-type*. By default
|
||||||
|
`HttpResponse::connection_type` is not defined. In that case *keep alive* is
|
||||||
|
defined by the request's http version.
|
||||||
|
|
||||||
|
> *keep alive* is **off** for *HTTP/1.0* and is **on** for *HTTP/1.1* and *HTTP/2.0*.
|
||||||
|
|
||||||
|
*Connection type* can be change with `HttpResponseBuilder::connection_type()` method.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, http};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.connection_type(http::ConnectionType::Close) // <- Close connection
|
||||||
|
.force_close() // <- Alternative method
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Graceful shutdown
|
||||||
|
|
||||||
|
`HttpServer` supports graceful shutdown. After receiving a stop signal, workers
|
||||||
|
have a specific amount of time to finish serving requests. Any workers still alive after the
|
||||||
|
timeout are force-dropped. By default the shutdown timeout is set to 30 seconds.
|
||||||
|
You can change this parameter with the `HttpServer::shutdown_timeout()` method.
|
||||||
|
|
||||||
|
You can send a stop message to the server with the server address and specify if you want
|
||||||
|
graceful shutdown or not. The `start()` methods returns address of the server.
|
||||||
|
|
||||||
|
`HttpServer` handles several OS signals. *CTRL-C* is available on all OSs,
|
||||||
|
other signals are available on unix systems.
|
||||||
|
|
||||||
|
- *SIGINT* - Force shutdown workers
|
||||||
|
- *SIGTERM* - Graceful shutdown workers
|
||||||
|
- *SIGQUIT* - Force shutdown workers
|
||||||
|
|
||||||
|
> It is possible to disable signal handling with `HttpServer::disable_signals()` method.
|
56
content/docs/static-files.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Static Files
|
||||||
|
menu: docs_advanced
|
||||||
|
weight: 230
|
||||||
|
---
|
||||||
|
|
||||||
|
# Individual file
|
||||||
|
|
||||||
|
It is possible to serve static files with a custom path pattern and `NamedFile`. To
|
||||||
|
match a path tail, we can use a `[.*]` regex.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<NamedFile> {
|
||||||
|
let path: PathBuf = req.match_info().query("tail")?;
|
||||||
|
Ok(NamedFile::open(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Directory
|
||||||
|
|
||||||
|
To serve files from specific directories and sub-directories, `StaticFiles` can be used.
|
||||||
|
`StaticFiles` must be registered with an `App::handler()` method, otherwise
|
||||||
|
it will be unable to serve sub-paths.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, fs};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.handler(
|
||||||
|
"/static",
|
||||||
|
fs::StaticFiles::new(".")
|
||||||
|
.show_files_listing())
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The parameter is the base directory. By default files listing for sub-directories
|
||||||
|
is disabled. Attempt to load directory listing will return *404 Not Found* response.
|
||||||
|
To enable files listing, use
|
||||||
|
[*StaticFiles::show_files_listing()*](../../actix-web/actix_web/fs/struct.StaticFiles.html#method.show_files_listing)
|
||||||
|
method.
|
||||||
|
|
||||||
|
Instead of showing files listing for directory, it is possible to redirect
|
||||||
|
to a specific index file. Use the
|
||||||
|
[*StaticFiles::index_file()*](../../actix-web/actix_web/fs/struct.StaticFiles.html#method.index_file)
|
||||||
|
method to configure this redirect.
|
178
content/docs/testing.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
---
|
||||||
|
title: Testing
|
||||||
|
menu: docs_advanced
|
||||||
|
weight: 210
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
Every application should be well tested. Actix provides tools to perform unit and
|
||||||
|
integration tests.
|
||||||
|
|
||||||
|
# Unit Tests
|
||||||
|
|
||||||
|
For unit testing, actix provides a request builder type and a simple handler runner.
|
||||||
|
[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html)
|
||||||
|
implements a builder-like pattern.
|
||||||
|
You can generate a `HttpRequest` instance with `finish()`, or you can
|
||||||
|
run your handler with `run()` or `run_async()`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) {
|
||||||
|
if let Ok(s) = hdr.to_str() {
|
||||||
|
return HttpResponse::Ok().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpResponse::BadRequest().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let resp = test::TestRequest::with_header("content-type", "text/plain")
|
||||||
|
.run(index)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||||
|
|
||||||
|
let resp = test::TestRequest::default()
|
||||||
|
.run(index)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Integration tests
|
||||||
|
|
||||||
|
There are several methods for testing your application. Actix provides
|
||||||
|
[*TestServer*](../../actix-web/actix_web/test/struct.TestServer.html), which can be used
|
||||||
|
to run the application with specific handlers in a real http server.
|
||||||
|
|
||||||
|
`TestServer::get()`, `TestServer::post()`, and `TestServer::client()`
|
||||||
|
methods can be used to send requests to the test server.
|
||||||
|
|
||||||
|
A simple form `TestServer` can be configured to use a handler.
|
||||||
|
`TestServer::new` method accepts a configuration function, and the only argument
|
||||||
|
for this function is a *test application* instance.
|
||||||
|
|
||||||
|
> Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html)
|
||||||
|
> for more information.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{HttpRequest, HttpMessage};
|
||||||
|
use actix_web::test::TestServer;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// start new test server
|
||||||
|
let mut srv = TestServer::new(|app| app.handler(index));
|
||||||
|
|
||||||
|
let request = srv.get().finish().unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
let body = str::from_utf8(&bytes).unwrap();
|
||||||
|
assert_eq!(body, "Hello world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The other option is to use an application factory. In this case, you need to pass the factory
|
||||||
|
function the same way as you would for real http server configuration.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{http, test, App, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function get called by http server.
|
||||||
|
fn create_app() -> App {
|
||||||
|
App::new()
|
||||||
|
.resource("/test", |r| r.h(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut srv = test::TestServer::with_factory(create_app);
|
||||||
|
|
||||||
|
let request = srv.client(
|
||||||
|
http::Method::GET, "/test").finish().unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need more complex application configuration, use the `TestServer::build_with_state()`
|
||||||
|
method. For example, you may need to initialize application state or start `SyncActor`'s for diesel
|
||||||
|
interation. This method accepts a closure that constructs the application state,
|
||||||
|
and it runs when the actix system is configured. Thus, you can initialize any additional actors.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let srv = TestServer::build_with_state(|| {
|
||||||
|
// we can start diesel actors
|
||||||
|
let addr = SyncArbiter::start(3, || {
|
||||||
|
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||||
|
});
|
||||||
|
// then we can construct custom state, or it could be `()`
|
||||||
|
MyState{addr: addr}
|
||||||
|
})
|
||||||
|
|
||||||
|
// register server handlers and start test server
|
||||||
|
.start(|app| {
|
||||||
|
app.resource(
|
||||||
|
"/{username}/index.html", |r| r.with(
|
||||||
|
|p: Path<PParam>| format!("Welcome {}!", p.username)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// now we can run our test code
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
# WebSocket server tests
|
||||||
|
|
||||||
|
It is possible to register a *handler* with `TestApp::handler()`, which
|
||||||
|
initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to
|
||||||
|
the websocket server and returns ws reader and writer objects. *TestServer* also
|
||||||
|
provides an `execute()` method, which runs future objects to completion and returns
|
||||||
|
result of the future computation.
|
||||||
|
|
||||||
|
The following example demonstrates how to test a websocket handler:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
struct Ws; // <- WebSocket actor
|
||||||
|
|
||||||
|
impl Actor for Ws {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
match msg {
|
||||||
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|req| ws::start(req, Ws)));
|
||||||
|
|
||||||
|
let (reader, mut writer) = srv.ws().unwrap();
|
||||||
|
writer.text("text");
|
||||||
|
|
||||||
|
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||||
|
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
||||||
|
}
|
||||||
|
```
|
629
content/docs/url-dispatch.md
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.route("/user/{name}", Method::GET, index)
|
||||||
|
.route("/user/{name}", Method::POST, index)
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/prefix", |r| r.f(index))
|
||||||
|
.resource("/user/{name}",
|
||||||
|
|r| r.method(Method::GET).f(|req| HttpResponse::Ok()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/path", |resource|
|
||||||
|
resource.route()
|
||||||
|
.filter(pred::Get())
|
||||||
|
.filter(pred::Header("content-type", "text/plain"))
|
||||||
|
.f(|req| HttpResponse::Ok())
|
||||||
|
)
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `HttpResponse::Ok()` is returned for *GET* requests.
|
||||||
|
If a request contains `Content-Type` header, the value of this header is *text/plain*,
|
||||||
|
and path equals to `/path`, Resource calls handle of the first matching route.
|
||||||
|
|
||||||
|
If a resource can not match any route, a "NOT FOUND" response is returned.
|
||||||
|
|
||||||
|
[*ResourceHandler::route()*](../../actix-web/actix_web/dev/struct.ResourceHandler.html#method.route) returns a
|
||||||
|
[*Route*](../../actix-web/actix_web/dev/struct.Route.html) object. Route can be configured with a
|
||||||
|
builder-like pattern. Following configuration methods are available:
|
||||||
|
|
||||||
|
* [*Route::filter()*](../../actix-web/actix_web/dev/struct.Route.html#method.filter)
|
||||||
|
registers a new predicate. Any number of predicates can be registered for each route.
|
||||||
|
* [*Route::f()*](../../actix-web/actix_web/dev/struct.Route.html#method.f) registers
|
||||||
|
handler function for this route. Only one handler can be registered.
|
||||||
|
Usually handler registration
|
||||||
|
is the last config operation. Handler function can be a function or closure
|
||||||
|
and has the type
|
||||||
|
`Fn(HttpRequest<S>) -> R + 'static`
|
||||||
|
* [*Route::h()*](../../actix-web/actix_web/dev/struct.Route.html#method.h) registers
|
||||||
|
a handler object that implements the `Handler` trait. This is
|
||||||
|
similar to `f()` method - only one handler can
|
||||||
|
be registered. Handler registration is the last config operation.
|
||||||
|
* [*Route::a()*](../../actix-web/actix_web/dev/struct.Route.html#method.a) registers
|
||||||
|
an async handler function for this route. Only one handler can be registered.
|
||||||
|
Handler registration is the last config operation. Handler function can
|
||||||
|
be a function or closure and has the type
|
||||||
|
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
|
||||||
|
|
||||||
|
# Route matching
|
||||||
|
|
||||||
|
The main purpose of route configuration is to match (or not match) the request's `path`
|
||||||
|
against a URL path pattern. `path` represents the path portion of the URL that was requested.
|
||||||
|
|
||||||
|
The way that *actix* does this is very simple. When a request enters the system,
|
||||||
|
for each resource configuration declaration present in the system, actix checks
|
||||||
|
the request's path against the pattern declared. This checking happens in the order that
|
||||||
|
the routes were declared via `App::resource()` method. If resource can not be found,
|
||||||
|
the *default resource* is used as the matched resource.
|
||||||
|
|
||||||
|
When a route configuration is declared, it may contain route predicate arguments. All route
|
||||||
|
predicates associated with a route declaration must be `true` for the route configuration to
|
||||||
|
be used for a given request during a check. If any predicate in the set of route predicate
|
||||||
|
arguments provided to a route configuration returns `false` during a check, that route is
|
||||||
|
skipped and route matching continues through the ordered set of routes.
|
||||||
|
|
||||||
|
If any route matches, the route matching process stops and the handler associated with
|
||||||
|
the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned.
|
||||||
|
|
||||||
|
# Resource pattern syntax
|
||||||
|
|
||||||
|
The syntax of the pattern matching language used by actix in the pattern
|
||||||
|
argument is straightforward.
|
||||||
|
|
||||||
|
The pattern used in route configuration may start with a slash character. If the pattern
|
||||||
|
does not start with a slash character, an implicit slash will be prepended
|
||||||
|
to it at matching time. For example, the following patterns are equivalent:
|
||||||
|
|
||||||
|
```
|
||||||
|
{foo}/bar/baz
|
||||||
|
```
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
```
|
||||||
|
/{foo}/bar/baz
|
||||||
|
```
|
||||||
|
|
||||||
|
A *variable part* (replacement marker) is specified in the form *{identifier}*,
|
||||||
|
where this means "accept any characters up to the next slash character and use this
|
||||||
|
as the name in the `HttpRequest.match_info()` object".
|
||||||
|
|
||||||
|
A replacement marker in a pattern matches the regular expression `[^{}/]+`.
|
||||||
|
|
||||||
|
A match_info is the `Params` object representing the dynamic parts extracted from a
|
||||||
|
*URL* based on the routing pattern. It is available as *request.match_info*. For example, the
|
||||||
|
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{baz}/{bar}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above pattern will match these URLs, generating the following match information:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
|
||||||
|
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
|
||||||
|
```
|
||||||
|
|
||||||
|
It will not match the following patterns however:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2/ -> No match (trailing slash)
|
||||||
|
bar/abc/def -> First segment literal mismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
The match for a segment replacement marker in a segment will be done only up to
|
||||||
|
the first non-alphanumeric character in the segment in the pattern. So, for instance,
|
||||||
|
if this route pattern was used:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{name}.html
|
||||||
|
```
|
||||||
|
|
||||||
|
The literal path */foo/biz.html* will match the above route pattern, and the match result
|
||||||
|
will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match,
|
||||||
|
because it does not contain a literal *.html* at the end of the segment represented
|
||||||
|
by *{name}.html* (it only contains biz, not biz.html).
|
||||||
|
|
||||||
|
To capture both segments, two replacement markers can be used:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{name}.{ext}
|
||||||
|
```
|
||||||
|
|
||||||
|
The literal path */foo/biz.html* will match the above route pattern, and the match
|
||||||
|
result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a
|
||||||
|
literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*.
|
||||||
|
|
||||||
|
Replacement markers can optionally specify a regular expression which will be used to decide
|
||||||
|
whether a path segment should match the marker. To specify that a replacement marker should
|
||||||
|
match only a specific set of characters as defined by a regular expression, you must use a
|
||||||
|
slightly extended form of replacement marker syntax. Within braces, the replacement marker
|
||||||
|
name must be followed by a colon, then directly thereafter, the regular expression. The default
|
||||||
|
regular expression associated with a replacement marker *[^/]+* matches one or more characters
|
||||||
|
which are not a slash. For example, under the hood, the replacement marker *{foo}* can more
|
||||||
|
verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression
|
||||||
|
to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits.
|
||||||
|
|
||||||
|
Segments must contain at least one character in order to match a segment replacement marker.
|
||||||
|
For example, for the URL */abc/*:
|
||||||
|
|
||||||
|
* */abc/{foo}* will not match.
|
||||||
|
* */{foo}/* will match.
|
||||||
|
|
||||||
|
> **Note**: path will be URL-unquoted and decoded into valid unicode string before
|
||||||
|
> matching pattern and values representing matched path segments will be URL-unquoted too.
|
||||||
|
|
||||||
|
So for instance, the following pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{bar}
|
||||||
|
```
|
||||||
|
|
||||||
|
When matching the following URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://example.com/foo/La%20Pe%C3%B1a
|
||||||
|
```
|
||||||
|
|
||||||
|
The matchdict will look like so (the value is URL-decoded):
|
||||||
|
|
||||||
|
```
|
||||||
|
Params{'bar': 'La Pe\xf1a'}
|
||||||
|
```
|
||||||
|
|
||||||
|
Literal strings in the path segment should represent the decoded value of the
|
||||||
|
path provided to actix. You don't want to use a URL-encoded value in the pattern.
|
||||||
|
For example, rather than this:
|
||||||
|
|
||||||
|
```
|
||||||
|
/Foo%20Bar/{baz}
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll want to use something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
/Foo Bar/{baz}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to get "tail match". For this purpose custom regex has to be used.
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{bar}/{tail:.*}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above pattern will match these URLs, generating the following match information:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
|
||||||
|
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 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.:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
let v1: u8 = req.match_info().query("v1")?;
|
||||||
|
let v2: u8 = req.match_info().query("v2")?;
|
||||||
|
Ok(format!("Values {} {}", v1, v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use actix_web::{App, HttpRequest, Result, http::Method};
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
let path: PathBuf = req.match_info().query("tail")?;
|
||||||
|
Ok(format!("Path {:?}", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will
|
||||||
|
always fail.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, Path, Result, http::Method};
|
||||||
|
|
||||||
|
// extract path info using serde
|
||||||
|
fn index(info: Path<(String, u32)>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}! id: {}", info.0, info.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/{username}/{id}/index.html", // <- define path parameters
|
||||||
|
|r| r.method(Method::GET).with(index));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
It also possible to extract path pattern information to a struct. In this case,
|
||||||
|
this struct must implement *serde's *`Deserialize` trait.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
use actix_web::{App, Path, Result, http::Method};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Info {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract path info using serde
|
||||||
|
fn index(info: Path<Info>) -> Result<String> {
|
||||||
|
Ok(format!("Welcome {}!", info.username))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/{username}/index.html", // <- define path parameters
|
||||||
|
|r| r.method(Method::GET).with(index));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[*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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource
|
||||||
|
Ok(HttpResponse::Found()
|
||||||
|
.header(header::LOCATION, url.as_str())
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/test/{a}/{b}/{c}", |r| {
|
||||||
|
r.name("foo"); // <- set resource name, then it could be used in `url_for`
|
||||||
|
r.method(Method::GET).f(|_| HttpResponse::Ok());
|
||||||
|
})
|
||||||
|
.route("/test/", Method::GET, index)
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::{App, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
|
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||||
|
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||||
|
Ok(HttpResponse::Ok().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/index.html", |r| r.f(index))
|
||||||
|
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 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*.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::http::NormalizePath;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/resource/", |r| r.f(index))
|
||||||
|
.default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpRequest, http::Method, http::NormalizePath};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new()
|
||||||
|
.resource("/resource/", |r| r.f(index))
|
||||||
|
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn show_users(req: HttpRequest) -> HttpResponse {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.prefix("/users")
|
||||||
|
.resource("/show", |r| r.f(show_users))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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*:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{http, pred::Predicate, App, HttpMessage, HttpRequest};
|
||||||
|
|
||||||
|
struct ContentTypeHeader;
|
||||||
|
|
||||||
|
impl<S: 'static> Predicate<S> for ContentTypeHeader {
|
||||||
|
|
||||||
|
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||||
|
req.headers().contains_key(http::header::CONTENT_TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/index.html", |r|
|
||||||
|
r.route()
|
||||||
|
.filter(ContentTypeHeader)
|
||||||
|
.f(|_| HttpResponse::Ok()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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":
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{pred, App, HttpResponse};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/index.html", |r|
|
||||||
|
r.route()
|
||||||
|
.filter(pred::Not(pred::Get()))
|
||||||
|
.f(|req| HttpResponse::MethodNotAllowed()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{App, HttpResponse, http::Method, pred};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.default_resource(|r| {
|
||||||
|
r.method(Method::GET).f(|req| HttpResponse::NotFound());
|
||||||
|
r.route().filter(pred::Not(pred::Get()))
|
||||||
|
.f(|req| HttpResponse::MethodNotAllowed());
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
50
content/docs/websockets.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Websockets
|
||||||
|
menu: docs_proto
|
||||||
|
weight: 240
|
||||||
|
---
|
||||||
|
|
||||||
|
Actix supports WebSockets out-of-the-box. It is possible to convert a request's `Payload`
|
||||||
|
to a stream of [*ws::Message*](../../actix-web/actix_web/ws/enum.Message.html) with
|
||||||
|
a [*ws::WsStream*](../../actix-web/actix_web/ws/struct.WsStream.html) and then use stream
|
||||||
|
combinators to handle actual messages, but it is simpler to handle websocket communications
|
||||||
|
with an http actor.
|
||||||
|
|
||||||
|
The following is an example of a simple websocket echo server:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
/// Define http actor
|
||||||
|
struct Ws;
|
||||||
|
|
||||||
|
impl Actor for Ws {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for ws::Message message
|
||||||
|
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws)))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> A simple websocket echo server example is available in the
|
||||||
|
> [examples directory](https://github.com/actix/examples/tree/master/websocket/).
|
||||||
|
|
||||||
|
> An example chat server with the ability to chat over a websocket or tcp connection
|
||||||
|
> is available in [websocket-chat directory](https://github.com/actix/examples/tree/master/websocket-chat/)
|
27
content/docs/whatis.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: What is Actix
|
||||||
|
menu: docs_intro
|
||||||
|
weight: 100
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actix is Multiple Things
|
||||||
|
|
||||||
|
Actix is a few things. The base of it is a powerful actor system for Rust on
|
||||||
|
top of which the `actix-web` system is built. This is what you are most likely
|
||||||
|
going to work with. What `actix-web` gives you is a fun and very fast web
|
||||||
|
development framework.
|
||||||
|
|
||||||
|
We call `actix-web` a small and pragmatic framework. For all intents and purposes
|
||||||
|
it's a microframework with a few twists. If you are already a Rust programmer
|
||||||
|
you will probably find yourself at home quickly, but even if you are coming from
|
||||||
|
another programming language you should find actix-web easy to pick up.
|
||||||
|
|
||||||
|
An application developed with `actix-web` will expose an HTTP server contained
|
||||||
|
within a native executable. You can put this behind another HTTP server like
|
||||||
|
nginx or serve it up as such. Even in the complete absence of another HTTP
|
||||||
|
server `actix-web` is powerful enough to provide HTTP 1 and HTTP 2 support as
|
||||||
|
well as SSL/TLS. This makes it useful for building small services ready for
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
Most importantly: `actix-web` runs on Rust 1.24 or later and it works with
|
||||||
|
stable releases.
|
@ -1,7 +0,0 @@
|
|||||||
title: Docs::API Example
|
|
||||||
layout: docs.liquid
|
|
||||||
permalink: /docs/API/API-example.html
|
|
||||||
---
|
|
||||||
## API Example
|
|
||||||
|
|
||||||
The words is comming.
|
|
@ -1,8 +0,0 @@
|
|||||||
title: Docs::Advance Example
|
|
||||||
layout: docs.liquid
|
|
||||||
permalink: /docs/Advance/Advance-example.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advance Example
|
|
||||||
|
|
||||||
The words is comming.
|
|
@ -1,8 +0,0 @@
|
|||||||
title: Docs::Basics Example
|
|
||||||
layout: docs.liquid
|
|
||||||
permalink: /docs/Basics/Basics-example.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Basics Example
|
|
||||||
|
|
||||||
The words is comming.
|
|
@ -1,8 +0,0 @@
|
|||||||
title: Docs::Reference Example
|
|
||||||
layout: docs.liquid
|
|
||||||
permalink: /docs/Reference/Reference-example.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Reference Example
|
|
||||||
|
|
||||||
The words is comming.
|
|
@ -1,13 +0,0 @@
|
|||||||
title: Docs
|
|
||||||
layout: docs.liquid
|
|
||||||
permalink: /docs/index.html
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Welcome to the Actix-web docs!
|
|
||||||
|
|
||||||
If you feel like contributing further, you can fork that repo,
|
|
||||||
branch from `source`, and submit a pull request.
|
|
||||||
|
|
||||||
Contributions are always welcome!
|
|
37
index.liquid
@ -1,37 +0,0 @@
|
|||||||
title: Actix web framework
|
|
||||||
layout: default.liquid
|
|
||||||
permalink: /index.html
|
|
||||||
---
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div>
|
|
||||||
<div id="top">
|
|
||||||
<h1>Actix web framework</h1>
|
|
||||||
<p>Actix web is a simple, pragmatic and extremely fast web framework for Rust.</p>
|
|
||||||
</div>
|
|
||||||
<div id="Features">
|
|
||||||
<div id="feature" class="columns four">
|
|
||||||
<article>
|
|
||||||
<h3>Type Safe</h3>
|
|
||||||
<p>Forget about stringly typed objects,
|
|
||||||
from request to response, everything has types.</p>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<h3>Feature rich</h3>
|
|
||||||
<p>Actix provides a lot of features out of box.
|
|
||||||
WebSockets, Http/2, pipelining etc.</p>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<h3>Extensible</h3>
|
|
||||||
<p>Easily create your own libraries that any Actix application can use.</p>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<h3>Blazingly Fast</h3>
|
|
||||||
<p>Actix is blazingly fast.
|
|
||||||
<a href="https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext">Check yourself</a>.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
23
layouts/code/baseof.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{{ partial "header" . }}
|
||||||
|
{{ $currentURL := .URL }}
|
||||||
|
|
||||||
|
<div class="actix-pageheader">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="display-4">{{ .Title }}</h1>
|
||||||
|
{{ if .Description }}
|
||||||
|
<p class="lead">{{ .Description }}</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container actix-community">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div class="actix-content">
|
||||||
|
{{ block "main" . }}{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ partial "footer" . }}
|
3
layouts/code/code.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
4
layouts/code/single.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
<p class="uplink"><a href="/code/">↑ Back to code</a>
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
23
layouts/community/baseof.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{{ partial "header" . }}
|
||||||
|
{{ $currentURL := .URL }}
|
||||||
|
|
||||||
|
<div class="actix-pageheader">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="display-4">{{ .Title }}</h1>
|
||||||
|
{{ if .Description }}
|
||||||
|
<p class="lead">{{ .Description }}</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container actix-community">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div class="actix-content">
|
||||||
|
{{ block "main" . }}{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ partial "footer" . }}
|
3
layouts/community/community.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
4
layouts/community/single.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
<p class="uplink"><a href="/community/">↑ Back to community</a>
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
100
layouts/docs/baseof.html
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
{{ partial "header" . }}
|
||||||
|
{{ $currentURL := .URL }}
|
||||||
|
|
||||||
|
<div class="actix-pageheader">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="display-4">{{ .Title }}</h1>
|
||||||
|
{{ if .Description }}
|
||||||
|
<p class="lead">{{ .Description }}</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container actix-docs">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<nav class="leftnav">
|
||||||
|
<div class="" id="">
|
||||||
|
<h5>Introduction</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
{{ range .Site.Menus.docs_intro }}
|
||||||
|
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Basics</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
{{ range .Site.Menus.docs_basics }}
|
||||||
|
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Advanced</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
{{ range .Site.Menus.docs_advanced }}
|
||||||
|
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Protocols</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
{{ range .Site.Menus.docs_proto }}
|
||||||
|
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Patterns</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
{{ range .Site.Menus.docs_patterns }}
|
||||||
|
<li{{ if eq $currentURL .URL }} class="active"{{ end }}>
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>API Documentation</h5>
|
||||||
|
<div>
|
||||||
|
<ul class="nav">
|
||||||
|
<li><a href="https://docs.rs/actix">actix <span class="fa fa-external-link"></span></a></li>
|
||||||
|
<li><a href="https://docs.rs/actix-web">actix-web <span class="fa fa-external-link"></span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="actix-content">
|
||||||
|
{{ block "main" . }}{{ end }}
|
||||||
|
<div class="github-edit">
|
||||||
|
<a class="fa fa-github" href="https://github.com/actix/actix-website/tree/master/content/{{ .File.Path }}"> Edit on GitHub</a>
|
||||||
|
</div>
|
||||||
|
<!-- Yes, we have to use *Prev* to get the next content, because the
|
||||||
|
weights are sorted inversely to how they are actually displayed... -->
|
||||||
|
{{ with .PrevInSection }}<div class="actix-next"><b>Next up</b>: <a href = {{ .URL }}>{{ end }}
|
||||||
|
{{ with .PrevInSection }} {{ .Title }}</a></div>{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ partial "footer" . }}
|
3
layouts/docs/docs.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
3
layouts/docs/single.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
64
layouts/index.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{{ partial "header" . }}
|
||||||
|
|
||||||
|
<div class="jumbotron">
|
||||||
|
<div class="actix-jumbotron">
|
||||||
|
<img src="/img/logo-large.png" class="align-middle actix-logo" alt="">
|
||||||
|
<p class="lead">rust's powerful actor system and most fun web framework</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container actix-home">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="actix-features">
|
||||||
|
<h2>
|
||||||
|
<i class="fa fa-fw fa-shield" aria-hidden="true"></i>
|
||||||
|
Type Safe
|
||||||
|
</h2>
|
||||||
|
<p>Forget about stringly typed objects, from request to response, everything has types.</p>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<i class="fa fa-fw fa-battery-full" aria-hidden="true"></i>
|
||||||
|
Feature Rich
|
||||||
|
</h2>
|
||||||
|
<p>Actix provides a lot of features out of box. WebSockets, HTTP/2, pipelining etc.</p>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<i class="fa fa-fw fa-puzzle-piece" aria-hidden="true"></i>
|
||||||
|
Extensible
|
||||||
|
</h2>
|
||||||
|
<p>Easily create your own libraries that any Actix application can use.</p>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<i class="fa fa-fw fa-dashboard" aria-hidden="true"></i>
|
||||||
|
Blazingly Fast
|
||||||
|
</h2>
|
||||||
|
<p>Actix is blazingly fast. <a href="https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=fortune">Check yourself</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="actix-content">
|
||||||
|
{{ 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(|| {
|
||||||
|
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" "" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ partial "footer" . }}
|
18
layouts/partials/footer.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<footer class="actix-footer">
|
||||||
|
<div class="text-muted actix-footer-gray d-flex justify-content-between">
|
||||||
|
<div class="actix-footer-info">
|
||||||
|
Copyright © 2018 The Actix Team
|
||||||
|
</div>
|
||||||
|
<div class="actix-footer-social">
|
||||||
|
<a href="https://github.com/actix" class="text-muted"><i class="fa fa-github" aria-hidden="true"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- jQuery first, then Tether, then Bootstrap JS. -->
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js" integrity="sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8" crossorigin="anonymous"></script>
|
||||||
|
<script src="/js/bootstrap.min.js"></script>
|
||||||
|
{{ template "_internal/google_analytics.html" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
47
layouts/partials/header.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap-reboot.css">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/actix.css">
|
||||||
|
<link rel="stylesheet" href="/css/highlight.css">
|
||||||
|
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="navbar navbar-light navbar-toggleable-md bd-navbar">
|
||||||
|
<nav class="actix-main-nav">
|
||||||
|
<div class="d-flex justify-content-between hidden-lg-up">
|
||||||
|
<a href="/" class="navbar-brand">
|
||||||
|
<img src="/img/logo-nav.png" class="align-middle" alt="">
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#actix-main-nav" aria-label="Toggle navigation" aria-controls="actix-main-nav" aria-expanded="false">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-collapse collapse" id="actix-main-nav">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="nav-item hd-lg-down">
|
||||||
|
<a class="navbar-brand" href="/"><img src="/img/logo-nav.png" class="align-middle" alt=""></a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/docs/">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/community/">Community</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/code/">Code</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
5
layouts/shortcodes/actix-version.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{- if eq (.Get 0) "actix" -}}
|
||||||
|
{{- .Page.Site.Params.actixVersion -}}
|
||||||
|
{{- else if eq (.Get 0) "actix-web" -}}
|
||||||
|
{{- .Page.Site.Params.actixWebVersion -}}
|
||||||
|
{{- end -}}
|
1
layouts/shortcodes/actix-web-version.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.6
|
15
more.md
@ -1,15 +0,0 @@
|
|||||||
title: More
|
|
||||||
layout: default.liquid
|
|
||||||
permalink: /more.html
|
|
||||||
---
|
|
||||||
<section >
|
|
||||||
<div >
|
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
The words is comming.
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
@ -1,328 +0,0 @@
|
|||||||
:root {
|
|
||||||
--grey: #f5f5f5;
|
|
||||||
--text-color: #413e3e;
|
|
||||||
--black: #000000;
|
|
||||||
--white: #ffffff;
|
|
||||||
--new: rgb(82, 29, 79);
|
|
||||||
--font-stack: helvetica, arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *:before, *:after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding:0px; margin:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--font-stack);
|
|
||||||
line-height: 1.4rem;
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--text-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:hover { color: var(--new) ;text-decoration:none; }
|
|
||||||
|
|
||||||
ul, ol, li {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------nav----------------------------------------*/
|
|
||||||
#lnav {
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
line-height: 60px;
|
|
||||||
zoom:1;
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--white);
|
|
||||||
justify-content: space-between;
|
|
||||||
border-bottom: 1px solid #dfdcdc;
|
|
||||||
box-shadow: 0 3px 3px rgba(200, 216, 206, 0.12), 0 3px 3px rgba(97, 107, 100, 0.24);
|
|
||||||
}
|
|
||||||
#line {
|
|
||||||
position: fixed;
|
|
||||||
zoom:1;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background-color: var(--black);
|
|
||||||
}
|
|
||||||
#lnav h1 #home {
|
|
||||||
font-size: 33px;
|
|
||||||
margin: auto 1vw;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
#lnav label {
|
|
||||||
float: right;
|
|
||||||
font-size: 26px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: auto 2vw;
|
|
||||||
}
|
|
||||||
#lnav h1 img {
|
|
||||||
width: 2.8rem;
|
|
||||||
height: 2.8rem;
|
|
||||||
margin-top: -0.25rem;
|
|
||||||
vertical-align:middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------section----------------------------------------*/
|
|
||||||
section {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
section.docs aside ul li a, section.blog aside ul li a {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 0.8rem;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.15s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.docs aside ul li a.active,
|
|
||||||
section.docs aside ul li a:hover,
|
|
||||||
section.docs aside ul li a:active,
|
|
||||||
section.docs aside ul li a:focus,
|
|
||||||
section.blog aside ul li a.active,
|
|
||||||
section.blog aside ul li a:hover,
|
|
||||||
section.blog aside ul li a:active,
|
|
||||||
section.blog aside ul li a:focus {
|
|
||||||
color: var(--new);
|
|
||||||
border-left: 3px solid var(--new);
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
}
|
|
||||||
section.docs aside p, section.blog aside p {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
padding: 9.5px;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
color: var(--new);
|
|
||||||
word-break: break-all;
|
|
||||||
word-wrap: break-word;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 2px 4px;
|
|
||||||
font-size: 90%;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
color: var(--new);
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
padding: 0;
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------footer----------------------------------------*/
|
|
||||||
footer{
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-top: 2px solid var(--new);
|
|
||||||
}
|
|
||||||
footer a {
|
|
||||||
color: var(--new);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
footer p {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: lighter;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------home page----------------------------------------*/
|
|
||||||
#top {
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 3rem;
|
|
||||||
border: 2px solid var(--grey);
|
|
||||||
}
|
|
||||||
#top h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
#top p {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
#top button {
|
|
||||||
margin-right: 18px;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 8px 22px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Features {
|
|
||||||
margin: 2rem auto;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
border: 2px solid var(--grey);
|
|
||||||
}
|
|
||||||
#Features #feature {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
#Features article {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
#Features article h3 {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------mobile----------------------------------------*/
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
#lnav h1 img {
|
|
||||||
margin-left: 1vw;
|
|
||||||
}
|
|
||||||
#rnav {
|
|
||||||
padding-top: 3.6rem;
|
|
||||||
height: 0;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
}
|
|
||||||
#rnav li a {
|
|
||||||
display:block;
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid #d8d8d8;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
}
|
|
||||||
#top {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 3.5rem 2rem 5rem 2rem;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
margin: 0 auto ;
|
|
||||||
padding-top: 1rem;
|
|
||||||
width: 94%;
|
|
||||||
}
|
|
||||||
#hr {
|
|
||||||
width: 100%;
|
|
||||||
height: 0.25rem;
|
|
||||||
margin: 0.6rem 0;
|
|
||||||
background-color: var(--black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* ----------------------------------------table----------------------------------------*/
|
|
||||||
@media only screen and (min-width: 600px) and (max-width: 1000px) {
|
|
||||||
#menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#lnav h1 img {
|
|
||||||
margin-left: 5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rnav {
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
line-height: 60px;
|
|
||||||
padding-right: 5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rnav li {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
#rnav li a {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-left: 2vw;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 5rem;
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.docs .inner, section.blog .inner {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
section.docs aside, section.blog aside {
|
|
||||||
flex: 0 1 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.docs main, section.blog main {
|
|
||||||
flex: 0 1 70%;
|
|
||||||
}
|
|
||||||
#hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.columns {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.columns.three > article {
|
|
||||||
flex: 0 1 33%;
|
|
||||||
margin-right: 1.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.columns.three > article:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------desktop----------------------------------------*/
|
|
||||||
@media only screen and (min-width: 1000px) {
|
|
||||||
#menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#lnav h1 img {
|
|
||||||
margin-left: 11vw;
|
|
||||||
}
|
|
||||||
#rnav {
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
line-height: 60px;
|
|
||||||
padding-right: 10vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rnav li {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
#rnav li a {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-left: 2vw;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 5.5rem;
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
section.docs .inner, section.blog .inner {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
section.docs aside, section.blog aside {
|
|
||||||
flex: 0 1 30%;
|
|
||||||
}
|
|
||||||
section.docs main, section.blog main {
|
|
||||||
flex: 0 1 70%;
|
|
||||||
}
|
|
||||||
#hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.columns {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.columns.four > article {
|
|
||||||
flex: 0 1 25%;
|
|
||||||
margin-right: 1.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.columns.four > article:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 32 KiB |
@ -1,14 +0,0 @@
|
|||||||
var menu = document.getElementById('menu');
|
|
||||||
menu.addEventListener('click', function() {
|
|
||||||
var nav = document.getElementById('rnav');
|
|
||||||
if (nav.style.height == 'auto') {
|
|
||||||
nav.style.height = '0';
|
|
||||||
}else{
|
|
||||||
nav.style.height = 'auto';
|
|
||||||
}
|
|
||||||
|
|
||||||
// they are same
|
|
||||||
// manv.style.height = 'auto';
|
|
||||||
// manv.setAttribute('style', 'height: auto !important');
|
|
||||||
// manv.style.setProperty( 'height',' auto', 'important');
|
|
||||||
}, false);
|
|
496
static/css/actix.css
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600|Roboto+Mono');
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #156060;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #003B3B;
|
||||||
|
}
|
||||||
|
|
||||||
|
table, td {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Content ====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.actix-content {
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content h2, .actix-content h3 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content hr {
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content figure {
|
||||||
|
margin: 2.5rem auto;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content figure figcaption {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #818a91;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-content figure img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-edit .fa {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-edit .fa:before {
|
||||||
|
font-family: "FontAwesome";
|
||||||
|
display: inline-block;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-edit {
|
||||||
|
text-align: right;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Navbar =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
margin-right: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
width: 52px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-logo { 100% { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.navbar-brand img {
|
||||||
|
animation: spin-logo 30s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggle {
|
||||||
|
padding: .25rem .35rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
|
height: 2.6rem;
|
||||||
|
width: 2.8rem;
|
||||||
|
background: 0 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-light .navbar-toggle {
|
||||||
|
border-color: rgba(0, 0, 0, 0.1);
|
||||||
|
color: rgba(0, 0, 0, 0.9);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset border and change color outline */
|
||||||
|
.navbar-light .navbar-toggle:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler-icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
vertical-align: middle;
|
||||||
|
content: "";
|
||||||
|
background: no-repeat center center;
|
||||||
|
-webkit-background-size: 100% 100%;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav li {
|
||||||
|
line-height: 60px;
|
||||||
|
height: 60px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav li {
|
||||||
|
margin: 1rem 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav li a {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav li.active {
|
||||||
|
margin: -0.5rem 0 -.5rem -1rem;
|
||||||
|
padding: 0.5rem 0 0.5rem 1rem;
|
||||||
|
background: #dceaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav li.active a {
|
||||||
|
color: #156060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav h5 {
|
||||||
|
margin: 2rem 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav h5:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftnav a {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.in .navbar-nav .nav-item {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggleable-md .navbar-nav {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.navbar-toggleable-md .navbar-brand {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Page header =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.actix-pageheader, .jumbotron {
|
||||||
|
background-image: url(/img/jumbotron.jpg);
|
||||||
|
background-size: cover;
|
||||||
|
background-color: #156060;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotron {
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 4rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-jumbotron {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 700px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotron .actix-logo {
|
||||||
|
max-height: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotron .lead {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 2rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-pageheader {
|
||||||
|
padding: 1rem 1rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-pageheader h1 {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-pageheader .lead {
|
||||||
|
color: #DADADA;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.actix-pageheader {
|
||||||
|
padding: 2rem 0rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Home =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.actix-home {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-features h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 2.25rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-features .fa {
|
||||||
|
margin-right: 0.4rem;
|
||||||
|
color: #156060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-features p {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.final-pitch {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Footer =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.actix-footer {
|
||||||
|
padding: 4rem 0;
|
||||||
|
margin-top: 4rem;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-footer-gray {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-footer-social a .fa {
|
||||||
|
font-size: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-footer-social a .fa-github {
|
||||||
|
margin-left: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Misc =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
p.uplink {
|
||||||
|
margin: 0;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Highlight =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: #006a70;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 2rem 0rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-size: 90%;
|
||||||
|
line-height: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show section header on hover */
|
||||||
|
|
||||||
|
h2:hover a:after,
|
||||||
|
h3:hover a:after,
|
||||||
|
h4:hover a:after,
|
||||||
|
h5:hover a:after {
|
||||||
|
content: '\2002\00a7\2002';
|
||||||
|
}
|
||||||
|
|
||||||
|
h2:hover a,
|
||||||
|
h3:hover a,
|
||||||
|
h4:hover a,
|
||||||
|
h5:hover a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:520px) {
|
||||||
|
.jumbotron {
|
||||||
|
padding: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Utilities =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.d-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-content-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ===== Media queries =====
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bootstrap-vertical-nav .navbar .navbar-collapse {
|
||||||
|
padding: 0;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.bootstrap-vertical-nav .navbar ul {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.bootstrap-vertical-nav .navbar ul:not {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.bootstrap-vertical-nav .navbar li {
|
||||||
|
float: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-vertical-nav .navbar-nav .nav-item+.nav-item {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 480px) {
|
||||||
|
.hidden-lg-up {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.navbar-collapse {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 479px) {
|
||||||
|
.hd-lg-down {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.navbar-toggleable-md > .container {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-right: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav li {
|
||||||
|
line-height: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item + .nav-item {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-footer-gray {
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actix-footer-social a .fa-github {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.actix-footer-gray {
|
||||||
|
width: 960px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.actix-footer-gray {
|
||||||
|
width: 1140px;
|
||||||
|
}
|
||||||
|
}
|
2
static/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{font-size:16px;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;line-height:1.5;color:#373a3c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #818a91}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#818a91;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
7
static/css/bootstrap.css
vendored
Normal file
4
static/css/font-awesome.min.css
vendored
Normal file
66
static/css/highlight.css
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/* Error */ .chroma .err { }
|
||||||
|
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
|
||||||
|
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
|
||||||
|
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
|
||||||
|
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
|
||||||
|
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
|
||||||
|
/* Keyword */ .chroma .k { color: #006a70; font-weight: bold }
|
||||||
|
/* KeywordConstant */ .chroma .kc { color: #006a70; font-weight: bold }
|
||||||
|
/* KeywordDeclaration */ .chroma .kd { color: #006a70; font-weight: bold }
|
||||||
|
/* KeywordNamespace */ .chroma .kn { color: #006a70; font-weight: bold }
|
||||||
|
/* KeywordPseudo */ .chroma .kp { color: #006a70 }
|
||||||
|
/* KeywordReserved */ .chroma .kr { color: #006a70; font-weight: bold }
|
||||||
|
/* KeywordType */ .chroma .kt { color: #902000 }
|
||||||
|
/* NameAttribute */ .chroma .na { color: #197a7a }
|
||||||
|
/* NameBuiltin */ .chroma .nb { color: #006a70 }
|
||||||
|
/* NameClass */ .chroma .nc { color: #137ea5; font-weight: bold }
|
||||||
|
/* NameConstant */ .chroma .no { color: #60add5 }
|
||||||
|
/* NameDecorator */ .chroma .nd { color: #555555; font-weight: bold }
|
||||||
|
/* NameEntity */ .chroma .ni { color: #d55537; font-weight: bold }
|
||||||
|
/* NameException */ .chroma .ne { color: #006a70 }
|
||||||
|
/* NameFunction */ .chroma .nf { color: #06287e }
|
||||||
|
/* NameLabel */ .chroma .nl { color: #002070; font-weight: bold }
|
||||||
|
/* NameNamespace */ .chroma .nn { color: #137ea5; font-weight: bold }
|
||||||
|
/* NameTag */ .chroma .nt { color: #062873; font-weight: bold }
|
||||||
|
/* NameVariable */ .chroma .nv { color: #bb60d5 }
|
||||||
|
/* LiteralString */ .chroma .s { color: #197a7a }
|
||||||
|
/* LiteralStringAffix */ .chroma .sa { color: #197a7a }
|
||||||
|
/* LiteralStringBacktick */ .chroma .sb { color: #197a7a }
|
||||||
|
/* LiteralStringChar */ .chroma .sc { color: #197a7a }
|
||||||
|
/* LiteralStringDelimiter */ .chroma .dl { color: #197a7a }
|
||||||
|
/* LiteralStringDoc */ .chroma .sd { color: #197a7a; font-style: italic }
|
||||||
|
/* LiteralStringDouble */ .chroma .s2 { color: #197a7a }
|
||||||
|
/* LiteralStringEscape */ .chroma .se { color: #197a7a; font-weight: bold }
|
||||||
|
/* LiteralStringHeredoc */ .chroma .sh { color: #197a7a }
|
||||||
|
/* LiteralStringInterpol */ .chroma .si { color: #70a0d0; font-style: italic }
|
||||||
|
/* LiteralStringOther */ .chroma .sx { color: #c65d09 }
|
||||||
|
/* LiteralStringRegex */ .chroma .sr { color: #235388 }
|
||||||
|
/* LiteralStringSingle */ .chroma .s1 { color: #197a7a }
|
||||||
|
/* LiteralStringSymbol */ .chroma .ss { color: #517918 }
|
||||||
|
/* LiteralNumber */ .chroma .m { color: #40a070 }
|
||||||
|
/* LiteralNumberBin */ .chroma .mb { color: #40a070 }
|
||||||
|
/* LiteralNumberFloat */ .chroma .mf { color: #40a070 }
|
||||||
|
/* LiteralNumberHex */ .chroma .mh { color: #40a070 }
|
||||||
|
/* LiteralNumberInteger */ .chroma .mi { color: #40a070 }
|
||||||
|
/* LiteralNumberIntegerLong */ .chroma .il { color: #40a070 }
|
||||||
|
/* LiteralNumberOct */ .chroma .mo { color: #40a070 }
|
||||||
|
/* Operator */ .chroma .o { color: #666666 }
|
||||||
|
/* OperatorWord */ .chroma .ow { color: #006a70; font-weight: bold }
|
||||||
|
/* Comment */ .chroma .c { color: #60a0b0; font-style: italic }
|
||||||
|
/* CommentHashbang */ .chroma .ch { color: #60a0b0; font-style: italic }
|
||||||
|
/* CommentMultiline */ .chroma .cm { color: #60a0b0; font-style: italic }
|
||||||
|
/* CommentSingle */ .chroma .c1 { color: #60a0b0; font-style: italic }
|
||||||
|
/* CommentSpecial */ .chroma .cs { color: #60a0b0; background-color: #fff0f0 }
|
||||||
|
/* CommentPreproc */ .chroma .cp { color: #006a70 }
|
||||||
|
/* CommentPreprocFile */ .chroma .cpf { color: #006a70 }
|
||||||
|
/* GenericDeleted */ .chroma .gd { color: #a00000 }
|
||||||
|
/* GenericEmph */ .chroma .ge { font-style: italic }
|
||||||
|
/* GenericError */ .chroma .gr { color: #ff0000 }
|
||||||
|
/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
|
||||||
|
/* GenericInserted */ .chroma .gi { color: #00a000 }
|
||||||
|
/* GenericOutput */ .chroma .go { color: #888888 }
|
||||||
|
/* GenericPrompt */ .chroma .gp { color: #c65d09; font-weight: bold }
|
||||||
|
/* GenericStrong */ .chroma .gs { font-weight: bold }
|
||||||
|
/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
|
||||||
|
/* GenericTraceback */ .chroma .gt { color: #0044dd }
|
||||||
|
/* TextWhitespace */ .chroma .w { color: #bbbbbb }
|
BIN
static/fonts/FontAwesome.otf
Normal file
BIN
static/fonts/fontawesome-webfont.eot
Normal file
2671
static/fonts/fontawesome-webfont.svg
Normal file
After Width: | Height: | Size: 434 KiB |
BIN
static/fonts/fontawesome-webfont.ttf
Normal file
BIN
static/fonts/fontawesome-webfont.woff
Normal file
BIN
static/fonts/fontawesome-webfont.woff2
Normal file
53
static/img/diagrams/cheatsheet-for-futures.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Cheatsheet for Futures</title>
|
||||||
|
</head>
|
||||||
|
<body><pre style="margin: 0"><code style="background: transparent"><span style="color: #8e908c">// Constructing leaf futures</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.empty.html">empty</a> () -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.ok.html">ok</a> (<var>T</var>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.err.html">err</a> (<var>E</var>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.result.html">result</a>(<span style="color: #4271ae">Result</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// General future constructor</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.poll_fn.html">poll_fn</a>(<span style="color: #8959a8">FnMut</span>(<span style="color: #3e999f">thread_local!</span>(<span style="color: #4271ae">Task</span>)) -> <span style="color: #4271ae">Poll</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Mapping futures</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.map">Future::map</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #8959a8">FnOnce</span>(<var>T</var>) -> <var>U</var>) -> <span style="color: #c82829">Future</span><<var>U</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.map_err">Future::map_err</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #8959a8">FnOnce</span>(<var>E</var>) -> <var>F</var>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>F</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.from_err">Future::from_err</a>(<span style="color: #c82829">Future</span><<var>T</var>, <span style="color: #4271ae">Into</span><<var>E</var>>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Chaining (sequencing) futures</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.then">Future::then</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #8959a8">FnOnce</span>(<span style="color: #4271ae">Result</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>F</var>>) -> <span style="color: #c82829">Future</span><<var>U</var>, <var>F</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.and_then">Future::and_then</a>(<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #8959a8">FnOnce</span>(<var>T</var>) -> <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>U</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.or_else">Future::or_else</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #8959a8">FnOnce</span>(<var>E</var>) -> <span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>F</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>F</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.flatten">Future::flatten</a> (<span style="color: #c82829">Future</span><<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #4271ae">Into</span><<var>E</var>>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Joining (waiting) futures</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.join">Future::join</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <var>U</var>), <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.join3">Future::join3</a>(<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>V</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <var>U</var>, <var>V</var>), <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.join4">Future::join4</a>(<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>V</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>W</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <var>U</var>, <var>V</var>, <var>W</var>), <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.join5">Future::join5</a>(<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>V</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>W</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>X</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <var>U</var>, <var>V</var>, <var>W</var>, <var>X</var>), <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.join_all.html">join_all</a> (<span style="color: #4271ae">IntoIterator</span><<span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>E</var>>>) -> <span style="color: #c82829">Future</span><<span style="color: #4271ae">Vec</span><<var>T</var>>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Selecting (racing) futures</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.select">Future::select</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>), (<var>E</var>, <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>)>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.select2">Future::select2</a>(<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>, <span style="color: #c82829">IntoFuture</span><<var>U</var>, <var>F</var>>) -> <span style="color: #c82829">Future</span><<span style="color: #4271ae">Either</span><(<var>T</var>, <span style="color: #c82829">Future</span><<var>U</var>, <var>F</var>>), (<var>U</var>, <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>)>, <span style="color: #4271ae">Either</span><(<var>E</var>, <span style="color: #c82829">Future</span><<var>U</var>, <var>F</var>>), (<var>F</var>, <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>)>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.select_all.html">select_all</a> (<span style="color: #4271ae">IntoIterator</span><<span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>E</var>>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <span style="color: #4271ae">usize</span>, <span style="color: #4271ae">Vec</span><<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>>), (<var>E</var>, <span style="color: #4271ae">usize</span>, <span style="color: #4271ae">Vec</span><<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>>)>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.select_ok.html">select_ok</a> (<span style="color: #4271ae">IntoIterator</span><<span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>E</var>>>) -> <span style="color: #c82829">Future</span><(<var>T</var>, <span style="color: #4271ae">Vec</span><<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>>), <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Utility</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.lazy.html">lazy</a> (<span style="color: #8959a8">FnOnce</span>() -> <span style="color: #c82829">IntoFuture</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/fn.loop_fn.html">loop_fn</a> (<var>S</var>, <span style="color: #8959a8">FnMut</span>(<var>S</var>) -> <span style="color: #c82829">IntoFuture</span><<span style="color: #4271ae">Loop</span><<var>T</var>, <var>S</var>>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
|
||||||
|
<span style="color: #8e908c">// Miscellaneous</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.into_stream">Future::into_stream</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>) -> <span style="color: #4271ae">Stream</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.flatten_stream">Future::flatten_stream</a>(<span style="color: #c82829">Future</span><<span style="color: #4271ae">Stream</span><<var>T</var>, <var>E</var>>, <var>E</var>>) -> <span style="color: #4271ae">Stream</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.fuse">Future::fuse</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.catch_unwind">Future::catch_unwind</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>+<span style="color: #4271ae">UnwindSafe</span>) -> <span style="color: #c82829">Future</span><<span style="color: #4271ae">Result</span><<var>T</var>, <var>E</var>>, <span style="color: #4271ae">Any</span>+<span style="color: #4271ae">Send</span>>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.shared">Future::shared</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>) -> <span style="color: #c82829">Future</span><<span style="color: #4271ae">SharedItem</span><<var>T</var>>, <span style="color: #4271ae">SharedError</span><<var>E</var>>>+<span style="color: #4271ae">Clone</span>
|
||||||
|
<span style="color: #8959a8">fn</span> <a href="https://docs.rs/futures/0.1.13/futures/future/trait.Future.html#method.wait">Future::wait</a> (<span style="color: #c82829">Future</span><<var>T</var>, <var>E</var>>) -> <span style="color: #4271ae">Result</span><<var>T</var>, <var>E</var>>
|
||||||
|
</code></pre></body>
|
||||||
|
</html>
|
BIN
static/img/diagrams/multiplexing.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
static/img/diagrams/task-layout.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/img/jumbotron.jpg
Normal file
After Width: | Height: | Size: 240 KiB |
BIN
static/img/logo-large.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
static/img/logo-nav.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
static/img/logo.png
Normal file
After Width: | Height: | Size: 39 KiB |
7
static/js/bootstrap.js
vendored
Normal file
7
static/js/bootstrap.min.js
vendored
Normal file
2
static/js/highlight.js
Normal file
16
tutorials.md
@ -1,16 +0,0 @@
|
|||||||
title: Tutorials
|
|
||||||
layout: default.liquid
|
|
||||||
permalink: /tutorials.html
|
|
||||||
---
|
|
||||||
<section >
|
|
||||||
<div >
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
The words is comming.
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|