├── tests ├── test.png ├── test.binary ├── cert.pem └── key.pem ├── rustfmt.toml ├── .gitignore ├── Makefile ├── src ├── server │ ├── ssl │ │ ├── mod.rs │ │ ├── nativetls.rs │ │ ├── rustls.rs │ │ └── openssl.rs │ ├── error.rs │ ├── helpers.rs │ └── message.rs ├── header │ ├── shared │ │ ├── mod.rs │ │ ├── encoding.rs │ │ ├── httpdate.rs │ │ └── charset.rs │ └── common │ │ ├── date.rs │ │ ├── expires.rs │ │ ├── last_modified.rs │ │ ├── if_modified_since.rs │ │ ├── if_unmodified_since.rs │ │ ├── content_language.rs │ │ ├── accept_charset.rs │ │ ├── accept_encoding.rs │ │ ├── accept_language.rs │ │ ├── allow.rs │ │ ├── if_match.rs │ │ ├── if_none_match.rs │ │ ├── etag.rs │ │ ├── if_range.rs │ │ ├── content_type.rs │ │ ├── accept.rs │ │ ├── content_range.rs │ │ └── cache_control.rs ├── serde_urlencoded │ ├── ser │ │ ├── value.rs │ │ ├── key.rs │ │ ├── part.rs │ │ └── pair.rs │ └── mod.rs ├── middleware │ ├── mod.rs │ ├── defaultheaders.rs │ └── errhandlers.rs ├── extensions.rs ├── httpcodes.rs ├── client │ ├── response.rs │ ├── mod.rs │ └── parser.rs ├── uri.rs ├── ws │ └── mask.rs ├── info.rs ├── with.rs ├── context.rs └── lib.rs ├── LICENSE-MIT ├── .appveyor.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── README.md └── MIGRATION.md /tests/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikdesjardins/actix-web/master/tests/test.png -------------------------------------------------------------------------------- /tests/test.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikdesjardins/actix-web/master/tests/test.binary -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 89 2 | reorder_imports = true 3 | #wrap_comments = true 4 | fn_args_density = "Compressed" 5 | #use_small_heuristics = false 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | guide/build/ 4 | /gh-pages 5 | 6 | *.so 7 | *.out 8 | *.pyc 9 | *.pid 10 | *.sock 11 | *~ 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default build test doc book clean 2 | 3 | CARGO_FLAGS := --features "$(FEATURES) alpn tls" 4 | 5 | default: test 6 | 7 | build: 8 | cargo build $(CARGO_FLAGS) 9 | 10 | test: build clippy 11 | cargo test $(CARGO_FLAGS) 12 | 13 | doc: build 14 | cargo doc --no-deps $(CARGO_FLAGS) 15 | -------------------------------------------------------------------------------- /src/server/ssl/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "alpn")] 2 | mod openssl; 3 | #[cfg(feature = "alpn")] 4 | pub use self::openssl::OpensslAcceptor; 5 | 6 | #[cfg(feature = "tls")] 7 | mod nativetls; 8 | #[cfg(feature = "tls")] 9 | pub use self::nativetls::NativeTlsAcceptor; 10 | 11 | #[cfg(feature = "rust-tls")] 12 | mod rustls; 13 | #[cfg(feature = "rust-tls")] 14 | pub use self::rustls::RustlsAcceptor; 15 | -------------------------------------------------------------------------------- /src/header/shared/mod.rs: -------------------------------------------------------------------------------- 1 | //! Copied for `hyper::header::shared`; 2 | 3 | pub use self::charset::Charset; 4 | pub use self::encoding::Encoding; 5 | pub use self::entity::EntityTag; 6 | pub use self::httpdate::HttpDate; 7 | pub use self::quality_item::{q, qitem, Quality, QualityItem}; 8 | pub use language_tags::LanguageTag; 9 | 10 | mod charset; 11 | mod encoding; 12 | mod entity; 13 | mod httpdate; 14 | mod quality_item; 15 | -------------------------------------------------------------------------------- /src/server/error.rs: -------------------------------------------------------------------------------- 1 | use futures::{Async, Poll}; 2 | 3 | use super::{helpers, HttpHandlerTask, Writer}; 4 | use http::{StatusCode, Version}; 5 | use Error; 6 | 7 | pub(crate) struct ServerError(Version, StatusCode); 8 | 9 | impl ServerError { 10 | pub fn err(ver: Version, status: StatusCode) -> Box { 11 | Box::new(ServerError(ver, status)) 12 | } 13 | } 14 | 15 | impl HttpHandlerTask for ServerError { 16 | fn poll_io(&mut self, io: &mut Writer) -> Poll { 17 | { 18 | let bytes = io.buffer(); 19 | helpers::write_status_line(self.0, self.1.as_u16(), bytes); 20 | } 21 | io.set_date(); 22 | Ok(Async::Ready(true)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nikolay Kim 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/header/common/date.rs: -------------------------------------------------------------------------------- 1 | use header::{HttpDate, DATE}; 2 | use std::time::SystemTime; 3 | 4 | header! { 5 | /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) 6 | /// 7 | /// The `Date` header field represents the date and time at which the 8 | /// message was originated. 9 | /// 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Date = HTTP-date 14 | /// ``` 15 | /// 16 | /// # Example values 17 | /// 18 | /// * `Tue, 15 Nov 1994 08:12:31 GMT` 19 | /// 20 | /// # Example 21 | /// 22 | /// ```rust 23 | /// use actix_web::HttpResponse; 24 | /// use actix_web::http::header::Date; 25 | /// use std::time::SystemTime; 26 | /// 27 | /// let mut builder = HttpResponse::Ok(); 28 | /// builder.set(Date(SystemTime::now().into())); 29 | /// ``` 30 | (Date, DATE) => [HttpDate] 31 | 32 | test_date { 33 | test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); 34 | } 35 | } 36 | 37 | impl Date { 38 | /// Create a date instance set to the current system time 39 | pub fn now() -> Date { 40 | Date(SystemTime::now().into()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/header/common/expires.rs: -------------------------------------------------------------------------------- 1 | use header::{HttpDate, EXPIRES}; 2 | 3 | header! { 4 | /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) 5 | /// 6 | /// The `Expires` header field gives the date/time after which the 7 | /// response is considered stale. 8 | /// 9 | /// The presence of an Expires field does not imply that the original 10 | /// resource will change or cease to exist at, before, or after that 11 | /// time. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Expires = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `Thu, 01 Dec 1994 16:00:00 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::Expires; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut builder = HttpResponse::Ok(); 30 | /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); 31 | /// builder.set(Expires(expiration.into())); 32 | /// ``` 33 | (Expires, EXPIRES) => [HttpDate] 34 | 35 | test_expires { 36 | // Test case from RFC 37 | test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/header/common/last_modified.rs: -------------------------------------------------------------------------------- 1 | use header::{HttpDate, LAST_MODIFIED}; 2 | 3 | header! { 4 | /// `Last-Modified` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) 6 | /// 7 | /// The `Last-Modified` header field in a response provides a timestamp 8 | /// indicating the date and time at which the origin server believes the 9 | /// selected representation was last modified, as determined at the 10 | /// conclusion of handling the request. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Expires = HTTP-date 16 | /// ``` 17 | /// 18 | /// # Example values 19 | /// 20 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::LastModified; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut builder = HttpResponse::Ok(); 30 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 31 | /// builder.set(LastModified(modified.into())); 32 | /// ``` 33 | (LastModified, LAST_MODIFIED) => [HttpDate] 34 | 35 | test_last_modified { 36 | // Test case from RFC 37 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} 38 | } 39 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | PROJECT_NAME: actix 4 | matrix: 5 | # Stable channel 6 | - TARGET: i686-pc-windows-msvc 7 | CHANNEL: stable 8 | - TARGET: x86_64-pc-windows-gnu 9 | CHANNEL: stable 10 | - TARGET: x86_64-pc-windows-msvc 11 | CHANNEL: stable 12 | # Nightly channel 13 | - TARGET: i686-pc-windows-msvc 14 | CHANNEL: nightly 15 | - TARGET: x86_64-pc-windows-gnu 16 | CHANNEL: nightly 17 | - TARGET: x86_64-pc-windows-msvc 18 | CHANNEL: nightly 19 | 20 | # Install Rust and Cargo 21 | # (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) 22 | install: 23 | - ps: >- 24 | If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') { 25 | $Env:PATH += ';C:\msys64\mingw64\bin' 26 | } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') { 27 | $Env:PATH += ';C:\MinGW\bin' 28 | } 29 | - curl -sSf -o rustup-init.exe https://win.rustup.rs 30 | - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y 31 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 32 | - rustc -Vv 33 | - cargo -V 34 | 35 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. 36 | build: false 37 | 38 | # Equivalent to Travis' `script` phase 39 | test_script: 40 | - cargo test --no-default-features --features="flate2-rust" 41 | -------------------------------------------------------------------------------- /src/header/common/if_modified_since.rs: -------------------------------------------------------------------------------- 1 | use header::{HttpDate, IF_MODIFIED_SINCE}; 2 | 3 | header! { 4 | /// `If-Modified-Since` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) 6 | /// 7 | /// The `If-Modified-Since` header field makes a GET or HEAD request 8 | /// method conditional on the selected representation's modification date 9 | /// being more recent than the date provided in the field-value. 10 | /// Transfer of the selected representation's data is avoided if that 11 | /// data has not changed. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// If-Unmodified-Since = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::IfModifiedSince; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut builder = HttpResponse::Ok(); 30 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 31 | /// builder.set(IfModifiedSince(modified.into())); 32 | /// ``` 33 | (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] 34 | 35 | test_if_modified_since { 36 | // Test case from RFC 37 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/header/common/if_unmodified_since.rs: -------------------------------------------------------------------------------- 1 | use header::{HttpDate, IF_UNMODIFIED_SINCE}; 2 | 3 | header! { 4 | /// `If-Unmodified-Since` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) 6 | /// 7 | /// The `If-Unmodified-Since` header field makes the request method 8 | /// conditional on the selected representation's last modification date 9 | /// being earlier than or equal to the date provided in the field-value. 10 | /// This field accomplishes the same purpose as If-Match for cases where 11 | /// the user agent does not have an entity-tag for the representation. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// If-Unmodified-Since = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 22 | /// 23 | /// # Example 24 | /// 25 | /// ```rust 26 | /// use actix_web::HttpResponse; 27 | /// use actix_web::http::header::IfUnmodifiedSince; 28 | /// use std::time::{SystemTime, Duration}; 29 | /// 30 | /// let mut builder = HttpResponse::Ok(); 31 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 32 | /// builder.set(IfUnmodifiedSince(modified.into())); 33 | /// ``` 34 | (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] 35 | 36 | test_if_unmodified_since { 37 | // Test case from RFC 38 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/serde_urlencoded/ser/value.rs: -------------------------------------------------------------------------------- 1 | use super::super::ser::part::{PartSerializer, Sink}; 2 | use super::super::ser::Error; 3 | use serde::ser::Serialize; 4 | use std::str; 5 | use url::form_urlencoded::Serializer as UrlEncodedSerializer; 6 | use url::form_urlencoded::Target as UrlEncodedTarget; 7 | 8 | pub struct ValueSink<'key, 'target, Target> 9 | where 10 | Target: 'target + UrlEncodedTarget, 11 | { 12 | urlencoder: &'target mut UrlEncodedSerializer, 13 | key: &'key str, 14 | } 15 | 16 | impl<'key, 'target, Target> ValueSink<'key, 'target, Target> 17 | where 18 | Target: 'target + UrlEncodedTarget, 19 | { 20 | pub fn new( 21 | urlencoder: &'target mut UrlEncodedSerializer, key: &'key str, 22 | ) -> Self { 23 | ValueSink { urlencoder, key } 24 | } 25 | } 26 | 27 | impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target> 28 | where 29 | Target: 'target + UrlEncodedTarget, 30 | { 31 | type Ok = (); 32 | 33 | fn serialize_str(self, value: &str) -> Result<(), Error> { 34 | self.urlencoder.append_pair(self.key, value); 35 | Ok(()) 36 | } 37 | 38 | fn serialize_static_str(self, value: &'static str) -> Result<(), Error> { 39 | self.serialize_str(value) 40 | } 41 | 42 | fn serialize_string(self, value: String) -> Result<(), Error> { 43 | self.serialize_str(&value) 44 | } 45 | 46 | fn serialize_none(self) -> Result { 47 | Ok(()) 48 | } 49 | 50 | fn serialize_some( 51 | self, value: &T, 52 | ) -> Result { 53 | value.serialize(PartSerializer::new(self)) 54 | } 55 | 56 | fn unsupported(self) -> Error { 57 | Error::Custom("unsupported value".into()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: required 3 | dist: trusty 4 | 5 | cache: 6 | cargo: true 7 | apt: true 8 | 9 | matrix: 10 | include: 11 | - rust: stable 12 | - rust: beta 13 | - rust: nightly 14 | allow_failures: 15 | - rust: nightly 16 | 17 | env: 18 | global: 19 | # - RUSTFLAGS="-C link-dead-code" 20 | - OPENSSL_VERSION=openssl-1.0.2 21 | 22 | before_install: 23 | - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl 24 | - sudo apt-get update -qq 25 | - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev 26 | 27 | # Add clippy 28 | before_script: 29 | - export PATH=$PATH:~/.cargo/bin 30 | 31 | script: 32 | - | 33 | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then 34 | cargo clean 35 | cargo test --features="alpn,tls,rust-tls" -- --nocapture 36 | fi 37 | - | 38 | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then 39 | RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin 40 | cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count 41 | bash <(curl -s https://codecov.io/bash) 42 | echo "Uploaded code coverage" 43 | fi 44 | 45 | # Upload docs 46 | after_success: 47 | - | 48 | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then 49 | cargo doc --features "alpn, tls, rust-tls, session" --no-deps && 50 | echo "" > target/doc/index.html && 51 | git clone https://github.com/davisp/ghp-import.git && 52 | ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && 53 | echo "Uploaded documentation" 54 | fi 55 | -------------------------------------------------------------------------------- /src/header/shared/encoding.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str; 3 | 4 | pub use self::Encoding::{ 5 | Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, 6 | }; 7 | 8 | /// A value to represent an encoding used in `Transfer-Encoding` 9 | /// or `Accept-Encoding` header. 10 | #[derive(Clone, PartialEq, Debug)] 11 | pub enum Encoding { 12 | /// The `chunked` encoding. 13 | Chunked, 14 | /// The `br` encoding. 15 | Brotli, 16 | /// The `gzip` encoding. 17 | Gzip, 18 | /// The `deflate` encoding. 19 | Deflate, 20 | /// The `compress` encoding. 21 | Compress, 22 | /// The `identity` encoding. 23 | Identity, 24 | /// The `trailers` encoding. 25 | Trailers, 26 | /// Some other encoding that is less common, can be any String. 27 | EncodingExt(String), 28 | } 29 | 30 | impl fmt::Display for Encoding { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | f.write_str(match *self { 33 | Chunked => "chunked", 34 | Brotli => "br", 35 | Gzip => "gzip", 36 | Deflate => "deflate", 37 | Compress => "compress", 38 | Identity => "identity", 39 | Trailers => "trailers", 40 | EncodingExt(ref s) => s.as_ref(), 41 | }) 42 | } 43 | } 44 | 45 | impl str::FromStr for Encoding { 46 | type Err = ::error::ParseError; 47 | fn from_str(s: &str) -> Result { 48 | match s { 49 | "chunked" => Ok(Chunked), 50 | "br" => Ok(Brotli), 51 | "deflate" => Ok(Deflate), 52 | "gzip" => Ok(Gzip), 53 | "compress" => Ok(Compress), 54 | "identity" => Ok(Identity), 55 | "trailers" => Ok(Trailers), 56 | _ => Ok(EncodingExt(s.to_owned())), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/server/ssl/nativetls.rs: -------------------------------------------------------------------------------- 1 | use std::net::Shutdown; 2 | use std::{io, time}; 3 | 4 | use futures::{Future, Poll}; 5 | use native_tls::TlsAcceptor; 6 | use tokio_tls::{AcceptAsync, TlsAcceptorExt, TlsStream}; 7 | 8 | use server::{AcceptorService, IoStream}; 9 | 10 | #[derive(Clone)] 11 | /// Support `SSL` connections via native-tls package 12 | /// 13 | /// `tls` feature enables `NativeTlsAcceptor` type 14 | pub struct NativeTlsAcceptor { 15 | acceptor: TlsAcceptor, 16 | } 17 | 18 | impl NativeTlsAcceptor { 19 | /// Create `NativeTlsAcceptor` instance 20 | pub fn new(acceptor: TlsAcceptor) -> Self { 21 | NativeTlsAcceptor { acceptor } 22 | } 23 | } 24 | 25 | pub struct AcceptorFut(AcceptAsync); 26 | 27 | impl Future for AcceptorFut { 28 | type Item = TlsStream; 29 | type Error = io::Error; 30 | 31 | fn poll(&mut self) -> Poll { 32 | self.0 33 | .poll() 34 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 35 | } 36 | } 37 | 38 | impl AcceptorService for NativeTlsAcceptor { 39 | type Accepted = TlsStream; 40 | type Future = AcceptorFut; 41 | 42 | fn scheme(&self) -> &'static str { 43 | "https" 44 | } 45 | 46 | fn accept(&self, io: Io) -> Self::Future { 47 | AcceptorFut(TlsAcceptorExt::accept_async(&self.acceptor, io)) 48 | } 49 | } 50 | 51 | impl IoStream for TlsStream { 52 | #[inline] 53 | fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { 54 | let _ = self.get_mut().shutdown(); 55 | Ok(()) 56 | } 57 | 58 | #[inline] 59 | fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { 60 | self.get_mut().get_mut().set_nodelay(nodelay) 61 | } 62 | 63 | #[inline] 64 | fn set_linger(&mut self, dur: Option) -> io::Result<()> { 65 | self.get_mut().get_mut().set_linger(dur) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/serde_urlencoded/ser/key.rs: -------------------------------------------------------------------------------- 1 | use super::super::ser::part::Sink; 2 | use super::super::ser::Error; 3 | use serde::Serialize; 4 | use std::borrow::Cow; 5 | use std::ops::Deref; 6 | 7 | pub enum Key<'key> { 8 | Static(&'static str), 9 | Dynamic(Cow<'key, str>), 10 | } 11 | 12 | impl<'key> Deref for Key<'key> { 13 | type Target = str; 14 | 15 | fn deref(&self) -> &str { 16 | match *self { 17 | Key::Static(key) => key, 18 | Key::Dynamic(ref key) => key, 19 | } 20 | } 21 | } 22 | 23 | impl<'key> From> for Cow<'static, str> { 24 | fn from(key: Key<'key>) -> Self { 25 | match key { 26 | Key::Static(key) => key.into(), 27 | Key::Dynamic(key) => key.into_owned().into(), 28 | } 29 | } 30 | } 31 | 32 | pub struct KeySink { 33 | end: End, 34 | } 35 | 36 | impl KeySink 37 | where 38 | End: for<'key> FnOnce(Key<'key>) -> Result, 39 | { 40 | pub fn new(end: End) -> Self { 41 | KeySink { end } 42 | } 43 | } 44 | 45 | impl Sink for KeySink 46 | where 47 | End: for<'key> FnOnce(Key<'key>) -> Result, 48 | { 49 | type Ok = Ok; 50 | 51 | fn serialize_static_str(self, value: &'static str) -> Result { 52 | (self.end)(Key::Static(value)) 53 | } 54 | 55 | fn serialize_str(self, value: &str) -> Result { 56 | (self.end)(Key::Dynamic(value.into())) 57 | } 58 | 59 | fn serialize_string(self, value: String) -> Result { 60 | (self.end)(Key::Dynamic(value.into())) 61 | } 62 | 63 | fn serialize_none(self) -> Result { 64 | Err(self.unsupported()) 65 | } 66 | 67 | fn serialize_some(self, _value: &T) -> Result { 68 | Err(self.unsupported()) 69 | } 70 | 71 | fn unsupported(self) -> Error { 72 | Error::Custom("unsupported key".into()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh 4 | bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4 5 | MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV 6 | BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD 7 | T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA 8 | A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF 9 | cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk 10 | L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ 11 | EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU 12 | 05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh 13 | 4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA 14 | 2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng 15 | dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4 16 | e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT 17 | 2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa 18 | TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID 19 | AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB 20 | AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm 21 | ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g 22 | 4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1 23 | hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe 24 | 0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq 25 | seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi 26 | 7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO 27 | 3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5 28 | XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq 29 | GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr 30 | E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /src/header/common/content_language.rs: -------------------------------------------------------------------------------- 1 | use header::{QualityItem, CONTENT_LANGUAGE}; 2 | use language_tags::LanguageTag; 3 | 4 | header! { 5 | /// `Content-Language` header, defined in 6 | /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) 7 | /// 8 | /// The `Content-Language` header field describes the natural language(s) 9 | /// of the intended audience for the representation. Note that this 10 | /// might not be equivalent to all the languages used within the 11 | /// representation. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Content-Language = 1#language-tag 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `da` 22 | /// * `mi, en` 23 | /// 24 | /// # Examples 25 | /// 26 | /// ```rust 27 | /// # extern crate actix_web; 28 | /// # #[macro_use] extern crate language_tags; 29 | /// use actix_web::HttpResponse; 30 | /// # use actix_web::http::header::{ContentLanguage, qitem}; 31 | /// # 32 | /// # fn main() { 33 | /// let mut builder = HttpResponse::Ok(); 34 | /// builder.set( 35 | /// ContentLanguage(vec![ 36 | /// qitem(langtag!(en)), 37 | /// ]) 38 | /// ); 39 | /// # } 40 | /// ``` 41 | /// 42 | /// ```rust 43 | /// # extern crate actix_web; 44 | /// # #[macro_use] extern crate language_tags; 45 | /// use actix_web::HttpResponse; 46 | /// # use actix_web::http::header::{ContentLanguage, qitem}; 47 | /// # 48 | /// # fn main() { 49 | /// 50 | /// let mut builder = HttpResponse::Ok(); 51 | /// builder.set( 52 | /// ContentLanguage(vec![ 53 | /// qitem(langtag!(da)), 54 | /// qitem(langtag!(en;;;GB)), 55 | /// ]) 56 | /// ); 57 | /// # } 58 | /// ``` 59 | (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ 60 | 61 | test_content_language { 62 | test_header!(test1, vec![b"da"]); 63 | test_header!(test2, vec![b"mi, en"]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | //! Middlewares 2 | use futures::Future; 3 | 4 | use error::{Error, Result}; 5 | use httprequest::HttpRequest; 6 | use httpresponse::HttpResponse; 7 | 8 | mod logger; 9 | 10 | pub mod cors; 11 | pub mod csrf; 12 | mod defaultheaders; 13 | mod errhandlers; 14 | #[cfg(feature = "session")] 15 | pub mod identity; 16 | #[cfg(feature = "session")] 17 | pub mod session; 18 | pub use self::defaultheaders::DefaultHeaders; 19 | pub use self::errhandlers::ErrorHandlers; 20 | pub use self::logger::Logger; 21 | 22 | /// Middleware start result 23 | pub enum Started { 24 | /// Middleware is completed, continue to next middleware 25 | Done, 26 | /// New http response got generated. If middleware generates response 27 | /// handler execution halts. 28 | Response(HttpResponse), 29 | /// Execution completed, runs future to completion. 30 | Future(Box, Error = Error>>), 31 | } 32 | 33 | /// Middleware execution result 34 | pub enum Response { 35 | /// New http response got generated 36 | Done(HttpResponse), 37 | /// Result is a future that resolves to a new http response 38 | Future(Box>), 39 | } 40 | 41 | /// Middleware finish result 42 | pub enum Finished { 43 | /// Execution completed 44 | Done, 45 | /// Execution completed, but run future to completion 46 | Future(Box>), 47 | } 48 | 49 | /// Middleware definition 50 | #[allow(unused_variables)] 51 | pub trait Middleware: 'static { 52 | /// Method is called when request is ready. It may return 53 | /// future, which should resolve before next middleware get called. 54 | fn start(&self, req: &HttpRequest) -> Result { 55 | Ok(Started::Done) 56 | } 57 | 58 | /// Method is called when handler returns response, 59 | /// but before sending http message to peer. 60 | fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { 61 | Ok(Response::Done(resp)) 62 | } 63 | 64 | /// Method is called after body stream get sent to peer. 65 | fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { 66 | Finished::Done 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/header/common/accept_charset.rs: -------------------------------------------------------------------------------- 1 | use header::{Charset, QualityItem, ACCEPT_CHARSET}; 2 | 3 | header! { 4 | /// `Accept-Charset` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) 6 | /// 7 | /// The `Accept-Charset` header field can be sent by a user agent to 8 | /// indicate what charsets are acceptable in textual response content. 9 | /// This field allows user agents capable of understanding more 10 | /// comprehensive or special-purpose charsets to signal that capability 11 | /// to an origin server that is capable of representing information in 12 | /// those charsets. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `iso-8859-5, unicode-1-1;q=0.8` 22 | /// 23 | /// # Examples 24 | /// ```rust 25 | /// # extern crate actix_web; 26 | /// use actix_web::HttpResponse; 27 | /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; 28 | /// 29 | /// # fn main() { 30 | /// let mut builder = HttpResponse::Ok(); 31 | /// builder.set( 32 | /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) 33 | /// ); 34 | /// # } 35 | /// ``` 36 | /// ```rust 37 | /// # extern crate actix_web; 38 | /// use actix_web::HttpResponse; 39 | /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; 40 | /// 41 | /// # fn main() { 42 | /// let mut builder = HttpResponse::Ok(); 43 | /// builder.set( 44 | /// AcceptCharset(vec![ 45 | /// QualityItem::new(Charset::Us_Ascii, q(900)), 46 | /// QualityItem::new(Charset::Iso_8859_10, q(200)), 47 | /// ]) 48 | /// ); 49 | /// # } 50 | /// ``` 51 | /// ```rust 52 | /// # extern crate actix_web; 53 | /// use actix_web::HttpResponse; 54 | /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; 55 | /// 56 | /// # fn main() { 57 | /// let mut builder = HttpResponse::Ok(); 58 | /// builder.set( 59 | /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) 60 | /// ); 61 | /// # } 62 | /// ``` 63 | (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ 64 | 65 | test_accept_charset { 66 | /// Test case from RFC 67 | test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/header/common/accept_encoding.rs: -------------------------------------------------------------------------------- 1 | use header::{Encoding, QualityItem}; 2 | 3 | header! { 4 | /// `Accept-Encoding` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) 6 | /// 7 | /// The `Accept-Encoding` header field can be used by user agents to 8 | /// indicate what response content-codings are 9 | /// acceptable in the response. An `identity` token is used as a synonym 10 | /// for "no encoding" in order to communicate when no encoding is 11 | /// preferred. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Accept-Encoding = #( codings [ weight ] ) 17 | /// codings = content-coding / "identity" / "*" 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `compress, gzip` 22 | /// * `` 23 | /// * `*` 24 | /// * `compress;q=0.5, gzip;q=1` 25 | /// * `gzip;q=1.0, identity; q=0.5, *;q=0` 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; 30 | /// 31 | /// let mut headers = Headers::new(); 32 | /// headers.set( 33 | /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) 34 | /// ); 35 | /// ``` 36 | /// ``` 37 | /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; 38 | /// 39 | /// let mut headers = Headers::new(); 40 | /// headers.set( 41 | /// AcceptEncoding(vec![ 42 | /// qitem(Encoding::Chunked), 43 | /// qitem(Encoding::Gzip), 44 | /// qitem(Encoding::Deflate), 45 | /// ]) 46 | /// ); 47 | /// ``` 48 | /// ``` 49 | /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; 50 | /// 51 | /// let mut headers = Headers::new(); 52 | /// headers.set( 53 | /// AcceptEncoding(vec![ 54 | /// qitem(Encoding::Chunked), 55 | /// QualityItem::new(Encoding::Gzip, q(600)), 56 | /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), 57 | /// ]) 58 | /// ); 59 | /// ``` 60 | (AcceptEncoding, "Accept-Encoding") => (QualityItem)* 61 | 62 | test_accept_encoding { 63 | // From the RFC 64 | test_header!(test1, vec![b"compress, gzip"]); 65 | test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); 66 | test_header!(test3, vec![b"*"]); 67 | // Note: Removed quality 1 from gzip 68 | test_header!(test4, vec![b"compress;q=0.5, gzip"]); 69 | // Note: Removed quality 1 from gzip 70 | test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/header/common/accept_language.rs: -------------------------------------------------------------------------------- 1 | use header::{QualityItem, ACCEPT_LANGUAGE}; 2 | use language_tags::LanguageTag; 3 | 4 | header! { 5 | /// `Accept-Language` header, defined in 6 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) 7 | /// 8 | /// The `Accept-Language` header field can be used by user agents to 9 | /// indicate the set of natural languages that are preferred in the 10 | /// response. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Accept-Language = 1#( language-range [ weight ] ) 16 | /// language-range = 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `da, en-gb;q=0.8, en;q=0.7` 21 | /// * `en-us;q=1.0, en;q=0.5, fr` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ```rust 26 | /// # extern crate actix_web; 27 | /// # extern crate language_tags; 28 | /// use actix_web::HttpResponse; 29 | /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; 30 | /// 31 | /// # fn main() { 32 | /// let mut builder = HttpResponse::Ok(); 33 | /// let mut langtag: LanguageTag = Default::default(); 34 | /// langtag.language = Some("en".to_owned()); 35 | /// langtag.region = Some("US".to_owned()); 36 | /// builder.set( 37 | /// AcceptLanguage(vec![ 38 | /// qitem(langtag), 39 | /// ]) 40 | /// ); 41 | /// # } 42 | /// ``` 43 | /// 44 | /// ```rust 45 | /// # extern crate actix_web; 46 | /// # #[macro_use] extern crate language_tags; 47 | /// use actix_web::HttpResponse; 48 | /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; 49 | /// # 50 | /// # fn main() { 51 | /// let mut builder = HttpResponse::Ok(); 52 | /// builder.set( 53 | /// AcceptLanguage(vec![ 54 | /// qitem(langtag!(da)), 55 | /// QualityItem::new(langtag!(en;;;GB), q(800)), 56 | /// QualityItem::new(langtag!(en), q(700)), 57 | /// ]) 58 | /// ); 59 | /// # } 60 | /// ``` 61 | (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ 62 | 63 | test_accept_language { 64 | // From the RFC 65 | test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); 66 | // Own test 67 | test_header!( 68 | test2, vec![b"en-US, en; q=0.5, fr"], 69 | Some(AcceptLanguage(vec![ 70 | qitem("en-US".parse().unwrap()), 71 | QualityItem::new("en".parse().unwrap(), q(500)), 72 | qitem("fr".parse().unwrap()), 73 | ]))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/header/common/allow.rs: -------------------------------------------------------------------------------- 1 | use http::Method; 2 | use http::header; 3 | 4 | header! { 5 | /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) 6 | /// 7 | /// The `Allow` header field lists the set of methods advertised as 8 | /// supported by the target resource. The purpose of this field is 9 | /// strictly to inform the recipient of valid request methods associated 10 | /// with the resource. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Allow = #method 16 | /// ``` 17 | /// 18 | /// # Example values 19 | /// * `GET, HEAD, PUT` 20 | /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` 21 | /// * `` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ```rust 26 | /// # extern crate http; 27 | /// # extern crate actix_web; 28 | /// use actix_web::HttpResponse; 29 | /// use actix_web::http::header::Allow; 30 | /// use http::Method; 31 | /// 32 | /// # fn main() { 33 | /// let mut builder = HttpResponse::Ok(); 34 | /// builder.set( 35 | /// Allow(vec![Method::GET]) 36 | /// ); 37 | /// # } 38 | /// ``` 39 | /// 40 | /// ```rust 41 | /// # extern crate http; 42 | /// # extern crate actix_web; 43 | /// use actix_web::HttpResponse; 44 | /// use actix_web::http::header::Allow; 45 | /// use http::Method; 46 | /// 47 | /// # fn main() { 48 | /// let mut builder = HttpResponse::Ok(); 49 | /// builder.set( 50 | /// Allow(vec![ 51 | /// Method::GET, 52 | /// Method::POST, 53 | /// Method::PATCH, 54 | /// ]) 55 | /// ); 56 | /// # } 57 | /// ``` 58 | (Allow, header::ALLOW) => (Method)* 59 | 60 | test_allow { 61 | // From the RFC 62 | test_header!( 63 | test1, 64 | vec![b"GET, HEAD, PUT"], 65 | Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); 66 | // Own tests 67 | test_header!( 68 | test2, 69 | vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], 70 | Some(HeaderField(vec![ 71 | Method::OPTIONS, 72 | Method::GET, 73 | Method::PUT, 74 | Method::POST, 75 | Method::DELETE, 76 | Method::HEAD, 77 | Method::TRACE, 78 | Method::CONNECT, 79 | Method::PATCH]))); 80 | test_header!( 81 | test3, 82 | vec![b""], 83 | Some(HeaderField(Vec::::new()))); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/header/common/if_match.rs: -------------------------------------------------------------------------------- 1 | use header::{EntityTag, IF_MATCH}; 2 | 3 | header! { 4 | /// `If-Match` header, defined in 5 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) 6 | /// 7 | /// The `If-Match` header field makes the request method conditional on 8 | /// the recipient origin server either having at least one current 9 | /// representation of the target resource, when the field-value is "*", 10 | /// or having a current representation of the target resource that has an 11 | /// entity-tag matching a member of the list of entity-tags provided in 12 | /// the field-value. 13 | /// 14 | /// An origin server MUST use the strong comparison function when 15 | /// comparing entity-tags for `If-Match`, since the client 16 | /// intends this precondition to prevent the method from being applied if 17 | /// there have been any changes to the representation data. 18 | /// 19 | /// # ABNF 20 | /// 21 | /// ```text 22 | /// If-Match = "*" / 1#entity-tag 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `"xyzzy"` 28 | /// * "xyzzy", "r2d2xxxx", "c3piozzzz" 29 | /// 30 | /// # Examples 31 | /// 32 | /// ```rust 33 | /// use actix_web::HttpResponse; 34 | /// use actix_web::http::header::IfMatch; 35 | /// 36 | /// let mut builder = HttpResponse::Ok(); 37 | /// builder.set(IfMatch::Any); 38 | /// ``` 39 | /// 40 | /// ```rust 41 | /// use actix_web::HttpResponse; 42 | /// use actix_web::http::header::{IfMatch, EntityTag}; 43 | /// 44 | /// let mut builder = HttpResponse::Ok(); 45 | /// builder.set( 46 | /// IfMatch::Items(vec![ 47 | /// EntityTag::new(false, "xyzzy".to_owned()), 48 | /// EntityTag::new(false, "foobar".to_owned()), 49 | /// EntityTag::new(false, "bazquux".to_owned()), 50 | /// ]) 51 | /// ); 52 | /// ``` 53 | (IfMatch, IF_MATCH) => {Any / (EntityTag)+} 54 | 55 | test_if_match { 56 | test_header!( 57 | test1, 58 | vec![b"\"xyzzy\""], 59 | Some(HeaderField::Items( 60 | vec![EntityTag::new(false, "xyzzy".to_owned())]))); 61 | test_header!( 62 | test2, 63 | vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], 64 | Some(HeaderField::Items( 65 | vec![EntityTag::new(false, "xyzzy".to_owned()), 66 | EntityTag::new(false, "r2d2xxxx".to_owned()), 67 | EntityTag::new(false, "c3piozzzz".to_owned())]))); 68 | test_header!(test3, vec![b"*"], Some(IfMatch::Any)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/server/ssl/rustls.rs: -------------------------------------------------------------------------------- 1 | use std::net::Shutdown; 2 | use std::sync::Arc; 3 | use std::{io, time}; 4 | 5 | use rustls::{ClientSession, ServerConfig, ServerSession}; 6 | use tokio_io::AsyncWrite; 7 | use tokio_rustls::{AcceptAsync, ServerConfigExt, TlsStream}; 8 | 9 | use server::{AcceptorService, IoStream, ServerFlags}; 10 | 11 | #[derive(Clone)] 12 | /// Support `SSL` connections via rustls package 13 | /// 14 | /// `rust-tls` feature enables `RustlsAcceptor` type 15 | pub struct RustlsAcceptor { 16 | config: Arc, 17 | } 18 | 19 | impl RustlsAcceptor { 20 | /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. 21 | pub fn new(config: ServerConfig) -> Self { 22 | RustlsAcceptor::with_flags(config, ServerFlags::HTTP1 | ServerFlags::HTTP2) 23 | } 24 | 25 | /// Create `OpensslAcceptor` with custom server flags. 26 | pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self { 27 | let mut protos = Vec::new(); 28 | if flags.contains(ServerFlags::HTTP1) { 29 | protos.push("http/1.1".to_string()); 30 | } 31 | if flags.contains(ServerFlags::HTTP2) { 32 | protos.push("h2".to_string()); 33 | } 34 | 35 | if !protos.is_empty() { 36 | config.set_protocols(&protos); 37 | } 38 | 39 | RustlsAcceptor { 40 | config: Arc::new(config), 41 | } 42 | } 43 | } 44 | 45 | impl AcceptorService for RustlsAcceptor { 46 | type Accepted = TlsStream; 47 | type Future = AcceptAsync; 48 | 49 | fn scheme(&self) -> &'static str { 50 | "https" 51 | } 52 | 53 | fn accept(&self, io: Io) -> Self::Future { 54 | ServerConfigExt::accept_async(&self.config, io) 55 | } 56 | } 57 | 58 | impl IoStream for TlsStream { 59 | #[inline] 60 | fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { 61 | let _ = ::shutdown(self); 62 | Ok(()) 63 | } 64 | 65 | #[inline] 66 | fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { 67 | self.get_mut().0.set_nodelay(nodelay) 68 | } 69 | 70 | #[inline] 71 | fn set_linger(&mut self, dur: Option) -> io::Result<()> { 72 | self.get_mut().0.set_linger(dur) 73 | } 74 | } 75 | 76 | impl IoStream for TlsStream { 77 | #[inline] 78 | fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { 79 | let _ = ::shutdown(self); 80 | Ok(()) 81 | } 82 | 83 | #[inline] 84 | fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { 85 | self.get_mut().0.set_nodelay(nodelay) 86 | } 87 | 88 | #[inline] 89 | fn set_linger(&mut self, dur: Option) -> io::Result<()> { 90 | self.get_mut().0.set_linger(dur) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/server/ssl/openssl.rs: -------------------------------------------------------------------------------- 1 | use std::net::Shutdown; 2 | use std::{io, time}; 3 | 4 | use futures::{Future, Poll}; 5 | use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; 6 | use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream}; 7 | 8 | use server::{AcceptorService, IoStream, ServerFlags}; 9 | 10 | #[derive(Clone)] 11 | /// Support `SSL` connections via openssl package 12 | /// 13 | /// `alpn` feature enables `OpensslAcceptor` type 14 | pub struct OpensslAcceptor { 15 | acceptor: SslAcceptor, 16 | } 17 | 18 | impl OpensslAcceptor { 19 | /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. 20 | pub fn new(builder: SslAcceptorBuilder) -> io::Result { 21 | OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) 22 | } 23 | 24 | /// Create `OpensslAcceptor` with custom server flags. 25 | pub fn with_flags( 26 | mut builder: SslAcceptorBuilder, flags: ServerFlags, 27 | ) -> io::Result { 28 | let mut protos = Vec::new(); 29 | if flags.contains(ServerFlags::HTTP1) { 30 | protos.extend(b"\x08http/1.1"); 31 | } 32 | if flags.contains(ServerFlags::HTTP2) { 33 | protos.extend(b"\x02h2"); 34 | builder.set_alpn_select_callback(|_, protos| { 35 | const H2: &[u8] = b"\x02h2"; 36 | if protos.windows(3).any(|window| window == H2) { 37 | Ok(b"h2") 38 | } else { 39 | Err(AlpnError::NOACK) 40 | } 41 | }); 42 | } 43 | 44 | if !protos.is_empty() { 45 | builder.set_alpn_protos(&protos)?; 46 | } 47 | 48 | Ok(OpensslAcceptor { 49 | acceptor: builder.build(), 50 | }) 51 | } 52 | } 53 | 54 | pub struct AcceptorFut(AcceptAsync); 55 | 56 | impl Future for AcceptorFut { 57 | type Item = SslStream; 58 | type Error = io::Error; 59 | 60 | fn poll(&mut self) -> Poll { 61 | self.0 62 | .poll() 63 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 64 | } 65 | } 66 | 67 | impl AcceptorService for OpensslAcceptor { 68 | type Accepted = SslStream; 69 | type Future = AcceptorFut; 70 | 71 | fn scheme(&self) -> &'static str { 72 | "https" 73 | } 74 | 75 | fn accept(&self, io: Io) -> Self::Future { 76 | AcceptorFut(SslAcceptorExt::accept_async(&self.acceptor, io)) 77 | } 78 | } 79 | 80 | impl IoStream for SslStream { 81 | #[inline] 82 | fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { 83 | let _ = self.get_mut().shutdown(); 84 | Ok(()) 85 | } 86 | 87 | #[inline] 88 | fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { 89 | self.get_mut().get_mut().set_nodelay(nodelay) 90 | } 91 | 92 | #[inline] 93 | fn set_linger(&mut self, dur: Option) -> io::Result<()> { 94 | self.get_mut().get_mut().set_linger(dur) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | 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. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | ## Scope 32 | 33 | 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. 34 | 35 | ## Enforcement 36 | 37 | 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. 38 | 39 | 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. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /tests/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP 3 | n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M 4 | IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 5 | 4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ 6 | WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk 7 | oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli 8 | JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 9 | /stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD 10 | YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP 11 | wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA 12 | 69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA 13 | AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ 14 | 9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm 15 | YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR 16 | 6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM 17 | ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI 18 | 7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab 19 | L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ 20 | vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ 21 | b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz 22 | 0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL 23 | OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI 24 | 6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC 25 | 71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g 26 | 9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu 27 | bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb 28 | IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga 29 | /BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc 30 | KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 31 | iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP 32 | tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD 33 | jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY 34 | l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj 35 | gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh 36 | Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q 37 | 1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW 38 | t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI 39 | fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 40 | 5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt 41 | +oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc 42 | 3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf 43 | cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T 44 | qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU 45 | DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K 46 | 5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc 47 | fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc 48 | Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ 49 | 4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 50 | I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /src/extensions.rs: -------------------------------------------------------------------------------- 1 | use std::any::{Any, TypeId}; 2 | use std::collections::HashMap; 3 | use std::fmt; 4 | use std::hash::{BuildHasherDefault, Hasher}; 5 | 6 | struct IdHasher { 7 | id: u64, 8 | } 9 | 10 | impl Default for IdHasher { 11 | fn default() -> IdHasher { 12 | IdHasher { id: 0 } 13 | } 14 | } 15 | 16 | impl Hasher for IdHasher { 17 | fn write(&mut self, bytes: &[u8]) { 18 | for &x in bytes { 19 | self.id.wrapping_add(u64::from(x)); 20 | } 21 | } 22 | 23 | fn write_u64(&mut self, u: u64) { 24 | self.id = u; 25 | } 26 | 27 | fn finish(&self) -> u64 { 28 | self.id 29 | } 30 | } 31 | 32 | type AnyMap = HashMap, BuildHasherDefault>; 33 | 34 | /// A type map of request extensions. 35 | pub struct Extensions { 36 | map: AnyMap, 37 | } 38 | 39 | impl Extensions { 40 | /// Create an empty `Extensions`. 41 | #[inline] 42 | pub(crate) fn new() -> Extensions { 43 | Extensions { 44 | map: HashMap::default(), 45 | } 46 | } 47 | 48 | /// Insert a type into this `Extensions`. 49 | /// 50 | /// If a extension of this type already existed, it will 51 | /// be returned. 52 | pub fn insert(&mut self, val: T) { 53 | self.map.insert(TypeId::of::(), Box::new(val)); 54 | } 55 | 56 | /// Get a reference to a type previously inserted on this `Extensions`. 57 | pub fn get(&self) -> Option<&T> { 58 | self.map 59 | .get(&TypeId::of::()) 60 | .and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref()) 61 | } 62 | 63 | /// Get a mutable reference to a type previously inserted on this `Extensions`. 64 | pub fn get_mut(&mut self) -> Option<&mut T> { 65 | self.map 66 | .get_mut(&TypeId::of::()) 67 | .and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut()) 68 | } 69 | 70 | /// Remove a type from this `Extensions`. 71 | /// 72 | /// If a extension of this type existed, it will be returned. 73 | pub fn remove(&mut self) -> Option { 74 | self.map.remove(&TypeId::of::()).and_then(|boxed| { 75 | (boxed as Box) 76 | .downcast() 77 | .ok() 78 | .map(|boxed| *boxed) 79 | }) 80 | } 81 | 82 | /// Clear the `Extensions` of all inserted extensions. 83 | #[inline] 84 | pub fn clear(&mut self) { 85 | self.map.clear(); 86 | } 87 | } 88 | 89 | impl fmt::Debug for Extensions { 90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 91 | f.debug_struct("Extensions").finish() 92 | } 93 | } 94 | 95 | #[test] 96 | fn test_extensions() { 97 | #[derive(Debug, PartialEq)] 98 | struct MyType(i32); 99 | 100 | let mut extensions = Extensions::new(); 101 | 102 | extensions.insert(5i32); 103 | extensions.insert(MyType(10)); 104 | 105 | assert_eq!(extensions.get(), Some(&5i32)); 106 | assert_eq!(extensions.get_mut(), Some(&mut 5i32)); 107 | 108 | assert_eq!(extensions.remove::(), Some(5i32)); 109 | assert!(extensions.get::().is_none()); 110 | 111 | assert_eq!(extensions.get::(), None); 112 | assert_eq!(extensions.get(), Some(&MyType(10))); 113 | } 114 | -------------------------------------------------------------------------------- /src/header/common/if_none_match.rs: -------------------------------------------------------------------------------- 1 | use header::{EntityTag, IF_NONE_MATCH}; 2 | 3 | header! { 4 | /// `If-None-Match` header, defined in 5 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) 6 | /// 7 | /// The `If-None-Match` header field makes the request method conditional 8 | /// on a recipient cache or origin server either not having any current 9 | /// representation of the target resource, when the field-value is "*", 10 | /// or having a selected representation with an entity-tag that does not 11 | /// match any of those listed in the field-value. 12 | /// 13 | /// A recipient MUST use the weak comparison function when comparing 14 | /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags 15 | /// can be used for cache validation even if there have been changes to 16 | /// the representation data. 17 | /// 18 | /// # ABNF 19 | /// 20 | /// ```text 21 | /// If-None-Match = "*" / 1#entity-tag 22 | /// ``` 23 | /// 24 | /// # Example values 25 | /// 26 | /// * `"xyzzy"` 27 | /// * `W/"xyzzy"` 28 | /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` 29 | /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` 30 | /// * `*` 31 | /// 32 | /// # Examples 33 | /// 34 | /// ```rust 35 | /// use actix_web::HttpResponse; 36 | /// use actix_web::http::header::IfNoneMatch; 37 | /// 38 | /// let mut builder = HttpResponse::Ok(); 39 | /// builder.set(IfNoneMatch::Any); 40 | /// ``` 41 | /// 42 | /// ```rust 43 | /// use actix_web::HttpResponse; 44 | /// use actix_web::http::header::{IfNoneMatch, EntityTag}; 45 | /// 46 | /// let mut builder = HttpResponse::Ok(); 47 | /// builder.set( 48 | /// IfNoneMatch::Items(vec![ 49 | /// EntityTag::new(false, "xyzzy".to_owned()), 50 | /// EntityTag::new(false, "foobar".to_owned()), 51 | /// EntityTag::new(false, "bazquux".to_owned()), 52 | /// ]) 53 | /// ); 54 | /// ``` 55 | (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} 56 | 57 | test_if_none_match { 58 | test_header!(test1, vec![b"\"xyzzy\""]); 59 | test_header!(test2, vec![b"W/\"xyzzy\""]); 60 | test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); 61 | test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); 62 | test_header!(test5, vec![b"*"]); 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::IfNoneMatch; 69 | use header::{EntityTag, Header, IF_NONE_MATCH}; 70 | use test::TestRequest; 71 | 72 | #[test] 73 | fn test_if_none_match() { 74 | let mut if_none_match: Result; 75 | 76 | let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); 77 | if_none_match = Header::parse(&req); 78 | assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); 79 | 80 | let req = 81 | TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) 82 | .finish(); 83 | 84 | if_none_match = Header::parse(&req); 85 | let mut entities: Vec = Vec::new(); 86 | let foobar_etag = EntityTag::new(false, "foobar".to_owned()); 87 | let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); 88 | entities.push(foobar_etag); 89 | entities.push(weak_etag); 90 | assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/serde_urlencoded/mod.rs: -------------------------------------------------------------------------------- 1 | //! `x-www-form-urlencoded` meets Serde 2 | 3 | extern crate dtoa; 4 | extern crate itoa; 5 | 6 | pub mod de; 7 | pub mod ser; 8 | 9 | #[doc(inline)] 10 | pub use self::de::{from_bytes, from_reader, from_str, Deserializer}; 11 | #[doc(inline)] 12 | pub use self::ser::{to_string, Serializer}; 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | #[test] 17 | fn deserialize_bytes() { 18 | let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; 19 | 20 | assert_eq!(super::from_bytes(b"first=23&last=42"), Ok(result)); 21 | } 22 | 23 | #[test] 24 | fn deserialize_str() { 25 | let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; 26 | 27 | assert_eq!(super::from_str("first=23&last=42"), Ok(result)); 28 | } 29 | 30 | #[test] 31 | fn deserialize_reader() { 32 | let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; 33 | 34 | assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), Ok(result)); 35 | } 36 | 37 | #[test] 38 | fn deserialize_option() { 39 | let result = vec![ 40 | ("first".to_owned(), Some(23)), 41 | ("last".to_owned(), Some(42)), 42 | ]; 43 | assert_eq!(super::from_str("first=23&last=42"), Ok(result)); 44 | } 45 | 46 | #[test] 47 | fn deserialize_unit() { 48 | assert_eq!(super::from_str(""), Ok(())); 49 | assert_eq!(super::from_str("&"), Ok(())); 50 | assert_eq!(super::from_str("&&"), Ok(())); 51 | assert!(super::from_str::<()>("first=23").is_err()); 52 | } 53 | 54 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 55 | enum X { 56 | A, 57 | B, 58 | C, 59 | } 60 | 61 | #[test] 62 | fn deserialize_unit_enum() { 63 | let result = vec![ 64 | ("one".to_owned(), X::A), 65 | ("two".to_owned(), X::B), 66 | ("three".to_owned(), X::C), 67 | ]; 68 | 69 | assert_eq!(super::from_str("one=A&two=B&three=C"), Ok(result)); 70 | } 71 | 72 | #[test] 73 | fn serialize_option_map_int() { 74 | let params = &[("first", Some(23)), ("middle", None), ("last", Some(42))]; 75 | 76 | assert_eq!(super::to_string(params), Ok("first=23&last=42".to_owned())); 77 | } 78 | 79 | #[test] 80 | fn serialize_option_map_string() { 81 | let params = &[ 82 | ("first", Some("hello")), 83 | ("middle", None), 84 | ("last", Some("world")), 85 | ]; 86 | 87 | assert_eq!( 88 | super::to_string(params), 89 | Ok("first=hello&last=world".to_owned()) 90 | ); 91 | } 92 | 93 | #[test] 94 | fn serialize_option_map_bool() { 95 | let params = &[("one", Some(true)), ("two", Some(false))]; 96 | 97 | assert_eq!( 98 | super::to_string(params), 99 | Ok("one=true&two=false".to_owned()) 100 | ); 101 | } 102 | 103 | #[test] 104 | fn serialize_map_bool() { 105 | let params = &[("one", true), ("two", false)]; 106 | 107 | assert_eq!( 108 | super::to_string(params), 109 | Ok("one=true&two=false".to_owned()) 110 | ); 111 | } 112 | 113 | #[test] 114 | fn serialize_unit_enum() { 115 | let params = &[("one", X::A), ("two", X::B), ("three", X::C)]; 116 | assert_eq!( 117 | super::to_string(params), 118 | Ok("one=A&two=B&three=C".to_owned()) 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/header/common/etag.rs: -------------------------------------------------------------------------------- 1 | use header::{EntityTag, ETAG}; 2 | 3 | header! { 4 | /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) 5 | /// 6 | /// The `ETag` header field in a response provides the current entity-tag 7 | /// for the selected representation, as determined at the conclusion of 8 | /// handling the request. An entity-tag is an opaque validator for 9 | /// differentiating between multiple representations of the same 10 | /// resource, regardless of whether those multiple representations are 11 | /// due to resource state changes over time, content negotiation 12 | /// resulting in multiple representations being valid at the same time, 13 | /// or both. An entity-tag consists of an opaque quoted string, possibly 14 | /// prefixed by a weakness indicator. 15 | /// 16 | /// # ABNF 17 | /// 18 | /// ```text 19 | /// ETag = entity-tag 20 | /// ``` 21 | /// 22 | /// # Example values 23 | /// 24 | /// * `"xyzzy"` 25 | /// * `W/"xyzzy"` 26 | /// * `""` 27 | /// 28 | /// # Examples 29 | /// 30 | /// ```rust 31 | /// use actix_web::HttpResponse; 32 | /// use actix_web::http::header::{ETag, EntityTag}; 33 | /// 34 | /// let mut builder = HttpResponse::Ok(); 35 | /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); 36 | /// ``` 37 | /// 38 | /// ```rust 39 | /// use actix_web::HttpResponse; 40 | /// use actix_web::http::header::{ETag, EntityTag}; 41 | /// 42 | /// let mut builder = HttpResponse::Ok(); 43 | /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); 44 | /// ``` 45 | (ETag, ETAG) => [EntityTag] 46 | 47 | test_etag { 48 | // From the RFC 49 | test_header!(test1, 50 | vec![b"\"xyzzy\""], 51 | Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); 52 | test_header!(test2, 53 | vec![b"W/\"xyzzy\""], 54 | Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); 55 | test_header!(test3, 56 | vec![b"\"\""], 57 | Some(ETag(EntityTag::new(false, "".to_owned())))); 58 | // Own tests 59 | test_header!(test4, 60 | vec![b"\"foobar\""], 61 | Some(ETag(EntityTag::new(false, "foobar".to_owned())))); 62 | test_header!(test5, 63 | vec![b"\"\""], 64 | Some(ETag(EntityTag::new(false, "".to_owned())))); 65 | test_header!(test6, 66 | vec![b"W/\"weak-etag\""], 67 | Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); 68 | test_header!(test7, 69 | vec![b"W/\"\x65\x62\""], 70 | Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); 71 | test_header!(test8, 72 | vec![b"W/\"\""], 73 | Some(ETag(EntityTag::new(true, "".to_owned())))); 74 | test_header!(test9, 75 | vec![b"no-dquotes"], 76 | None::); 77 | test_header!(test10, 78 | vec![b"w/\"the-first-w-is-case-sensitive\""], 79 | None::); 80 | test_header!(test11, 81 | vec![b""], 82 | None::); 83 | test_header!(test12, 84 | vec![b"\"unmatched-dquotes1"], 85 | None::); 86 | test_header!(test13, 87 | vec![b"unmatched-dquotes2\""], 88 | None::); 89 | test_header!(test14, 90 | vec![b"matched-\"dquotes\""], 91 | None::); 92 | test_header!(test15, 93 | vec![b"\""], 94 | None::); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/header/shared/httpdate.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::io::Write; 3 | use std::str::FromStr; 4 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 5 | 6 | use bytes::{BufMut, BytesMut}; 7 | use http::header::{HeaderValue, InvalidHeaderValueBytes}; 8 | use time; 9 | 10 | use error::ParseError; 11 | use header::IntoHeaderValue; 12 | 13 | /// A timestamp with HTTP formatting and parsing 14 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 15 | pub struct HttpDate(time::Tm); 16 | 17 | impl FromStr for HttpDate { 18 | type Err = ParseError; 19 | 20 | fn from_str(s: &str) -> Result { 21 | match time::strptime(s, "%a, %d %b %Y %T %Z") 22 | .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) 23 | .or_else(|_| time::strptime(s, "%c")) 24 | { 25 | Ok(t) => Ok(HttpDate(t)), 26 | Err(_) => Err(ParseError::Header), 27 | } 28 | } 29 | } 30 | 31 | impl Display for HttpDate { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | fmt::Display::fmt(&self.0.to_utc().rfc822(), f) 34 | } 35 | } 36 | 37 | impl From for HttpDate { 38 | fn from(tm: time::Tm) -> HttpDate { 39 | HttpDate(tm) 40 | } 41 | } 42 | 43 | impl From for HttpDate { 44 | fn from(sys: SystemTime) -> HttpDate { 45 | let tmspec = match sys.duration_since(UNIX_EPOCH) { 46 | Ok(dur) => { 47 | time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) 48 | } 49 | Err(err) => { 50 | let neg = err.duration(); 51 | time::Timespec::new( 52 | -(neg.as_secs() as i64), 53 | -(neg.subsec_nanos() as i32), 54 | ) 55 | } 56 | }; 57 | HttpDate(time::at_utc(tmspec)) 58 | } 59 | } 60 | 61 | impl IntoHeaderValue for HttpDate { 62 | type Error = InvalidHeaderValueBytes; 63 | 64 | fn try_into(self) -> Result { 65 | let mut wrt = BytesMut::with_capacity(29).writer(); 66 | write!(wrt, "{}", self.0.rfc822()).unwrap(); 67 | HeaderValue::from_shared(wrt.get_mut().take().freeze()) 68 | } 69 | } 70 | 71 | impl From for SystemTime { 72 | fn from(date: HttpDate) -> SystemTime { 73 | let spec = date.0.to_timespec(); 74 | if spec.sec >= 0 { 75 | UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) 76 | } else { 77 | UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::HttpDate; 85 | use time::Tm; 86 | 87 | const NOV_07: HttpDate = HttpDate(Tm { 88 | tm_nsec: 0, 89 | tm_sec: 37, 90 | tm_min: 48, 91 | tm_hour: 8, 92 | tm_mday: 7, 93 | tm_mon: 10, 94 | tm_year: 94, 95 | tm_wday: 0, 96 | tm_isdst: 0, 97 | tm_yday: 0, 98 | tm_utcoff: 0, 99 | }); 100 | 101 | #[test] 102 | fn test_date() { 103 | assert_eq!( 104 | "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), 105 | NOV_07 106 | ); 107 | assert_eq!( 108 | "Sunday, 07-Nov-94 08:48:37 GMT" 109 | .parse::() 110 | .unwrap(), 111 | NOV_07 112 | ); 113 | assert_eq!( 114 | "Sun Nov 7 08:48:37 1994".parse::().unwrap(), 115 | NOV_07 116 | ); 117 | assert!("this-is-no-date".parse::().is_err()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/httpcodes.rs: -------------------------------------------------------------------------------- 1 | //! Basic http responses 2 | #![allow(non_upper_case_globals)] 3 | use http::StatusCode; 4 | use httpresponse::{HttpResponse, HttpResponseBuilder}; 5 | 6 | macro_rules! STATIC_RESP { 7 | ($name:ident, $status:expr) => { 8 | #[allow(non_snake_case, missing_docs)] 9 | pub fn $name() -> HttpResponseBuilder { 10 | HttpResponse::build($status) 11 | } 12 | }; 13 | } 14 | 15 | impl HttpResponse { 16 | STATIC_RESP!(Ok, StatusCode::OK); 17 | STATIC_RESP!(Created, StatusCode::CREATED); 18 | STATIC_RESP!(Accepted, StatusCode::ACCEPTED); 19 | STATIC_RESP!( 20 | NonAuthoritativeInformation, 21 | StatusCode::NON_AUTHORITATIVE_INFORMATION 22 | ); 23 | 24 | STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); 25 | STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); 26 | STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); 27 | STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); 28 | STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); 29 | 30 | STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); 31 | STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY); 32 | STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); 33 | STATIC_RESP!(Found, StatusCode::FOUND); 34 | STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); 35 | STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); 36 | STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); 37 | STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); 38 | STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); 39 | 40 | STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); 41 | STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); 42 | STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); 43 | STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); 44 | STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); 45 | STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); 46 | STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); 47 | STATIC_RESP!( 48 | ProxyAuthenticationRequired, 49 | StatusCode::PROXY_AUTHENTICATION_REQUIRED 50 | ); 51 | STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); 52 | STATIC_RESP!(Conflict, StatusCode::CONFLICT); 53 | STATIC_RESP!(Gone, StatusCode::GONE); 54 | STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); 55 | STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); 56 | STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); 57 | STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); 58 | STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); 59 | STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); 60 | STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); 61 | 62 | STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); 63 | STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); 64 | STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); 65 | STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); 66 | STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); 67 | STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); 68 | STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); 69 | STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); 70 | STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use body::Body; 76 | use http::StatusCode; 77 | use httpresponse::HttpResponse; 78 | 79 | #[test] 80 | fn test_build() { 81 | let resp = HttpResponse::Ok().body(Body::Empty); 82 | assert_eq!(resp.status(), StatusCode::OK); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web" 3 | version = "0.7.4" 4 | authors = ["Nikolay Kim "] 5 | description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." 6 | readme = "README.md" 7 | keywords = ["http", "web", "framework", "async", "futures"] 8 | homepage = "https://actix.rs" 9 | repository = "https://github.com/actix/actix-web.git" 10 | documentation = "https://actix.rs/api/actix-web/stable/actix_web/" 11 | categories = ["network-programming", "asynchronous", 12 | "web-programming::http-server", 13 | "web-programming::http-client", 14 | "web-programming::websocket"] 15 | license = "MIT/Apache-2.0" 16 | exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] 17 | build = "build.rs" 18 | 19 | [package.metadata.docs.rs] 20 | features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] 21 | 22 | [badges] 23 | travis-ci = { repository = "actix/actix-web", branch = "master" } 24 | appveyor = { repository = "fafhrd91/actix-web-hdy9d" } 25 | codecov = { repository = "actix/actix-web", branch = "master", service = "github" } 26 | 27 | [lib] 28 | name = "actix_web" 29 | path = "src/lib.rs" 30 | 31 | [features] 32 | default = ["session", "brotli", "flate2-c"] 33 | 34 | # tls 35 | tls = ["native-tls", "tokio-tls"] 36 | 37 | # openssl 38 | alpn = ["openssl", "tokio-openssl"] 39 | 40 | # rustls 41 | rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] 42 | 43 | # sessions feature, session require "ring" crate and c compiler 44 | session = ["cookie/secure"] 45 | 46 | # brotli encoding, requires c compiler 47 | brotli = ["brotli2"] 48 | 49 | # miniz-sys backend for flate2 crate 50 | flate2-c = ["flate2/miniz-sys"] 51 | 52 | # rust backend for flate2 crate 53 | flate2-rust = ["flate2/rust_backend"] 54 | 55 | [dependencies] 56 | actix = "0.7.0" 57 | 58 | base64 = "0.9" 59 | bitflags = "1.0" 60 | h2 = "0.1" 61 | htmlescape = "0.3" 62 | http = "^0.1.8" 63 | httparse = "1.3" 64 | log = "0.4" 65 | mime = "0.3" 66 | mime_guess = "2.0.0-alpha" 67 | num_cpus = "1.0" 68 | percent-encoding = "1.0" 69 | rand = "0.5" 70 | regex = "1.0" 71 | serde = "1.0" 72 | serde_json = "1.0" 73 | sha1 = "0.6" 74 | smallvec = "0.6" 75 | time = "0.1" 76 | encoding = "0.2" 77 | language-tags = "0.2" 78 | lazy_static = "1.0" 79 | lazycell = "1.0.0" 80 | parking_lot = "0.6" 81 | url = { version="1.7", features=["query_encoding"] } 82 | cookie = { version="0.11", features=["percent-encode"] } 83 | brotli2 = { version="^0.3.2", optional = true } 84 | flate2 = { version="^1.0.2", optional = true, default-features = false } 85 | 86 | failure = "^0.1.2" 87 | 88 | # io 89 | mio = "^0.6.13" 90 | net2 = "0.2" 91 | bytes = "0.4" 92 | byteorder = "1.2" 93 | futures = "0.1" 94 | futures-cpupool = "0.1" 95 | slab = "0.4" 96 | tokio = "0.1" 97 | tokio-io = "0.1" 98 | tokio-tcp = "0.1" 99 | tokio-timer = "0.2" 100 | tokio-reactor = "0.1" 101 | 102 | # native-tls 103 | native-tls = { version="0.1", optional = true } 104 | tokio-tls = { version="0.1", optional = true } 105 | 106 | # openssl 107 | openssl = { version="0.10", optional = true } 108 | tokio-openssl = { version="0.2", optional = true } 109 | 110 | #rustls 111 | rustls = { version = "0.13", optional = true } 112 | tokio-rustls = { version = "0.7", optional = true } 113 | webpki = { version = "0.18", optional = true } 114 | webpki-roots = { version = "0.15", optional = true } 115 | 116 | # forked url_encoded 117 | itoa = "0.4" 118 | dtoa = "0.4" 119 | 120 | [dev-dependencies] 121 | env_logger = "0.5" 122 | serde_derive = "1.0" 123 | 124 | [build-dependencies] 125 | version_check = "0.1" 126 | 127 | [profile.release] 128 | lto = true 129 | opt-level = 3 130 | codegen-units = 1 131 | 132 | [workspace] 133 | members = [ 134 | "./", 135 | ] 136 | -------------------------------------------------------------------------------- /src/client/response.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::{fmt, str}; 3 | 4 | use cookie::Cookie; 5 | use http::header::{self, HeaderValue}; 6 | use http::{HeaderMap, StatusCode, Version}; 7 | 8 | use error::CookieParseError; 9 | use httpmessage::HttpMessage; 10 | 11 | use super::pipeline::Pipeline; 12 | 13 | pub(crate) struct ClientMessage { 14 | pub status: StatusCode, 15 | pub version: Version, 16 | pub headers: HeaderMap, 17 | pub cookies: Option>>, 18 | } 19 | 20 | impl Default for ClientMessage { 21 | fn default() -> ClientMessage { 22 | ClientMessage { 23 | status: StatusCode::OK, 24 | version: Version::HTTP_11, 25 | headers: HeaderMap::with_capacity(16), 26 | cookies: None, 27 | } 28 | } 29 | } 30 | 31 | /// An HTTP Client response 32 | pub struct ClientResponse(ClientMessage, RefCell>>); 33 | 34 | impl HttpMessage for ClientResponse { 35 | type Stream = Box; 36 | 37 | /// Get the headers from the response. 38 | #[inline] 39 | fn headers(&self) -> &HeaderMap { 40 | &self.0.headers 41 | } 42 | 43 | #[inline] 44 | fn payload(&self) -> Box { 45 | self.1 46 | .borrow_mut() 47 | .take() 48 | .expect("Payload is already consumed.") 49 | } 50 | } 51 | 52 | impl ClientResponse { 53 | pub(crate) fn new(msg: ClientMessage) -> ClientResponse { 54 | ClientResponse(msg, RefCell::new(None)) 55 | } 56 | 57 | pub(crate) fn set_pipeline(&mut self, pl: Box) { 58 | *self.1.borrow_mut() = Some(pl); 59 | } 60 | 61 | /// Get the HTTP version of this response. 62 | #[inline] 63 | pub fn version(&self) -> Version { 64 | self.0.version 65 | } 66 | 67 | /// Get the status from the server. 68 | #[inline] 69 | pub fn status(&self) -> StatusCode { 70 | self.0.status 71 | } 72 | 73 | /// Load response cookies. 74 | pub fn cookies(&self) -> Result>, CookieParseError> { 75 | let mut cookies = Vec::new(); 76 | for val in self.0.headers.get_all(header::SET_COOKIE).iter() { 77 | let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; 78 | cookies.push(Cookie::parse_encoded(s)?.into_owned()); 79 | } 80 | Ok(cookies) 81 | } 82 | 83 | /// Return request cookie. 84 | pub fn cookie(&self, name: &str) -> Option { 85 | if let Ok(cookies) = self.cookies() { 86 | for cookie in cookies { 87 | if cookie.name() == name { 88 | return Some(cookie); 89 | } 90 | } 91 | } 92 | None 93 | } 94 | } 95 | 96 | impl fmt::Debug for ClientResponse { 97 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 98 | let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status()); 99 | let _ = writeln!(f, " headers:"); 100 | for (key, val) in self.headers().iter() { 101 | let _ = writeln!(f, " {:?}: {:?}", key, val); 102 | } 103 | res 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::*; 110 | 111 | #[test] 112 | fn test_debug() { 113 | let mut resp = ClientResponse::new(ClientMessage::default()); 114 | resp.0 115 | .headers 116 | .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); 117 | resp.0 118 | .headers 119 | .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); 120 | 121 | let dbg = format!("{:?}", resp); 122 | assert!(dbg.contains("ClientResponse")); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/header/common/if_range.rs: -------------------------------------------------------------------------------- 1 | use error::ParseError; 2 | use header::from_one_raw_str; 3 | use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, 4 | InvalidHeaderValueBytes, Writer}; 5 | use http::header; 6 | use httpmessage::HttpMessage; 7 | use std::fmt::{self, Display, Write}; 8 | 9 | /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) 10 | /// 11 | /// If a client has a partial copy of a representation and wishes to have 12 | /// an up-to-date copy of the entire representation, it could use the 13 | /// Range header field with a conditional GET (using either or both of 14 | /// If-Unmodified-Since and If-Match.) However, if the precondition 15 | /// fails because the representation has been modified, the client would 16 | /// then have to make a second request to obtain the entire current 17 | /// representation. 18 | /// 19 | /// The `If-Range` header field allows a client to \"short-circuit\" the 20 | /// second request. Informally, its meaning is as follows: if the 21 | /// representation is unchanged, send me the part(s) that I am requesting 22 | /// in Range; otherwise, send me the entire representation. 23 | /// 24 | /// # ABNF 25 | /// 26 | /// ```text 27 | /// If-Range = entity-tag / HTTP-date 28 | /// ``` 29 | /// 30 | /// # Example values 31 | /// 32 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 33 | /// * `\"xyzzy\"` 34 | /// 35 | /// # Examples 36 | /// 37 | /// ```rust 38 | /// use actix_web::HttpResponse; 39 | /// use actix_web::http::header::{EntityTag, IfRange}; 40 | /// 41 | /// let mut builder = HttpResponse::Ok(); 42 | /// builder.set(IfRange::EntityTag(EntityTag::new( 43 | /// false, 44 | /// "xyzzy".to_owned(), 45 | /// ))); 46 | /// ``` 47 | /// 48 | /// ```rust 49 | /// use actix_web::HttpResponse; 50 | /// use actix_web::http::header::IfRange; 51 | /// use std::time::{Duration, SystemTime}; 52 | /// 53 | /// let mut builder = HttpResponse::Ok(); 54 | /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 55 | /// builder.set(IfRange::Date(fetched.into())); 56 | /// ``` 57 | #[derive(Clone, Debug, PartialEq)] 58 | pub enum IfRange { 59 | /// The entity-tag the client has of the resource 60 | EntityTag(EntityTag), 61 | /// The date when the client retrieved the resource 62 | Date(HttpDate), 63 | } 64 | 65 | impl Header for IfRange { 66 | fn name() -> HeaderName { 67 | header::IF_RANGE 68 | } 69 | #[inline] 70 | fn parse(msg: &T) -> Result 71 | where 72 | T: HttpMessage, 73 | { 74 | let etag: Result = 75 | from_one_raw_str(msg.headers().get(header::IF_RANGE)); 76 | if let Ok(etag) = etag { 77 | return Ok(IfRange::EntityTag(etag)); 78 | } 79 | let date: Result = 80 | from_one_raw_str(msg.headers().get(header::IF_RANGE)); 81 | if let Ok(date) = date { 82 | return Ok(IfRange::Date(date)); 83 | } 84 | Err(ParseError::Header) 85 | } 86 | } 87 | 88 | impl Display for IfRange { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | match *self { 91 | IfRange::EntityTag(ref x) => Display::fmt(x, f), 92 | IfRange::Date(ref x) => Display::fmt(x, f), 93 | } 94 | } 95 | } 96 | 97 | impl IntoHeaderValue for IfRange { 98 | type Error = InvalidHeaderValueBytes; 99 | 100 | fn try_into(self) -> Result { 101 | let mut writer = Writer::new(); 102 | let _ = write!(&mut writer, "{}", self); 103 | HeaderValue::from_shared(writer.take()) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod test_if_range { 109 | use super::IfRange as HeaderField; 110 | use header::*; 111 | use std::str; 112 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 113 | test_header!(test2, vec![b"\"xyzzy\""]); 114 | test_header!(test3, vec![b"this-is-invalid"], None::); 115 | } 116 | -------------------------------------------------------------------------------- /src/middleware/defaultheaders.rs: -------------------------------------------------------------------------------- 1 | //! Default response headers 2 | use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; 3 | use http::{HeaderMap, HttpTryFrom}; 4 | 5 | use error::Result; 6 | use httprequest::HttpRequest; 7 | use httpresponse::HttpResponse; 8 | use middleware::{Middleware, Response}; 9 | 10 | /// `Middleware` for setting default response headers. 11 | /// 12 | /// This middleware does not set header if response headers already contains it. 13 | /// 14 | /// ```rust 15 | /// # extern crate actix_web; 16 | /// use actix_web::{http, middleware, App, HttpResponse}; 17 | /// 18 | /// fn main() { 19 | /// let app = App::new() 20 | /// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) 21 | /// .resource("/test", |r| { 22 | /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); 23 | /// r.method(http::Method::HEAD) 24 | /// .f(|_| HttpResponse::MethodNotAllowed()); 25 | /// }) 26 | /// .finish(); 27 | /// } 28 | /// ``` 29 | pub struct DefaultHeaders { 30 | ct: bool, 31 | headers: HeaderMap, 32 | } 33 | 34 | impl Default for DefaultHeaders { 35 | fn default() -> Self { 36 | DefaultHeaders { 37 | ct: false, 38 | headers: HeaderMap::new(), 39 | } 40 | } 41 | } 42 | 43 | impl DefaultHeaders { 44 | /// Construct `DefaultHeaders` middleware. 45 | pub fn new() -> DefaultHeaders { 46 | DefaultHeaders::default() 47 | } 48 | 49 | /// Set a header. 50 | #[inline] 51 | #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] 52 | pub fn header(mut self, key: K, value: V) -> Self 53 | where 54 | HeaderName: HttpTryFrom, 55 | HeaderValue: HttpTryFrom, 56 | { 57 | match HeaderName::try_from(key) { 58 | Ok(key) => match HeaderValue::try_from(value) { 59 | Ok(value) => { 60 | self.headers.append(key, value); 61 | } 62 | Err(_) => panic!("Can not create header value"), 63 | }, 64 | Err(_) => panic!("Can not create header name"), 65 | } 66 | self 67 | } 68 | 69 | /// Set *CONTENT-TYPE* header if response does not contain this header. 70 | pub fn content_type(mut self) -> Self { 71 | self.ct = true; 72 | self 73 | } 74 | } 75 | 76 | impl Middleware for DefaultHeaders { 77 | fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { 78 | for (key, value) in self.headers.iter() { 79 | if !resp.headers().contains_key(key) { 80 | resp.headers_mut().insert(key, value.clone()); 81 | } 82 | } 83 | // default content-type 84 | if self.ct && !resp.headers().contains_key(CONTENT_TYPE) { 85 | resp.headers_mut().insert( 86 | CONTENT_TYPE, 87 | HeaderValue::from_static("application/octet-stream"), 88 | ); 89 | } 90 | Ok(Response::Done(resp)) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use http::header::CONTENT_TYPE; 98 | use test::TestRequest; 99 | 100 | #[test] 101 | fn test_default_headers() { 102 | let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); 103 | 104 | let req = TestRequest::default().finish(); 105 | 106 | let resp = HttpResponse::Ok().finish(); 107 | let resp = match mw.response(&req, resp) { 108 | Ok(Response::Done(resp)) => resp, 109 | _ => panic!(), 110 | }; 111 | assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); 112 | 113 | let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); 114 | let resp = match mw.response(&req, resp) { 115 | Ok(Response::Done(resp)) => resp, 116 | _ => panic!(), 117 | }; 118 | assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/header/common/content_type.rs: -------------------------------------------------------------------------------- 1 | use header::CONTENT_TYPE; 2 | use mime::{self, Mime}; 3 | 4 | header! { 5 | /// `Content-Type` header, defined in 6 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) 7 | /// 8 | /// The `Content-Type` header field indicates the media type of the 9 | /// associated representation: either the representation enclosed in the 10 | /// message payload or the selected representation, as determined by the 11 | /// message semantics. The indicated media type defines both the data 12 | /// format and how that data is intended to be processed by a recipient, 13 | /// within the scope of the received message semantics, after any content 14 | /// codings indicated by Content-Encoding are decoded. 15 | /// 16 | /// Although the `mime` crate allows the mime options to be any slice, this crate 17 | /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If 18 | /// this is an issue, it's possible to implement `Header` on a custom struct. 19 | /// 20 | /// # ABNF 21 | /// 22 | /// ```text 23 | /// Content-Type = media-type 24 | /// ``` 25 | /// 26 | /// # Example values 27 | /// 28 | /// * `text/html; charset=utf-8` 29 | /// * `application/json` 30 | /// 31 | /// # Examples 32 | /// 33 | /// ```rust 34 | /// use actix_web::HttpResponse; 35 | /// use actix_web::http::header::ContentType; 36 | /// 37 | /// # fn main() { 38 | /// let mut builder = HttpResponse::Ok(); 39 | /// builder.set( 40 | /// ContentType::json() 41 | /// ); 42 | /// # } 43 | /// ``` 44 | /// 45 | /// ```rust 46 | /// # extern crate mime; 47 | /// # extern crate actix_web; 48 | /// use mime::TEXT_HTML; 49 | /// use actix_web::HttpResponse; 50 | /// use actix_web::http::header::ContentType; 51 | /// 52 | /// # fn main() { 53 | /// let mut builder = HttpResponse::Ok(); 54 | /// builder.set( 55 | /// ContentType(TEXT_HTML) 56 | /// ); 57 | /// # } 58 | /// ``` 59 | (ContentType, CONTENT_TYPE) => [Mime] 60 | 61 | test_content_type { 62 | test_header!( 63 | test1, 64 | vec![b"text/html"], 65 | Some(HeaderField(TEXT_HTML))); 66 | } 67 | } 68 | 69 | impl ContentType { 70 | /// A constructor to easily create a `Content-Type: application/json` 71 | /// header. 72 | #[inline] 73 | pub fn json() -> ContentType { 74 | ContentType(mime::APPLICATION_JSON) 75 | } 76 | 77 | /// A constructor to easily create a `Content-Type: text/plain; 78 | /// charset=utf-8` header. 79 | #[inline] 80 | pub fn plaintext() -> ContentType { 81 | ContentType(mime::TEXT_PLAIN_UTF_8) 82 | } 83 | 84 | /// A constructor to easily create a `Content-Type: text/html` header. 85 | #[inline] 86 | pub fn html() -> ContentType { 87 | ContentType(mime::TEXT_HTML) 88 | } 89 | 90 | /// A constructor to easily create a `Content-Type: text/xml` header. 91 | #[inline] 92 | pub fn xml() -> ContentType { 93 | ContentType(mime::TEXT_XML) 94 | } 95 | 96 | /// A constructor to easily create a `Content-Type: 97 | /// application/www-form-url-encoded` header. 98 | #[inline] 99 | pub fn form_url_encoded() -> ContentType { 100 | ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) 101 | } 102 | /// A constructor to easily create a `Content-Type: image/jpeg` header. 103 | #[inline] 104 | pub fn jpeg() -> ContentType { 105 | ContentType(mime::IMAGE_JPEG) 106 | } 107 | 108 | /// A constructor to easily create a `Content-Type: image/png` header. 109 | #[inline] 110 | pub fn png() -> ContentType { 111 | ContentType(mime::IMAGE_PNG) 112 | } 113 | 114 | /// A constructor to easily create a `Content-Type: 115 | /// application/octet-stream` header. 116 | #[inline] 117 | pub fn octet_stream() -> ContentType { 118 | ContentType(mime::APPLICATION_OCTET_STREAM) 119 | } 120 | } 121 | 122 | impl Eq for ContentType {} 123 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | //! Http client api 2 | //! 3 | //! ```rust 4 | //! # extern crate actix_web; 5 | //! # extern crate futures; 6 | //! # extern crate tokio; 7 | //! # use futures::Future; 8 | //! # use std::process; 9 | //! use actix_web::{actix, client}; 10 | //! 11 | //! fn main() { 12 | //! actix::run( 13 | //! || client::get("http://www.rust-lang.org") // <- Create request builder 14 | //! .header("User-Agent", "Actix-web") 15 | //! .finish().unwrap() 16 | //! .send() // <- Send http request 17 | //! .map_err(|_| ()) 18 | //! .and_then(|response| { // <- server http response 19 | //! println!("Response: {:?}", response); 20 | //! # actix::System::current().stop(); 21 | //! Ok(()) 22 | //! }) 23 | //! ); 24 | //! } 25 | //! ``` 26 | mod connector; 27 | mod parser; 28 | mod pipeline; 29 | mod request; 30 | mod response; 31 | mod writer; 32 | 33 | pub use self::connector::{ 34 | ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, 35 | Pause, Resume, 36 | }; 37 | pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; 38 | pub(crate) use self::pipeline::Pipeline; 39 | pub use self::pipeline::{SendRequest, SendRequestError}; 40 | pub use self::request::{ClientRequest, ClientRequestBuilder}; 41 | pub use self::response::ClientResponse; 42 | pub(crate) use self::writer::HttpClientWriter; 43 | 44 | use error::ResponseError; 45 | use http::Method; 46 | use httpresponse::HttpResponse; 47 | 48 | /// Convert `SendRequestError` to a `HttpResponse` 49 | impl ResponseError for SendRequestError { 50 | fn error_response(&self) -> HttpResponse { 51 | match *self { 52 | SendRequestError::Timeout => HttpResponse::GatewayTimeout(), 53 | SendRequestError::Connector(_) => HttpResponse::BadGateway(), 54 | _ => HttpResponse::InternalServerError(), 55 | }.into() 56 | } 57 | } 58 | 59 | /// Create request builder for `GET` requests 60 | /// 61 | /// 62 | /// ```rust 63 | /// # extern crate actix_web; 64 | /// # extern crate futures; 65 | /// # extern crate tokio; 66 | /// # extern crate env_logger; 67 | /// # use futures::Future; 68 | /// # use std::process; 69 | /// use actix_web::{actix, client}; 70 | /// 71 | /// fn main() { 72 | /// actix::run( 73 | /// || client::get("http://www.rust-lang.org") // <- Create request builder 74 | /// .header("User-Agent", "Actix-web") 75 | /// .finish().unwrap() 76 | /// .send() // <- Send http request 77 | /// .map_err(|_| ()) 78 | /// .and_then(|response| { // <- server http response 79 | /// println!("Response: {:?}", response); 80 | /// # actix::System::current().stop(); 81 | /// Ok(()) 82 | /// }), 83 | /// ); 84 | /// } 85 | /// ``` 86 | pub fn get>(uri: U) -> ClientRequestBuilder { 87 | let mut builder = ClientRequest::build(); 88 | builder.method(Method::GET).uri(uri); 89 | builder 90 | } 91 | 92 | /// Create request builder for `HEAD` requests 93 | pub fn head>(uri: U) -> ClientRequestBuilder { 94 | let mut builder = ClientRequest::build(); 95 | builder.method(Method::HEAD).uri(uri); 96 | builder 97 | } 98 | 99 | /// Create request builder for `POST` requests 100 | pub fn post>(uri: U) -> ClientRequestBuilder { 101 | let mut builder = ClientRequest::build(); 102 | builder.method(Method::POST).uri(uri); 103 | builder 104 | } 105 | 106 | /// Create request builder for `PUT` requests 107 | pub fn put>(uri: U) -> ClientRequestBuilder { 108 | let mut builder = ClientRequest::build(); 109 | builder.method(Method::PUT).uri(uri); 110 | builder 111 | } 112 | 113 | /// Create request builder for `DELETE` requests 114 | pub fn delete>(uri: U) -> ClientRequestBuilder { 115 | let mut builder = ClientRequest::build(); 116 | builder.method(Method::DELETE).uri(uri); 117 | builder 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | Actix web is a simple, pragmatic and extremely fast web framework for Rust. 4 | 5 | * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols 6 | * Streaming and pipelining 7 | * Keep-alive and slow requests handling 8 | * Client/server [WebSockets](https://actix.rs/docs/websockets/) support 9 | * Transparent content compression/decompression (br, gzip, deflate) 10 | * Configurable [request routing](https://actix.rs/docs/url-dispatch/) 11 | * Graceful server shutdown 12 | * Multipart streams 13 | * Static assets 14 | * SSL support with OpenSSL or `native-tls` 15 | * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) 16 | * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) 17 | * Built on top of [Actix actor framework](https://github.com/actix/actix) 18 | 19 | ## Documentation & community resources 20 | 21 | * [User Guide](https://actix.rs/docs/) 22 | * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) 23 | * [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) 24 | * [Chat on gitter](https://gitter.im/actix/actix) 25 | * Cargo package: [actix-web](https://crates.io/crates/actix-web) 26 | * Minimum supported Rust version: 1.26 or later 27 | 28 | ## Example 29 | 30 | ```rust 31 | extern crate actix_web; 32 | use actix_web::{http, server, App, Path, Responder}; 33 | 34 | fn index(info: Path<(u32, String)>) -> impl Responder { 35 | format!("Hello {}! id:{}", info.1, info.0) 36 | } 37 | 38 | fn main() { 39 | server::new( 40 | || App::new() 41 | .route("/{id}/{name}/index.html", http::Method::GET, index)) 42 | .bind("127.0.0.1:8080").unwrap() 43 | .run(); 44 | } 45 | ``` 46 | 47 | ### More examples 48 | 49 | * [Basics](https://github.com/actix/examples/tree/master/basics/) 50 | * [Stateful](https://github.com/actix/examples/tree/master/state/) 51 | * [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) 52 | * [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) 53 | * [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) 54 | * [Tera](https://github.com/actix/examples/tree/master/template_tera/) / 55 | [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates 56 | * [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) 57 | * [r2d2](https://github.com/actix/examples/tree/master/r2d2/) 58 | * [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) 59 | * [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) 60 | * [Json](https://github.com/actix/examples/tree/master/json/) 61 | 62 | You may consider checking out 63 | [this directory](https://github.com/actix/examples/tree/master/) for more examples. 64 | 65 | ## Benchmarks 66 | 67 | * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) 68 | 69 | * Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). 70 | 71 | ## License 72 | 73 | This project is licensed under either of 74 | 75 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 76 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 77 | 78 | at your option. 79 | 80 | ## Code of Conduct 81 | 82 | Contribution to the actix-web crate is organized under the terms of the 83 | Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to 84 | intervene to uphold that code of conduct. 85 | -------------------------------------------------------------------------------- /src/header/shared/charset.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | 4 | use self::Charset::*; 5 | 6 | /// A Mime charset. 7 | /// 8 | /// The string representation is normalized to upper case. 9 | /// 10 | /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. 11 | /// 12 | /// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml 13 | #[derive(Clone, Debug, PartialEq)] 14 | #[allow(non_camel_case_types)] 15 | pub enum Charset { 16 | /// US ASCII 17 | Us_Ascii, 18 | /// ISO-8859-1 19 | Iso_8859_1, 20 | /// ISO-8859-2 21 | Iso_8859_2, 22 | /// ISO-8859-3 23 | Iso_8859_3, 24 | /// ISO-8859-4 25 | Iso_8859_4, 26 | /// ISO-8859-5 27 | Iso_8859_5, 28 | /// ISO-8859-6 29 | Iso_8859_6, 30 | /// ISO-8859-7 31 | Iso_8859_7, 32 | /// ISO-8859-8 33 | Iso_8859_8, 34 | /// ISO-8859-9 35 | Iso_8859_9, 36 | /// ISO-8859-10 37 | Iso_8859_10, 38 | /// Shift_JIS 39 | Shift_Jis, 40 | /// EUC-JP 41 | Euc_Jp, 42 | /// ISO-2022-KR 43 | Iso_2022_Kr, 44 | /// EUC-KR 45 | Euc_Kr, 46 | /// ISO-2022-JP 47 | Iso_2022_Jp, 48 | /// ISO-2022-JP-2 49 | Iso_2022_Jp_2, 50 | /// ISO-8859-6-E 51 | Iso_8859_6_E, 52 | /// ISO-8859-6-I 53 | Iso_8859_6_I, 54 | /// ISO-8859-8-E 55 | Iso_8859_8_E, 56 | /// ISO-8859-8-I 57 | Iso_8859_8_I, 58 | /// GB2312 59 | Gb2312, 60 | /// Big5 61 | Big5, 62 | /// KOI8-R 63 | Koi8_R, 64 | /// An arbitrary charset specified as a string 65 | Ext(String), 66 | } 67 | 68 | impl Charset { 69 | fn label(&self) -> &str { 70 | match *self { 71 | Us_Ascii => "US-ASCII", 72 | Iso_8859_1 => "ISO-8859-1", 73 | Iso_8859_2 => "ISO-8859-2", 74 | Iso_8859_3 => "ISO-8859-3", 75 | Iso_8859_4 => "ISO-8859-4", 76 | Iso_8859_5 => "ISO-8859-5", 77 | Iso_8859_6 => "ISO-8859-6", 78 | Iso_8859_7 => "ISO-8859-7", 79 | Iso_8859_8 => "ISO-8859-8", 80 | Iso_8859_9 => "ISO-8859-9", 81 | Iso_8859_10 => "ISO-8859-10", 82 | Shift_Jis => "Shift-JIS", 83 | Euc_Jp => "EUC-JP", 84 | Iso_2022_Kr => "ISO-2022-KR", 85 | Euc_Kr => "EUC-KR", 86 | Iso_2022_Jp => "ISO-2022-JP", 87 | Iso_2022_Jp_2 => "ISO-2022-JP-2", 88 | Iso_8859_6_E => "ISO-8859-6-E", 89 | Iso_8859_6_I => "ISO-8859-6-I", 90 | Iso_8859_8_E => "ISO-8859-8-E", 91 | Iso_8859_8_I => "ISO-8859-8-I", 92 | Gb2312 => "GB2312", 93 | Big5 => "big5", 94 | Koi8_R => "KOI8-R", 95 | Ext(ref s) => s, 96 | } 97 | } 98 | } 99 | 100 | impl Display for Charset { 101 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 102 | f.write_str(self.label()) 103 | } 104 | } 105 | 106 | impl FromStr for Charset { 107 | type Err = ::Error; 108 | fn from_str(s: &str) -> ::Result { 109 | Ok(match s.to_ascii_uppercase().as_ref() { 110 | "US-ASCII" => Us_Ascii, 111 | "ISO-8859-1" => Iso_8859_1, 112 | "ISO-8859-2" => Iso_8859_2, 113 | "ISO-8859-3" => Iso_8859_3, 114 | "ISO-8859-4" => Iso_8859_4, 115 | "ISO-8859-5" => Iso_8859_5, 116 | "ISO-8859-6" => Iso_8859_6, 117 | "ISO-8859-7" => Iso_8859_7, 118 | "ISO-8859-8" => Iso_8859_8, 119 | "ISO-8859-9" => Iso_8859_9, 120 | "ISO-8859-10" => Iso_8859_10, 121 | "SHIFT-JIS" => Shift_Jis, 122 | "EUC-JP" => Euc_Jp, 123 | "ISO-2022-KR" => Iso_2022_Kr, 124 | "EUC-KR" => Euc_Kr, 125 | "ISO-2022-JP" => Iso_2022_Jp, 126 | "ISO-2022-JP-2" => Iso_2022_Jp_2, 127 | "ISO-8859-6-E" => Iso_8859_6_E, 128 | "ISO-8859-6-I" => Iso_8859_6_I, 129 | "ISO-8859-8-E" => Iso_8859_8_E, 130 | "ISO-8859-8-I" => Iso_8859_8_I, 131 | "GB2312" => Gb2312, 132 | "big5" => Big5, 133 | "KOI8-R" => Koi8_R, 134 | s => Ext(s.to_owned()), 135 | }) 136 | } 137 | } 138 | 139 | #[test] 140 | fn test_parse() { 141 | assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); 142 | assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); 143 | assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); 144 | assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); 145 | assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); 146 | } 147 | 148 | #[test] 149 | fn test_display() { 150 | assert_eq!("US-ASCII", format!("{}", Us_Ascii)); 151 | assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); 152 | } 153 | -------------------------------------------------------------------------------- /src/middleware/errhandlers.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use error::Result; 4 | use http::StatusCode; 5 | use httprequest::HttpRequest; 6 | use httpresponse::HttpResponse; 7 | use middleware::{Middleware, Response}; 8 | 9 | type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; 10 | 11 | /// `Middleware` for allowing custom handlers for responses. 12 | /// 13 | /// You can use `ErrorHandlers::handler()` method to register a custom error 14 | /// handler for specific status code. You can modify existing response or 15 | /// create completely new one. 16 | /// 17 | /// ## Example 18 | /// 19 | /// ```rust 20 | /// # extern crate actix_web; 21 | /// use actix_web::middleware::{ErrorHandlers, Response}; 22 | /// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; 23 | /// 24 | /// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { 25 | /// let mut builder = resp.into_builder(); 26 | /// builder.header(http::header::CONTENT_TYPE, "application/json"); 27 | /// Ok(Response::Done(builder.into())) 28 | /// } 29 | /// 30 | /// fn main() { 31 | /// let app = App::new() 32 | /// .middleware( 33 | /// ErrorHandlers::new() 34 | /// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), 35 | /// ) 36 | /// .resource("/test", |r| { 37 | /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); 38 | /// r.method(http::Method::HEAD) 39 | /// .f(|_| HttpResponse::MethodNotAllowed()); 40 | /// }) 41 | /// .finish(); 42 | /// } 43 | /// ``` 44 | pub struct ErrorHandlers { 45 | handlers: HashMap>>, 46 | } 47 | 48 | impl Default for ErrorHandlers { 49 | fn default() -> Self { 50 | ErrorHandlers { 51 | handlers: HashMap::new(), 52 | } 53 | } 54 | } 55 | 56 | impl ErrorHandlers { 57 | /// Construct new `ErrorHandlers` instance 58 | pub fn new() -> Self { 59 | ErrorHandlers::default() 60 | } 61 | 62 | /// Register error handler for specified status code 63 | pub fn handler(mut self, status: StatusCode, handler: F) -> Self 64 | where 65 | F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, 66 | { 67 | self.handlers.insert(status, Box::new(handler)); 68 | self 69 | } 70 | } 71 | 72 | impl Middleware for ErrorHandlers { 73 | fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { 74 | if let Some(handler) = self.handlers.get(&resp.status()) { 75 | handler(req, resp) 76 | } else { 77 | Ok(Response::Done(resp)) 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use error::{Error, ErrorInternalServerError}; 86 | use http::header::CONTENT_TYPE; 87 | use http::StatusCode; 88 | use httpmessage::HttpMessage; 89 | use middleware::Started; 90 | use test::{self, TestRequest}; 91 | 92 | fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { 93 | let mut builder = resp.into_builder(); 94 | builder.header(CONTENT_TYPE, "0001"); 95 | Ok(Response::Done(builder.into())) 96 | } 97 | 98 | #[test] 99 | fn test_handler() { 100 | let mw = 101 | ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); 102 | 103 | let mut req = TestRequest::default().finish(); 104 | let resp = HttpResponse::InternalServerError().finish(); 105 | let resp = match mw.response(&mut req, resp) { 106 | Ok(Response::Done(resp)) => resp, 107 | _ => panic!(), 108 | }; 109 | assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); 110 | 111 | let resp = HttpResponse::Ok().finish(); 112 | let resp = match mw.response(&mut req, resp) { 113 | Ok(Response::Done(resp)) => resp, 114 | _ => panic!(), 115 | }; 116 | assert!(!resp.headers().contains_key(CONTENT_TYPE)); 117 | } 118 | 119 | struct MiddlewareOne; 120 | 121 | impl Middleware for MiddlewareOne { 122 | fn start(&self, _: &HttpRequest) -> Result { 123 | Err(ErrorInternalServerError("middleware error")) 124 | } 125 | } 126 | 127 | #[test] 128 | fn test_middleware_start_error() { 129 | let mut srv = test::TestServer::new(move |app| { 130 | app.middleware( 131 | ErrorHandlers::new() 132 | .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), 133 | ).middleware(MiddlewareOne) 134 | .handler(|_| HttpResponse::Ok()) 135 | }); 136 | 137 | let request = srv.get().finish().unwrap(); 138 | let response = srv.execute(request.send()).unwrap(); 139 | assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/uri.rs: -------------------------------------------------------------------------------- 1 | use http::Uri; 2 | use std::rc::Rc; 3 | 4 | #[allow(dead_code)] 5 | const GEN_DELIMS: &[u8] = b":/?#[]@"; 6 | #[allow(dead_code)] 7 | const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; 8 | #[allow(dead_code)] 9 | const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; 10 | #[allow(dead_code)] 11 | const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; 12 | #[allow(dead_code)] 13 | const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz 14 | ABCDEFGHIJKLMNOPQRSTUVWXYZ 15 | 1234567890 16 | -._~"; 17 | const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz 18 | ABCDEFGHIJKLMNOPQRSTUVWXYZ 19 | 1234567890 20 | -._~ 21 | !$'()*,"; 22 | const QS: &[u8] = b"+&=;b"; 23 | 24 | #[inline] 25 | fn bit_at(array: &[u8], ch: u8) -> bool { 26 | array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 27 | } 28 | 29 | #[inline] 30 | fn set_bit(array: &mut [u8], ch: u8) { 31 | array[(ch >> 3) as usize] |= 1 << (ch & 7) 32 | } 33 | 34 | lazy_static! { 35 | static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; 36 | } 37 | 38 | #[derive(Default, Clone, Debug)] 39 | pub(crate) struct Url { 40 | uri: Uri, 41 | path: Option>, 42 | } 43 | 44 | impl Url { 45 | pub fn new(uri: Uri) -> Url { 46 | let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); 47 | 48 | Url { uri, path } 49 | } 50 | 51 | pub fn uri(&self) -> &Uri { 52 | &self.uri 53 | } 54 | 55 | pub fn path(&self) -> &str { 56 | if let Some(ref s) = self.path { 57 | s 58 | } else { 59 | self.uri.path() 60 | } 61 | } 62 | } 63 | 64 | pub(crate) struct Quoter { 65 | safe_table: [u8; 16], 66 | protected_table: [u8; 16], 67 | } 68 | 69 | impl Quoter { 70 | pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { 71 | let mut q = Quoter { 72 | safe_table: [0; 16], 73 | protected_table: [0; 16], 74 | }; 75 | 76 | // prepare safe table 77 | for i in 0..128 { 78 | if ALLOWED.contains(&i) { 79 | set_bit(&mut q.safe_table, i); 80 | } 81 | if QS.contains(&i) { 82 | set_bit(&mut q.safe_table, i); 83 | } 84 | } 85 | 86 | for ch in safe { 87 | set_bit(&mut q.safe_table, *ch) 88 | } 89 | 90 | // prepare protected table 91 | for ch in protected { 92 | set_bit(&mut q.safe_table, *ch); 93 | set_bit(&mut q.protected_table, *ch); 94 | } 95 | 96 | q 97 | } 98 | 99 | pub fn requote(&self, val: &[u8]) -> Option> { 100 | let mut has_pct = 0; 101 | let mut pct = [b'%', 0, 0]; 102 | let mut idx = 0; 103 | let mut cloned: Option> = None; 104 | 105 | let len = val.len(); 106 | while idx < len { 107 | let ch = val[idx]; 108 | 109 | if has_pct != 0 { 110 | pct[has_pct] = val[idx]; 111 | has_pct += 1; 112 | if has_pct == 3 { 113 | has_pct = 0; 114 | let buf = cloned.as_mut().unwrap(); 115 | 116 | if let Some(ch) = restore_ch(pct[1], pct[2]) { 117 | if ch < 128 { 118 | if bit_at(&self.protected_table, ch) { 119 | buf.extend_from_slice(&pct); 120 | idx += 1; 121 | continue; 122 | } 123 | 124 | if bit_at(&self.safe_table, ch) { 125 | buf.push(ch); 126 | idx += 1; 127 | continue; 128 | } 129 | } 130 | buf.push(ch); 131 | } else { 132 | buf.extend_from_slice(&pct[..]); 133 | } 134 | } 135 | } else if ch == b'%' { 136 | has_pct = 1; 137 | if cloned.is_none() { 138 | let mut c = Vec::with_capacity(len); 139 | c.extend_from_slice(&val[..idx]); 140 | cloned = Some(c); 141 | } 142 | } else if let Some(ref mut cloned) = cloned { 143 | cloned.push(ch) 144 | } 145 | idx += 1; 146 | } 147 | 148 | if let Some(data) = cloned { 149 | // Unsafe: we get data from http::Uri, which does utf-8 checks already 150 | // this code only decodes valid pct encoded values 151 | Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) 152 | } else { 153 | None 154 | } 155 | } 156 | } 157 | 158 | #[inline] 159 | fn from_hex(v: u8) -> Option { 160 | if v >= b'0' && v <= b'9' { 161 | Some(v - 0x30) // ord('0') == 0x30 162 | } else if v >= b'A' && v <= b'F' { 163 | Some(v - 0x41 + 10) // ord('A') == 0x41 164 | } else if v > b'a' && v <= b'f' { 165 | Some(v - 0x61 + 10) // ord('a') == 0x61 166 | } else { 167 | None 168 | } 169 | } 170 | 171 | #[inline] 172 | fn restore_ch(d1: u8, d2: u8) -> Option { 173 | from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) 174 | } 175 | -------------------------------------------------------------------------------- /src/header/common/accept.rs: -------------------------------------------------------------------------------- 1 | use header::{qitem, QualityItem}; 2 | use http::header as http; 3 | use mime::{self, Mime}; 4 | 5 | header! { 6 | /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) 7 | /// 8 | /// The `Accept` header field can be used by user agents to specify 9 | /// response media types that are acceptable. Accept header fields can 10 | /// be used to indicate that the request is specifically limited to a 11 | /// small set of desired types, as in the case of a request for an 12 | /// in-line image 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Accept = #( media-range [ accept-params ] ) 18 | /// 19 | /// media-range = ( "*/*" 20 | /// / ( type "/" "*" ) 21 | /// / ( type "/" subtype ) 22 | /// ) *( OWS ";" OWS parameter ) 23 | /// accept-params = weight *( accept-ext ) 24 | /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] 25 | /// ``` 26 | /// 27 | /// # Example values 28 | /// * `audio/*; q=0.2, audio/basic` 29 | /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` 30 | /// 31 | /// # Examples 32 | /// ```rust 33 | /// # extern crate actix_web; 34 | /// extern crate mime; 35 | /// use actix_web::HttpResponse; 36 | /// use actix_web::http::header::{Accept, qitem}; 37 | /// 38 | /// # fn main() { 39 | /// let mut builder = HttpResponse::Ok(); 40 | /// 41 | /// builder.set( 42 | /// Accept(vec![ 43 | /// qitem(mime::TEXT_HTML), 44 | /// ]) 45 | /// ); 46 | /// # } 47 | /// ``` 48 | /// 49 | /// ```rust 50 | /// # extern crate actix_web; 51 | /// extern crate mime; 52 | /// use actix_web::HttpResponse; 53 | /// use actix_web::http::header::{Accept, qitem}; 54 | /// 55 | /// # fn main() { 56 | /// let mut builder = HttpResponse::Ok(); 57 | /// 58 | /// builder.set( 59 | /// Accept(vec![ 60 | /// qitem(mime::APPLICATION_JSON), 61 | /// ]) 62 | /// ); 63 | /// # } 64 | /// ``` 65 | /// 66 | /// ```rust 67 | /// # extern crate actix_web; 68 | /// extern crate mime; 69 | /// use actix_web::HttpResponse; 70 | /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; 71 | /// 72 | /// # fn main() { 73 | /// let mut builder = HttpResponse::Ok(); 74 | /// 75 | /// builder.set( 76 | /// Accept(vec![ 77 | /// qitem(mime::TEXT_HTML), 78 | /// qitem("application/xhtml+xml".parse().unwrap()), 79 | /// QualityItem::new( 80 | /// mime::TEXT_XML, 81 | /// q(900) 82 | /// ), 83 | /// qitem("image/webp".parse().unwrap()), 84 | /// QualityItem::new( 85 | /// mime::STAR_STAR, 86 | /// q(800) 87 | /// ), 88 | /// ]) 89 | /// ); 90 | /// # } 91 | /// ``` 92 | (Accept, http::ACCEPT) => (QualityItem)+ 93 | 94 | test_accept { 95 | // Tests from the RFC 96 | test_header!( 97 | test1, 98 | vec![b"audio/*; q=0.2, audio/basic"], 99 | Some(HeaderField(vec![ 100 | QualityItem::new("audio/*".parse().unwrap(), q(200)), 101 | qitem("audio/basic".parse().unwrap()), 102 | ]))); 103 | test_header!( 104 | test2, 105 | vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], 106 | Some(HeaderField(vec![ 107 | QualityItem::new(TEXT_PLAIN, q(500)), 108 | qitem(TEXT_HTML), 109 | QualityItem::new( 110 | "text/x-dvi".parse().unwrap(), 111 | q(800)), 112 | qitem("text/x-c".parse().unwrap()), 113 | ]))); 114 | // Custom tests 115 | test_header!( 116 | test3, 117 | vec![b"text/plain; charset=utf-8"], 118 | Some(Accept(vec![ 119 | qitem(TEXT_PLAIN_UTF_8), 120 | ]))); 121 | test_header!( 122 | test4, 123 | vec![b"text/plain; charset=utf-8; q=0.5"], 124 | Some(Accept(vec![ 125 | QualityItem::new(TEXT_PLAIN_UTF_8, 126 | q(500)), 127 | ]))); 128 | 129 | #[test] 130 | fn test_fuzzing1() { 131 | use test::TestRequest; 132 | let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish(); 133 | let header = Accept::parse(&req); 134 | assert!(header.is_ok()); 135 | } 136 | } 137 | } 138 | 139 | impl Accept { 140 | /// A constructor to easily create `Accept: */*`. 141 | pub fn star() -> Accept { 142 | Accept(vec![qitem(mime::STAR_STAR)]) 143 | } 144 | 145 | /// A constructor to easily create `Accept: application/json`. 146 | pub fn json() -> Accept { 147 | Accept(vec![qitem(mime::APPLICATION_JSON)]) 148 | } 149 | 150 | /// A constructor to easily create `Accept: text/*`. 151 | pub fn text() -> Accept { 152 | Accept(vec![qitem(mime::TEXT_STAR)]) 153 | } 154 | 155 | /// A constructor to easily create `Accept: image/*`. 156 | pub fn image() -> Accept { 157 | Accept(vec![qitem(mime::IMAGE_STAR)]) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/ws/mask.rs: -------------------------------------------------------------------------------- 1 | //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) 2 | #![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] 3 | use std::ptr::copy_nonoverlapping; 4 | use std::slice; 5 | 6 | // Holds a slice guaranteed to be shorter than 8 bytes 7 | struct ShortSlice<'a>(&'a mut [u8]); 8 | 9 | impl<'a> ShortSlice<'a> { 10 | unsafe fn new(slice: &'a mut [u8]) -> Self { 11 | // Sanity check for debug builds 12 | debug_assert!(slice.len() < 8); 13 | ShortSlice(slice) 14 | } 15 | fn len(&self) -> usize { 16 | self.0.len() 17 | } 18 | } 19 | 20 | /// Faster version of `apply_mask()` which operates on 8-byte blocks. 21 | #[inline] 22 | #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] 23 | pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { 24 | // Extend the mask to 64 bits 25 | let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); 26 | // Split the buffer into three segments 27 | let (head, mid, tail) = align_buf(buf); 28 | 29 | // Initial unaligned segment 30 | let head_len = head.len(); 31 | if head_len > 0 { 32 | xor_short(head, mask_u64); 33 | if cfg!(target_endian = "big") { 34 | mask_u64 = mask_u64.rotate_left(8 * head_len as u32); 35 | } else { 36 | mask_u64 = mask_u64.rotate_right(8 * head_len as u32); 37 | } 38 | } 39 | // Aligned segment 40 | for v in mid { 41 | *v ^= mask_u64; 42 | } 43 | // Final unaligned segment 44 | if tail.len() > 0 { 45 | xor_short(tail, mask_u64); 46 | } 47 | } 48 | 49 | #[inline] 50 | // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so 51 | // inefficient, it could be done better. The compiler does not understand that 52 | // a `ShortSlice` must be smaller than a u64. 53 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 54 | fn xor_short(buf: ShortSlice, mask: u64) { 55 | // Unsafe: we know that a `ShortSlice` fits in a u64 56 | unsafe { 57 | let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); 58 | let mut b: u64 = 0; 59 | #[allow(trivial_casts)] 60 | copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); 61 | b ^= mask; 62 | #[allow(trivial_casts)] 63 | copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); 64 | } 65 | } 66 | 67 | #[inline] 68 | // Unsafe: caller must ensure the buffer has the correct size and alignment 69 | unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { 70 | // Assert correct size and alignment in debug builds 71 | debug_assert!(buf.len().trailing_zeros() >= 3); 72 | debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); 73 | 74 | slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) 75 | } 76 | 77 | #[inline] 78 | // Splits a slice into three parts: an unaligned short head and tail, plus an aligned 79 | // u64 mid section. 80 | fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { 81 | let start_ptr = buf.as_ptr() as usize; 82 | let end_ptr = start_ptr + buf.len(); 83 | 84 | // Round *up* to next aligned boundary for start 85 | let start_aligned = (start_ptr + 7) & !0x7; 86 | // Round *down* to last aligned boundary for end 87 | let end_aligned = end_ptr & !0x7; 88 | 89 | if end_aligned >= start_aligned { 90 | // We have our three segments (head, mid, tail) 91 | let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); 92 | let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); 93 | 94 | // Unsafe: we know the middle section is correctly aligned, and the outer 95 | // sections are smaller than 8 bytes 96 | unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) } 97 | } else { 98 | // We didn't cross even one aligned boundary! 99 | 100 | // Unsafe: The outer sections are smaller than 8 bytes 101 | unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::apply_mask; 108 | use byteorder::{ByteOrder, LittleEndian}; 109 | 110 | /// A safe unoptimized mask application. 111 | fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { 112 | for (i, byte) in buf.iter_mut().enumerate() { 113 | *byte ^= mask[i & 3]; 114 | } 115 | } 116 | 117 | #[test] 118 | fn test_apply_mask() { 119 | let mask = [0x6d, 0xb6, 0xb2, 0x80]; 120 | let mask_u32: u32 = LittleEndian::read_u32(&mask); 121 | 122 | let unmasked = vec![ 123 | 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 124 | 0x74, 0xf9, 0x12, 0x03, 125 | ]; 126 | 127 | // Check masking with proper alignment. 128 | { 129 | let mut masked = unmasked.clone(); 130 | apply_mask_fallback(&mut masked, &mask); 131 | 132 | let mut masked_fast = unmasked.clone(); 133 | apply_mask(&mut masked_fast, mask_u32); 134 | 135 | assert_eq!(masked, masked_fast); 136 | } 137 | 138 | // Check masking without alignment. 139 | { 140 | let mut masked = unmasked.clone(); 141 | apply_mask_fallback(&mut masked[1..], &mask); 142 | 143 | let mut masked_fast = unmasked.clone(); 144 | apply_mask(&mut masked_fast[1..], mask_u32); 145 | 146 | assert_eq!(masked, masked_fast); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | ## 0.7 2 | 3 | * `HttpRequest` does not implement `Stream` anymore. If you need to read request payload 4 | use `HttpMessage::payload()` method. 5 | 6 | instead of 7 | 8 | ```rust 9 | fn index(req: HttpRequest) -> impl Responder { 10 | req 11 | .from_err() 12 | .fold(...) 13 | .... 14 | } 15 | ``` 16 | 17 | use `.payload()` 18 | 19 | ```rust 20 | fn index(req: HttpRequest) -> impl Responder { 21 | req 22 | .payload() // <- get request payload stream 23 | .from_err() 24 | .fold(...) 25 | .... 26 | } 27 | ``` 28 | 29 | * [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) 30 | trait uses `&HttpRequest` instead of `&mut HttpRequest`. 31 | 32 | * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. 33 | 34 | instead of 35 | 36 | ```rust 37 | fn index(query: Query<..>, info: Json impl Responder {} 38 | ``` 39 | 40 | use tuple of extractors and use `.with()` for registration: 41 | 42 | ```rust 43 | fn index((query, json): (Query<..>, Json impl Responder {} 44 | ``` 45 | 46 | * `Handler::handle()` uses `&self` instead of `&mut self` 47 | 48 | * `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value 49 | 50 | * Removed deprecated `HttpServer::threads()`, use 51 | [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. 52 | 53 | * Renamed `client::ClientConnectorError::Connector` to 54 | `client::ClientConnectorError::Resolver` 55 | 56 | * `Route::with()` does not return `ExtractorConfig`, to configure 57 | extractor use `Route::with_config()` 58 | 59 | instead of 60 | 61 | ```rust 62 | fn main() { 63 | let app = App::new().resource("/index.html", |r| { 64 | r.method(http::Method::GET) 65 | .with(index) 66 | .limit(4096); // <- limit size of the payload 67 | }); 68 | } 69 | ``` 70 | 71 | use 72 | 73 | ```rust 74 | 75 | fn main() { 76 | let app = App::new().resource("/index.html", |r| { 77 | r.method(http::Method::GET) 78 | .with_config(index, |cfg| { // <- register handler 79 | cfg.limit(4096); // <- limit size of the payload 80 | }) 81 | }); 82 | } 83 | ``` 84 | 85 | * `Route::with_async()` does not return `ExtractorConfig`, to configure 86 | extractor use `Route::with_async_config()` 87 | 88 | 89 | ## 0.6 90 | 91 | * `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` 92 | 93 | * `ws::Message::Close` now includes optional close reason. 94 | `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. 95 | 96 | * `HttpServer::threads()` renamed to `HttpServer::workers()`. 97 | 98 | * `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. 99 | Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. 100 | 101 | * `HttpRequest::extensions()` returns read only reference to the request's Extension 102 | `HttpRequest::extensions_mut()` returns mutable reference. 103 | 104 | * Instead of 105 | 106 | `use actix_web::middleware::{ 107 | CookieSessionBackend, CookieSessionError, RequestSession, 108 | Session, SessionBackend, SessionImpl, SessionStorage};` 109 | 110 | use `actix_web::middleware::session` 111 | 112 | `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, 113 | RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` 114 | 115 | * `FromRequest::from_request()` accepts mutable reference to a request 116 | 117 | * `FromRequest::Result` has to implement `Into>` 118 | 119 | * [`Responder::respond_to()`]( 120 | https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) 121 | is generic over `S` 122 | 123 | * Use `Query` extractor instead of HttpRequest::query()`. 124 | 125 | ```rust 126 | fn index(q: Query>) -> Result<..> { 127 | ... 128 | } 129 | ``` 130 | 131 | or 132 | 133 | ```rust 134 | let q = Query::>::extract(req); 135 | ``` 136 | 137 | * Websocket operations are implemented as `WsWriter` trait. 138 | you need to use `use actix_web::ws::WsWriter` 139 | 140 | 141 | ## 0.5 142 | 143 | * `HttpResponseBuilder::body()`, `.finish()`, `.json()` 144 | methods return `HttpResponse` instead of `Result` 145 | 146 | * `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` 147 | moved to `actix_web::http` module 148 | 149 | * `actix_web::header` moved to `actix_web::http::header` 150 | 151 | * `NormalizePath` moved to `actix_web::http` module 152 | 153 | * `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, 154 | shortcut for `actix_web::server::HttpServer::new()` 155 | 156 | * `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself 157 | 158 | * `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. 159 | 160 | * `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type 161 | 162 | * `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` 163 | functions should be used instead 164 | 165 | * `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` 166 | instead of `Result<_, http::Error>` 167 | 168 | * `Application` renamed to a `App` 169 | 170 | * `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` 171 | -------------------------------------------------------------------------------- /src/serde_urlencoded/ser/part.rs: -------------------------------------------------------------------------------- 1 | use serde; 2 | 3 | use super::super::dtoa; 4 | use super::super::itoa; 5 | use super::super::ser::Error; 6 | use std::str; 7 | 8 | pub struct PartSerializer { 9 | sink: S, 10 | } 11 | 12 | impl PartSerializer { 13 | pub fn new(sink: S) -> Self { 14 | PartSerializer { sink } 15 | } 16 | } 17 | 18 | pub trait Sink: Sized { 19 | type Ok; 20 | 21 | fn serialize_static_str(self, value: &'static str) -> Result; 22 | 23 | fn serialize_str(self, value: &str) -> Result; 24 | fn serialize_string(self, value: String) -> Result; 25 | fn serialize_none(self) -> Result; 26 | 27 | fn serialize_some( 28 | self, value: &T, 29 | ) -> Result; 30 | 31 | fn unsupported(self) -> Error; 32 | } 33 | 34 | impl serde::ser::Serializer for PartSerializer { 35 | type Ok = S::Ok; 36 | type Error = Error; 37 | type SerializeSeq = serde::ser::Impossible; 38 | type SerializeTuple = serde::ser::Impossible; 39 | type SerializeTupleStruct = serde::ser::Impossible; 40 | type SerializeTupleVariant = serde::ser::Impossible; 41 | type SerializeMap = serde::ser::Impossible; 42 | type SerializeStruct = serde::ser::Impossible; 43 | type SerializeStructVariant = serde::ser::Impossible; 44 | 45 | fn serialize_bool(self, v: bool) -> Result { 46 | self.sink 47 | .serialize_static_str(if v { "true" } else { "false" }) 48 | } 49 | 50 | fn serialize_i8(self, v: i8) -> Result { 51 | self.serialize_integer(v) 52 | } 53 | 54 | fn serialize_i16(self, v: i16) -> Result { 55 | self.serialize_integer(v) 56 | } 57 | 58 | fn serialize_i32(self, v: i32) -> Result { 59 | self.serialize_integer(v) 60 | } 61 | 62 | fn serialize_i64(self, v: i64) -> Result { 63 | self.serialize_integer(v) 64 | } 65 | 66 | fn serialize_u8(self, v: u8) -> Result { 67 | self.serialize_integer(v) 68 | } 69 | 70 | fn serialize_u16(self, v: u16) -> Result { 71 | self.serialize_integer(v) 72 | } 73 | 74 | fn serialize_u32(self, v: u32) -> Result { 75 | self.serialize_integer(v) 76 | } 77 | 78 | fn serialize_u64(self, v: u64) -> Result { 79 | self.serialize_integer(v) 80 | } 81 | 82 | fn serialize_f32(self, v: f32) -> Result { 83 | self.serialize_floating(v) 84 | } 85 | 86 | fn serialize_f64(self, v: f64) -> Result { 87 | self.serialize_floating(v) 88 | } 89 | 90 | fn serialize_char(self, v: char) -> Result { 91 | self.sink.serialize_string(v.to_string()) 92 | } 93 | 94 | fn serialize_str(self, value: &str) -> Result { 95 | self.sink.serialize_str(value) 96 | } 97 | 98 | fn serialize_bytes(self, value: &[u8]) -> Result { 99 | match str::from_utf8(value) { 100 | Ok(value) => self.sink.serialize_str(value), 101 | Err(err) => Err(Error::Utf8(err)), 102 | } 103 | } 104 | 105 | fn serialize_unit(self) -> Result { 106 | Err(self.sink.unsupported()) 107 | } 108 | 109 | fn serialize_unit_struct(self, name: &'static str) -> Result { 110 | self.sink.serialize_static_str(name) 111 | } 112 | 113 | fn serialize_unit_variant( 114 | self, _name: &'static str, _variant_index: u32, variant: &'static str, 115 | ) -> Result { 116 | self.sink.serialize_static_str(variant) 117 | } 118 | 119 | fn serialize_newtype_struct( 120 | self, _name: &'static str, value: &T, 121 | ) -> Result { 122 | value.serialize(self) 123 | } 124 | 125 | fn serialize_newtype_variant( 126 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 127 | _value: &T, 128 | ) -> Result { 129 | Err(self.sink.unsupported()) 130 | } 131 | 132 | fn serialize_none(self) -> Result { 133 | self.sink.serialize_none() 134 | } 135 | 136 | fn serialize_some( 137 | self, value: &T, 138 | ) -> Result { 139 | self.sink.serialize_some(value) 140 | } 141 | 142 | fn serialize_seq(self, _len: Option) -> Result { 143 | Err(self.sink.unsupported()) 144 | } 145 | 146 | fn serialize_tuple(self, _len: usize) -> Result { 147 | Err(self.sink.unsupported()) 148 | } 149 | 150 | fn serialize_tuple_struct( 151 | self, _name: &'static str, _len: usize, 152 | ) -> Result { 153 | Err(self.sink.unsupported()) 154 | } 155 | 156 | fn serialize_tuple_variant( 157 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 158 | _len: usize, 159 | ) -> Result { 160 | Err(self.sink.unsupported()) 161 | } 162 | 163 | fn serialize_map(self, _len: Option) -> Result { 164 | Err(self.sink.unsupported()) 165 | } 166 | 167 | fn serialize_struct( 168 | self, _name: &'static str, _len: usize, 169 | ) -> Result { 170 | Err(self.sink.unsupported()) 171 | } 172 | 173 | fn serialize_struct_variant( 174 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 175 | _len: usize, 176 | ) -> Result { 177 | Err(self.sink.unsupported()) 178 | } 179 | } 180 | 181 | impl PartSerializer { 182 | fn serialize_integer(self, value: I) -> Result 183 | where 184 | I: itoa::Integer, 185 | { 186 | let mut buf = [b'\0'; 20]; 187 | let len = itoa::write(&mut buf[..], value).unwrap(); 188 | let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; 189 | serde::ser::Serializer::serialize_str(self, part) 190 | } 191 | 192 | fn serialize_floating(self, value: F) -> Result 193 | where 194 | F: dtoa::Floating, 195 | { 196 | let mut buf = [b'\0'; 24]; 197 | let len = dtoa::write(&mut buf[..], value).unwrap(); 198 | let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; 199 | serde::ser::Serializer::serialize_str(self, part) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/server/helpers.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | use http::Version; 3 | use std::{mem, ptr, slice}; 4 | 5 | const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 6 | 2021222324252627282930313233343536373839\ 7 | 4041424344454647484950515253545556575859\ 8 | 6061626364656667686970717273747576777879\ 9 | 8081828384858687888990919293949596979899"; 10 | 11 | pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { 12 | let mut buf: [u8; 13] = [ 13 | b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ', 14 | ]; 15 | match version { 16 | Version::HTTP_2 => buf[5] = b'2', 17 | Version::HTTP_10 => buf[7] = b'0', 18 | Version::HTTP_09 => { 19 | buf[5] = b'0'; 20 | buf[7] = b'9'; 21 | } 22 | _ => (), 23 | } 24 | 25 | let mut curr: isize = 12; 26 | let buf_ptr = buf.as_mut_ptr(); 27 | let lut_ptr = DEC_DIGITS_LUT.as_ptr(); 28 | let four = n > 999; 29 | 30 | unsafe { 31 | // decode 2 more chars, if > 2 chars 32 | let d1 = (n % 100) << 1; 33 | n /= 100; 34 | curr -= 2; 35 | ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); 36 | 37 | // decode last 1 or 2 chars 38 | if n < 10 { 39 | curr -= 1; 40 | *buf_ptr.offset(curr) = (n as u8) + b'0'; 41 | } else { 42 | let d1 = n << 1; 43 | curr -= 2; 44 | ptr::copy_nonoverlapping( 45 | lut_ptr.offset(d1 as isize), 46 | buf_ptr.offset(curr), 47 | 2, 48 | ); 49 | } 50 | } 51 | 52 | bytes.put_slice(&buf); 53 | if four { 54 | bytes.put(b' '); 55 | } 56 | } 57 | 58 | /// NOTE: bytes object has to contain enough space 59 | pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { 60 | if n < 10 { 61 | let mut buf: [u8; 21] = [ 62 | b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', 63 | b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n', 64 | ]; 65 | buf[18] = (n as u8) + b'0'; 66 | bytes.put_slice(&buf); 67 | } else if n < 100 { 68 | let mut buf: [u8; 22] = [ 69 | b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', 70 | b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n', 71 | ]; 72 | let d1 = n << 1; 73 | unsafe { 74 | ptr::copy_nonoverlapping( 75 | DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), 76 | buf.as_mut_ptr().offset(18), 77 | 2, 78 | ); 79 | } 80 | bytes.put_slice(&buf); 81 | } else if n < 1000 { 82 | let mut buf: [u8; 23] = [ 83 | b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', 84 | b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r', b'\n', 85 | ]; 86 | // decode 2 more chars, if > 2 chars 87 | let d1 = (n % 100) << 1; 88 | n /= 100; 89 | unsafe { 90 | ptr::copy_nonoverlapping( 91 | DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), 92 | buf.as_mut_ptr().offset(19), 93 | 2, 94 | ) 95 | }; 96 | 97 | // decode last 1 98 | buf[18] = (n as u8) + b'0'; 99 | 100 | bytes.put_slice(&buf); 101 | } else { 102 | bytes.put_slice(b"\r\ncontent-length: "); 103 | convert_usize(n, bytes); 104 | } 105 | } 106 | 107 | pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { 108 | unsafe { 109 | let mut curr: isize = 39; 110 | let mut buf: [u8; 41] = mem::uninitialized(); 111 | buf[39] = b'\r'; 112 | buf[40] = b'\n'; 113 | let buf_ptr = buf.as_mut_ptr(); 114 | let lut_ptr = DEC_DIGITS_LUT.as_ptr(); 115 | 116 | // eagerly decode 4 characters at a time 117 | while n >= 10_000 { 118 | let rem = (n % 10_000) as isize; 119 | n /= 10_000; 120 | 121 | let d1 = (rem / 100) << 1; 122 | let d2 = (rem % 100) << 1; 123 | curr -= 4; 124 | ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); 125 | ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); 126 | } 127 | 128 | // if we reach here numbers are <= 9999, so at most 4 chars long 129 | let mut n = n as isize; // possibly reduce 64bit math 130 | 131 | // decode 2 more chars, if > 2 chars 132 | if n >= 100 { 133 | let d1 = (n % 100) << 1; 134 | n /= 100; 135 | curr -= 2; 136 | ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); 137 | } 138 | 139 | // decode last 1 or 2 chars 140 | if n < 10 { 141 | curr -= 1; 142 | *buf_ptr.offset(curr) = (n as u8) + b'0'; 143 | } else { 144 | let d1 = n << 1; 145 | curr -= 2; 146 | ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); 147 | } 148 | 149 | bytes.extend_from_slice(slice::from_raw_parts( 150 | buf_ptr.offset(curr), 151 | 41 - curr as usize, 152 | )); 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | 160 | #[test] 161 | fn test_write_content_length() { 162 | let mut bytes = BytesMut::new(); 163 | bytes.reserve(50); 164 | write_content_length(0, &mut bytes); 165 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); 166 | bytes.reserve(50); 167 | write_content_length(9, &mut bytes); 168 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); 169 | bytes.reserve(50); 170 | write_content_length(10, &mut bytes); 171 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); 172 | bytes.reserve(50); 173 | write_content_length(99, &mut bytes); 174 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); 175 | bytes.reserve(50); 176 | write_content_length(100, &mut bytes); 177 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); 178 | bytes.reserve(50); 179 | write_content_length(101, &mut bytes); 180 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); 181 | bytes.reserve(50); 182 | write_content_length(998, &mut bytes); 183 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); 184 | bytes.reserve(50); 185 | write_content_length(1000, &mut bytes); 186 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); 187 | bytes.reserve(50); 188 | write_content_length(1001, &mut bytes); 189 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); 190 | bytes.reserve(50); 191 | write_content_length(5909, &mut bytes); 192 | assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/header/common/content_range.rs: -------------------------------------------------------------------------------- 1 | use error::ParseError; 2 | use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, 3 | CONTENT_RANGE}; 4 | use std::fmt::{self, Display, Write}; 5 | use std::str::FromStr; 6 | 7 | header! { 8 | /// `Content-Range` header, defined in 9 | /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) 10 | (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] 11 | 12 | test_content_range { 13 | test_header!(test_bytes, 14 | vec![b"bytes 0-499/500"], 15 | Some(ContentRange(ContentRangeSpec::Bytes { 16 | range: Some((0, 499)), 17 | instance_length: Some(500) 18 | }))); 19 | 20 | test_header!(test_bytes_unknown_len, 21 | vec![b"bytes 0-499/*"], 22 | Some(ContentRange(ContentRangeSpec::Bytes { 23 | range: Some((0, 499)), 24 | instance_length: None 25 | }))); 26 | 27 | test_header!(test_bytes_unknown_range, 28 | vec![b"bytes */500"], 29 | Some(ContentRange(ContentRangeSpec::Bytes { 30 | range: None, 31 | instance_length: Some(500) 32 | }))); 33 | 34 | test_header!(test_unregistered, 35 | vec![b"seconds 1-2"], 36 | Some(ContentRange(ContentRangeSpec::Unregistered { 37 | unit: "seconds".to_owned(), 38 | resp: "1-2".to_owned() 39 | }))); 40 | 41 | test_header!(test_no_len, 42 | vec![b"bytes 0-499"], 43 | None::); 44 | 45 | test_header!(test_only_unit, 46 | vec![b"bytes"], 47 | None::); 48 | 49 | test_header!(test_end_less_than_start, 50 | vec![b"bytes 499-0/500"], 51 | None::); 52 | 53 | test_header!(test_blank, 54 | vec![b""], 55 | None::); 56 | 57 | test_header!(test_bytes_many_spaces, 58 | vec![b"bytes 1-2/500 3"], 59 | None::); 60 | 61 | test_header!(test_bytes_many_slashes, 62 | vec![b"bytes 1-2/500/600"], 63 | None::); 64 | 65 | test_header!(test_bytes_many_dashes, 66 | vec![b"bytes 1-2-3/500"], 67 | None::); 68 | 69 | } 70 | } 71 | 72 | /// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) 73 | /// 74 | /// # ABNF 75 | /// 76 | /// ```text 77 | /// Content-Range = byte-content-range 78 | /// / other-content-range 79 | /// 80 | /// byte-content-range = bytes-unit SP 81 | /// ( byte-range-resp / unsatisfied-range ) 82 | /// 83 | /// byte-range-resp = byte-range "/" ( complete-length / "*" ) 84 | /// byte-range = first-byte-pos "-" last-byte-pos 85 | /// unsatisfied-range = "*/" complete-length 86 | /// 87 | /// complete-length = 1*DIGIT 88 | /// 89 | /// other-content-range = other-range-unit SP other-range-resp 90 | /// other-range-resp = *CHAR 91 | /// ``` 92 | #[derive(PartialEq, Clone, Debug)] 93 | pub enum ContentRangeSpec { 94 | /// Byte range 95 | Bytes { 96 | /// First and last bytes of the range, omitted if request could not be 97 | /// satisfied 98 | range: Option<(u64, u64)>, 99 | 100 | /// Total length of the instance, can be omitted if unknown 101 | instance_length: Option, 102 | }, 103 | 104 | /// Custom range, with unit not registered at IANA 105 | Unregistered { 106 | /// other-range-unit 107 | unit: String, 108 | 109 | /// other-range-resp 110 | resp: String, 111 | }, 112 | } 113 | 114 | fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { 115 | let mut iter = s.splitn(2, separator); 116 | match (iter.next(), iter.next()) { 117 | (Some(a), Some(b)) => Some((a, b)), 118 | _ => None, 119 | } 120 | } 121 | 122 | impl FromStr for ContentRangeSpec { 123 | type Err = ParseError; 124 | 125 | fn from_str(s: &str) -> Result { 126 | let res = match split_in_two(s, ' ') { 127 | Some(("bytes", resp)) => { 128 | let (range, instance_length) = 129 | split_in_two(resp, '/').ok_or(ParseError::Header)?; 130 | 131 | let instance_length = if instance_length == "*" { 132 | None 133 | } else { 134 | Some(instance_length 135 | .parse() 136 | .map_err(|_| ParseError::Header)?) 137 | }; 138 | 139 | let range = if range == "*" { 140 | None 141 | } else { 142 | let (first_byte, last_byte) = 143 | split_in_two(range, '-').ok_or(ParseError::Header)?; 144 | let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; 145 | let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; 146 | if last_byte < first_byte { 147 | return Err(ParseError::Header); 148 | } 149 | Some((first_byte, last_byte)) 150 | }; 151 | 152 | ContentRangeSpec::Bytes { 153 | range, 154 | instance_length, 155 | } 156 | } 157 | Some((unit, resp)) => ContentRangeSpec::Unregistered { 158 | unit: unit.to_owned(), 159 | resp: resp.to_owned(), 160 | }, 161 | _ => return Err(ParseError::Header), 162 | }; 163 | Ok(res) 164 | } 165 | } 166 | 167 | impl Display for ContentRangeSpec { 168 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 169 | match *self { 170 | ContentRangeSpec::Bytes { 171 | range, 172 | instance_length, 173 | } => { 174 | f.write_str("bytes ")?; 175 | match range { 176 | Some((first_byte, last_byte)) => { 177 | write!(f, "{}-{}", first_byte, last_byte)?; 178 | } 179 | None => { 180 | f.write_str("*")?; 181 | } 182 | }; 183 | f.write_str("/")?; 184 | if let Some(v) = instance_length { 185 | write!(f, "{}", v) 186 | } else { 187 | f.write_str("*") 188 | } 189 | } 190 | ContentRangeSpec::Unregistered { 191 | ref unit, 192 | ref resp, 193 | } => { 194 | f.write_str(unit)?; 195 | f.write_str(" ")?; 196 | f.write_str(resp) 197 | } 198 | } 199 | } 200 | } 201 | 202 | impl IntoHeaderValue for ContentRangeSpec { 203 | type Error = InvalidHeaderValueBytes; 204 | 205 | fn try_into(self) -> Result { 206 | let mut writer = Writer::new(); 207 | let _ = write!(&mut writer, "{}", self); 208 | HeaderValue::from_shared(writer.take()) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use http::header::{self, HeaderName}; 2 | use server::Request; 3 | 4 | const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; 5 | const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; 6 | const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; 7 | 8 | /// `HttpRequest` connection information 9 | #[derive(Clone, Default)] 10 | pub struct ConnectionInfo { 11 | scheme: String, 12 | host: String, 13 | remote: Option, 14 | peer: Option, 15 | } 16 | 17 | impl ConnectionInfo { 18 | /// Create *ConnectionInfo* instance for a request. 19 | #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] 20 | pub fn update(&mut self, req: &Request) { 21 | let mut host = None; 22 | let mut scheme = None; 23 | let mut remote = None; 24 | let mut peer = None; 25 | 26 | // load forwarded header 27 | for hdr in req.headers().get_all(header::FORWARDED) { 28 | if let Ok(val) = hdr.to_str() { 29 | for pair in val.split(';') { 30 | for el in pair.split(',') { 31 | let mut items = el.trim().splitn(2, '='); 32 | if let Some(name) = items.next() { 33 | if let Some(val) = items.next() { 34 | match &name.to_lowercase() as &str { 35 | "for" => if remote.is_none() { 36 | remote = Some(val.trim()); 37 | }, 38 | "proto" => if scheme.is_none() { 39 | scheme = Some(val.trim()); 40 | }, 41 | "host" => if host.is_none() { 42 | host = Some(val.trim()); 43 | }, 44 | _ => (), 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | // scheme 54 | if scheme.is_none() { 55 | if let Some(h) = req 56 | .headers() 57 | .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) 58 | { 59 | if let Ok(h) = h.to_str() { 60 | scheme = h.split(',').next().map(|v| v.trim()); 61 | } 62 | } 63 | if scheme.is_none() { 64 | scheme = req.uri().scheme_part().map(|a| a.as_str()); 65 | if scheme.is_none() && req.server_settings().secure() { 66 | scheme = Some("https") 67 | } 68 | } 69 | } 70 | 71 | // host 72 | if host.is_none() { 73 | if let Some(h) = req 74 | .headers() 75 | .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) 76 | { 77 | if let Ok(h) = h.to_str() { 78 | host = h.split(',').next().map(|v| v.trim()); 79 | } 80 | } 81 | if host.is_none() { 82 | if let Some(h) = req.headers().get(header::HOST) { 83 | host = h.to_str().ok(); 84 | } 85 | if host.is_none() { 86 | host = req.uri().authority_part().map(|a| a.as_str()); 87 | if host.is_none() { 88 | host = Some(req.server_settings().host()); 89 | } 90 | } 91 | } 92 | } 93 | 94 | // remote addr 95 | if remote.is_none() { 96 | if let Some(h) = req 97 | .headers() 98 | .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) 99 | { 100 | if let Ok(h) = h.to_str() { 101 | remote = h.split(',').next().map(|v| v.trim()); 102 | } 103 | } 104 | if remote.is_none() { 105 | // get peeraddr from socketaddr 106 | peer = req.peer_addr().map(|addr| format!("{}", addr)); 107 | } 108 | } 109 | 110 | self.scheme = scheme.unwrap_or("http").to_owned(); 111 | self.host = host.unwrap_or("localhost").to_owned(); 112 | self.remote = remote.map(|s| s.to_owned()); 113 | self.peer = peer; 114 | } 115 | 116 | /// Scheme of the request. 117 | /// 118 | /// Scheme is resolved through the following headers, in this order: 119 | /// 120 | /// - Forwarded 121 | /// - X-Forwarded-Proto 122 | /// - Uri 123 | #[inline] 124 | pub fn scheme(&self) -> &str { 125 | &self.scheme 126 | } 127 | 128 | /// Hostname of the request. 129 | /// 130 | /// Hostname is resolved through the following headers, in this order: 131 | /// 132 | /// - Forwarded 133 | /// - X-Forwarded-Host 134 | /// - Host 135 | /// - Uri 136 | /// - Server hostname 137 | pub fn host(&self) -> &str { 138 | &self.host 139 | } 140 | 141 | /// Remote IP of client initiated HTTP request. 142 | /// 143 | /// The IP is resolved through the following headers, in this order: 144 | /// 145 | /// - Forwarded 146 | /// - X-Forwarded-For 147 | /// - peer name of opened socket 148 | #[inline] 149 | pub fn remote(&self) -> Option<&str> { 150 | if let Some(ref r) = self.remote { 151 | Some(r) 152 | } else if let Some(ref peer) = self.peer { 153 | Some(peer) 154 | } else { 155 | None 156 | } 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | use test::TestRequest; 164 | 165 | #[test] 166 | fn test_forwarded() { 167 | let req = TestRequest::default().request(); 168 | let mut info = ConnectionInfo::default(); 169 | info.update(&req); 170 | assert_eq!(info.scheme(), "http"); 171 | assert_eq!(info.host(), "localhost:8080"); 172 | 173 | let req = TestRequest::default() 174 | .header( 175 | header::FORWARDED, 176 | "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", 177 | ) 178 | .request(); 179 | 180 | let mut info = ConnectionInfo::default(); 181 | info.update(&req); 182 | assert_eq!(info.scheme(), "https"); 183 | assert_eq!(info.host(), "rust-lang.org"); 184 | assert_eq!(info.remote(), Some("192.0.2.60")); 185 | 186 | let req = TestRequest::default() 187 | .header(header::HOST, "rust-lang.org") 188 | .request(); 189 | 190 | let mut info = ConnectionInfo::default(); 191 | info.update(&req); 192 | assert_eq!(info.scheme(), "http"); 193 | assert_eq!(info.host(), "rust-lang.org"); 194 | assert_eq!(info.remote(), None); 195 | 196 | let req = TestRequest::default() 197 | .header(X_FORWARDED_FOR, "192.0.2.60") 198 | .request(); 199 | let mut info = ConnectionInfo::default(); 200 | info.update(&req); 201 | assert_eq!(info.remote(), Some("192.0.2.60")); 202 | 203 | let req = TestRequest::default() 204 | .header(X_FORWARDED_HOST, "192.0.2.60") 205 | .request(); 206 | let mut info = ConnectionInfo::default(); 207 | info.update(&req); 208 | assert_eq!(info.host(), "192.0.2.60"); 209 | assert_eq!(info.remote(), None); 210 | 211 | let req = TestRequest::default() 212 | .header(X_FORWARDED_PROTO, "https") 213 | .request(); 214 | let mut info = ConnectionInfo::default(); 215 | info.update(&req); 216 | assert_eq!(info.scheme(), "https"); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/serde_urlencoded/ser/pair.rs: -------------------------------------------------------------------------------- 1 | use super::super::ser::key::KeySink; 2 | use super::super::ser::part::PartSerializer; 3 | use super::super::ser::value::ValueSink; 4 | use super::super::ser::Error; 5 | use serde::ser; 6 | use std::borrow::Cow; 7 | use std::mem; 8 | use url::form_urlencoded::Serializer as UrlEncodedSerializer; 9 | use url::form_urlencoded::Target as UrlEncodedTarget; 10 | 11 | pub struct PairSerializer<'target, Target: 'target + UrlEncodedTarget> { 12 | urlencoder: &'target mut UrlEncodedSerializer, 13 | state: PairState, 14 | } 15 | 16 | impl<'target, Target> PairSerializer<'target, Target> 17 | where 18 | Target: 'target + UrlEncodedTarget, 19 | { 20 | pub fn new(urlencoder: &'target mut UrlEncodedSerializer) -> Self { 21 | PairSerializer { 22 | urlencoder, 23 | state: PairState::WaitingForKey, 24 | } 25 | } 26 | } 27 | 28 | impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> 29 | where 30 | Target: 'target + UrlEncodedTarget, 31 | { 32 | type Ok = (); 33 | type Error = Error; 34 | type SerializeSeq = ser::Impossible<(), Error>; 35 | type SerializeTuple = Self; 36 | type SerializeTupleStruct = ser::Impossible<(), Error>; 37 | type SerializeTupleVariant = ser::Impossible<(), Error>; 38 | type SerializeMap = ser::Impossible<(), Error>; 39 | type SerializeStruct = ser::Impossible<(), Error>; 40 | type SerializeStructVariant = ser::Impossible<(), Error>; 41 | 42 | fn serialize_bool(self, _v: bool) -> Result<(), Error> { 43 | Err(Error::unsupported_pair()) 44 | } 45 | 46 | fn serialize_i8(self, _v: i8) -> Result<(), Error> { 47 | Err(Error::unsupported_pair()) 48 | } 49 | 50 | fn serialize_i16(self, _v: i16) -> Result<(), Error> { 51 | Err(Error::unsupported_pair()) 52 | } 53 | 54 | fn serialize_i32(self, _v: i32) -> Result<(), Error> { 55 | Err(Error::unsupported_pair()) 56 | } 57 | 58 | fn serialize_i64(self, _v: i64) -> Result<(), Error> { 59 | Err(Error::unsupported_pair()) 60 | } 61 | 62 | fn serialize_u8(self, _v: u8) -> Result<(), Error> { 63 | Err(Error::unsupported_pair()) 64 | } 65 | 66 | fn serialize_u16(self, _v: u16) -> Result<(), Error> { 67 | Err(Error::unsupported_pair()) 68 | } 69 | 70 | fn serialize_u32(self, _v: u32) -> Result<(), Error> { 71 | Err(Error::unsupported_pair()) 72 | } 73 | 74 | fn serialize_u64(self, _v: u64) -> Result<(), Error> { 75 | Err(Error::unsupported_pair()) 76 | } 77 | 78 | fn serialize_f32(self, _v: f32) -> Result<(), Error> { 79 | Err(Error::unsupported_pair()) 80 | } 81 | 82 | fn serialize_f64(self, _v: f64) -> Result<(), Error> { 83 | Err(Error::unsupported_pair()) 84 | } 85 | 86 | fn serialize_char(self, _v: char) -> Result<(), Error> { 87 | Err(Error::unsupported_pair()) 88 | } 89 | 90 | fn serialize_str(self, _value: &str) -> Result<(), Error> { 91 | Err(Error::unsupported_pair()) 92 | } 93 | 94 | fn serialize_bytes(self, _value: &[u8]) -> Result<(), Error> { 95 | Err(Error::unsupported_pair()) 96 | } 97 | 98 | fn serialize_unit(self) -> Result<(), Error> { 99 | Err(Error::unsupported_pair()) 100 | } 101 | 102 | fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> { 103 | Err(Error::unsupported_pair()) 104 | } 105 | 106 | fn serialize_unit_variant( 107 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 108 | ) -> Result<(), Error> { 109 | Err(Error::unsupported_pair()) 110 | } 111 | 112 | fn serialize_newtype_struct( 113 | self, _name: &'static str, value: &T, 114 | ) -> Result<(), Error> { 115 | value.serialize(self) 116 | } 117 | 118 | fn serialize_newtype_variant( 119 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 120 | _value: &T, 121 | ) -> Result<(), Error> { 122 | Err(Error::unsupported_pair()) 123 | } 124 | 125 | fn serialize_none(self) -> Result<(), Error> { 126 | Ok(()) 127 | } 128 | 129 | fn serialize_some(self, value: &T) -> Result<(), Error> { 130 | value.serialize(self) 131 | } 132 | 133 | fn serialize_seq(self, _len: Option) -> Result { 134 | Err(Error::unsupported_pair()) 135 | } 136 | 137 | fn serialize_tuple(self, len: usize) -> Result { 138 | if len == 2 { 139 | Ok(self) 140 | } else { 141 | Err(Error::unsupported_pair()) 142 | } 143 | } 144 | 145 | fn serialize_tuple_struct( 146 | self, _name: &'static str, _len: usize, 147 | ) -> Result { 148 | Err(Error::unsupported_pair()) 149 | } 150 | 151 | fn serialize_tuple_variant( 152 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 153 | _len: usize, 154 | ) -> Result { 155 | Err(Error::unsupported_pair()) 156 | } 157 | 158 | fn serialize_map(self, _len: Option) -> Result { 159 | Err(Error::unsupported_pair()) 160 | } 161 | 162 | fn serialize_struct( 163 | self, _name: &'static str, _len: usize, 164 | ) -> Result { 165 | Err(Error::unsupported_pair()) 166 | } 167 | 168 | fn serialize_struct_variant( 169 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 170 | _len: usize, 171 | ) -> Result { 172 | Err(Error::unsupported_pair()) 173 | } 174 | } 175 | 176 | impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> 177 | where 178 | Target: 'target + UrlEncodedTarget, 179 | { 180 | type Ok = (); 181 | type Error = Error; 182 | 183 | fn serialize_element( 184 | &mut self, value: &T, 185 | ) -> Result<(), Error> { 186 | match mem::replace(&mut self.state, PairState::Done) { 187 | PairState::WaitingForKey => { 188 | let key_sink = KeySink::new(|key| Ok(key.into())); 189 | let key_serializer = PartSerializer::new(key_sink); 190 | self.state = PairState::WaitingForValue { 191 | key: value.serialize(key_serializer)?, 192 | }; 193 | Ok(()) 194 | } 195 | PairState::WaitingForValue { key } => { 196 | let result = { 197 | let value_sink = ValueSink::new(self.urlencoder, &key); 198 | let value_serializer = PartSerializer::new(value_sink); 199 | value.serialize(value_serializer) 200 | }; 201 | if result.is_ok() { 202 | self.state = PairState::Done; 203 | } else { 204 | self.state = PairState::WaitingForValue { key }; 205 | } 206 | result 207 | } 208 | PairState::Done => Err(Error::done()), 209 | } 210 | } 211 | 212 | fn end(self) -> Result<(), Error> { 213 | if let PairState::Done = self.state { 214 | Ok(()) 215 | } else { 216 | Err(Error::not_done()) 217 | } 218 | } 219 | } 220 | 221 | enum PairState { 222 | WaitingForKey, 223 | WaitingForValue { key: Cow<'static, str> }, 224 | Done, 225 | } 226 | 227 | impl Error { 228 | fn done() -> Self { 229 | Error::Custom("this pair has already been serialized".into()) 230 | } 231 | 232 | fn not_done() -> Self { 233 | Error::Custom("this pair has not yet been serialized".into()) 234 | } 235 | 236 | fn unsupported_pair() -> Self { 237 | Error::Custom("unsupported pair".into()) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/server/message.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, Ref, RefCell, RefMut}; 2 | use std::collections::VecDeque; 3 | use std::net::SocketAddr; 4 | use std::rc::Rc; 5 | 6 | use http::{header, HeaderMap, Method, Uri, Version}; 7 | 8 | use extensions::Extensions; 9 | use httpmessage::HttpMessage; 10 | use info::ConnectionInfo; 11 | use payload::Payload; 12 | use server::ServerSettings; 13 | use uri::Url as InnerUrl; 14 | 15 | bitflags! { 16 | pub(crate) struct MessageFlags: u8 { 17 | const KEEPALIVE = 0b0000_0001; 18 | const CONN_INFO = 0b0000_0010; 19 | } 20 | } 21 | 22 | /// Request's context 23 | pub struct Request { 24 | pub(crate) inner: Rc, 25 | } 26 | 27 | pub(crate) struct InnerRequest { 28 | pub(crate) version: Version, 29 | pub(crate) method: Method, 30 | pub(crate) url: InnerUrl, 31 | pub(crate) flags: Cell, 32 | pub(crate) headers: HeaderMap, 33 | pub(crate) extensions: RefCell, 34 | pub(crate) addr: Option, 35 | pub(crate) info: RefCell, 36 | pub(crate) payload: RefCell>, 37 | pub(crate) settings: ServerSettings, 38 | pub(crate) stream_extensions: Option>, 39 | pool: &'static RequestPool, 40 | } 41 | 42 | impl InnerRequest { 43 | #[inline] 44 | /// Reset request instance 45 | pub fn reset(&mut self) { 46 | self.headers.clear(); 47 | self.extensions.borrow_mut().clear(); 48 | self.flags.set(MessageFlags::empty()); 49 | *self.payload.borrow_mut() = None; 50 | } 51 | } 52 | 53 | impl HttpMessage for Request { 54 | type Stream = Payload; 55 | 56 | fn headers(&self) -> &HeaderMap { 57 | &self.inner.headers 58 | } 59 | 60 | #[inline] 61 | fn payload(&self) -> Payload { 62 | if let Some(payload) = self.inner.payload.borrow_mut().take() { 63 | payload 64 | } else { 65 | Payload::empty() 66 | } 67 | } 68 | } 69 | 70 | impl Request { 71 | /// Create new RequestContext instance 72 | pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request { 73 | Request { 74 | inner: Rc::new(InnerRequest { 75 | pool, 76 | settings, 77 | method: Method::GET, 78 | url: InnerUrl::default(), 79 | version: Version::HTTP_11, 80 | headers: HeaderMap::with_capacity(16), 81 | flags: Cell::new(MessageFlags::empty()), 82 | addr: None, 83 | info: RefCell::new(ConnectionInfo::default()), 84 | payload: RefCell::new(None), 85 | extensions: RefCell::new(Extensions::new()), 86 | stream_extensions: None, 87 | }), 88 | } 89 | } 90 | 91 | #[inline] 92 | pub(crate) fn inner(&self) -> &InnerRequest { 93 | self.inner.as_ref() 94 | } 95 | 96 | #[inline] 97 | pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest { 98 | Rc::get_mut(&mut self.inner).expect("Multiple copies exist") 99 | } 100 | 101 | #[inline] 102 | pub(crate) fn url(&self) -> &InnerUrl { 103 | &self.inner().url 104 | } 105 | 106 | /// Read the Request Uri. 107 | #[inline] 108 | pub fn uri(&self) -> &Uri { 109 | self.inner().url.uri() 110 | } 111 | 112 | /// Read the Request method. 113 | #[inline] 114 | pub fn method(&self) -> &Method { 115 | &self.inner().method 116 | } 117 | 118 | /// Read the Request Version. 119 | #[inline] 120 | pub fn version(&self) -> Version { 121 | self.inner().version 122 | } 123 | 124 | /// The target path of this Request. 125 | #[inline] 126 | pub fn path(&self) -> &str { 127 | self.inner().url.path() 128 | } 129 | 130 | #[inline] 131 | /// Returns Request's headers. 132 | pub fn headers(&self) -> &HeaderMap { 133 | &self.inner().headers 134 | } 135 | 136 | #[inline] 137 | /// Returns mutable Request's headers. 138 | pub fn headers_mut(&mut self) -> &mut HeaderMap { 139 | &mut self.inner_mut().headers 140 | } 141 | 142 | /// Peer socket address 143 | /// 144 | /// Peer address is actual socket address, if proxy is used in front of 145 | /// actix http server, then peer address would be address of this proxy. 146 | /// 147 | /// To get client connection information `connection_info()` method should 148 | /// be used. 149 | pub fn peer_addr(&self) -> Option { 150 | self.inner().addr 151 | } 152 | 153 | /// Checks if a connection should be kept alive. 154 | #[inline] 155 | pub fn keep_alive(&self) -> bool { 156 | self.inner().flags.get().contains(MessageFlags::KEEPALIVE) 157 | } 158 | 159 | /// Request extensions 160 | #[inline] 161 | pub fn extensions(&self) -> Ref { 162 | self.inner().extensions.borrow() 163 | } 164 | 165 | /// Mutable reference to a the request's extensions 166 | #[inline] 167 | pub fn extensions_mut(&self) -> RefMut { 168 | self.inner().extensions.borrow_mut() 169 | } 170 | 171 | /// Check if request requires connection upgrade 172 | pub fn upgrade(&self) -> bool { 173 | if let Some(conn) = self.inner().headers.get(header::CONNECTION) { 174 | if let Ok(s) = conn.to_str() { 175 | return s.to_lowercase().contains("upgrade"); 176 | } 177 | } 178 | self.inner().method == Method::CONNECT 179 | } 180 | 181 | /// Get *ConnectionInfo* for the correct request. 182 | pub fn connection_info(&self) -> Ref { 183 | if self.inner().flags.get().contains(MessageFlags::CONN_INFO) { 184 | self.inner().info.borrow() 185 | } else { 186 | let mut flags = self.inner().flags.get(); 187 | flags.insert(MessageFlags::CONN_INFO); 188 | self.inner().flags.set(flags); 189 | self.inner().info.borrow_mut().update(self); 190 | self.inner().info.borrow() 191 | } 192 | } 193 | 194 | /// Io stream extensions 195 | #[inline] 196 | pub fn stream_extensions(&self) -> Option<&Extensions> { 197 | self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) 198 | } 199 | 200 | /// Server settings 201 | #[inline] 202 | pub fn server_settings(&self) -> &ServerSettings { 203 | &self.inner().settings 204 | } 205 | 206 | pub(crate) fn clone(&self) -> Self { 207 | Request { 208 | inner: self.inner.clone(), 209 | } 210 | } 211 | 212 | pub(crate) fn release(self) { 213 | let mut inner = self.inner; 214 | if let Some(r) = Rc::get_mut(&mut inner) { 215 | r.reset(); 216 | } else { 217 | return; 218 | } 219 | inner.pool.release(inner); 220 | } 221 | } 222 | 223 | pub(crate) struct RequestPool( 224 | RefCell>>, 225 | RefCell, 226 | ); 227 | 228 | thread_local!(static POOL: &'static RequestPool = RequestPool::create()); 229 | 230 | impl RequestPool { 231 | fn create() -> &'static RequestPool { 232 | let pool = RequestPool( 233 | RefCell::new(VecDeque::with_capacity(128)), 234 | RefCell::new(ServerSettings::default()), 235 | ); 236 | Box::leak(Box::new(pool)) 237 | } 238 | 239 | pub fn pool(settings: ServerSettings) -> &'static RequestPool { 240 | POOL.with(|p| { 241 | *p.1.borrow_mut() = settings; 242 | *p 243 | }) 244 | } 245 | 246 | #[inline] 247 | pub fn get(pool: &'static RequestPool) -> Request { 248 | if let Some(msg) = pool.0.borrow_mut().pop_front() { 249 | Request { inner: msg } 250 | } else { 251 | Request::new(pool, pool.1.borrow().clone()) 252 | } 253 | } 254 | 255 | #[inline] 256 | /// Release request instance 257 | pub fn release(&self, msg: Rc) { 258 | let v = &mut self.0.borrow_mut(); 259 | if v.len() < 128 { 260 | v.push_front(msg); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/with.rs: -------------------------------------------------------------------------------- 1 | use futures::{Async, Future, Poll}; 2 | use std::marker::PhantomData; 3 | use std::rc::Rc; 4 | 5 | use error::Error; 6 | use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; 7 | use httprequest::HttpRequest; 8 | use httpresponse::HttpResponse; 9 | 10 | pub(crate) struct With 11 | where 12 | F: Fn(T) -> R, 13 | T: FromRequest, 14 | S: 'static, 15 | { 16 | hnd: Rc, 17 | cfg: Rc, 18 | _s: PhantomData, 19 | } 20 | 21 | impl With 22 | where 23 | F: Fn(T) -> R, 24 | T: FromRequest, 25 | S: 'static, 26 | { 27 | pub fn new(f: F, cfg: T::Config) -> Self { 28 | With { 29 | cfg: Rc::new(cfg), 30 | hnd: Rc::new(f), 31 | _s: PhantomData, 32 | } 33 | } 34 | } 35 | 36 | impl Handler for With 37 | where 38 | F: Fn(T) -> R + 'static, 39 | R: Responder + 'static, 40 | T: FromRequest + 'static, 41 | S: 'static, 42 | { 43 | type Result = AsyncResult; 44 | 45 | fn handle(&self, req: &HttpRequest) -> Self::Result { 46 | let mut fut = WithHandlerFut { 47 | req: req.clone(), 48 | started: false, 49 | hnd: Rc::clone(&self.hnd), 50 | cfg: self.cfg.clone(), 51 | fut1: None, 52 | fut2: None, 53 | }; 54 | 55 | match fut.poll() { 56 | Ok(Async::Ready(resp)) => AsyncResult::ok(resp), 57 | Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), 58 | Err(e) => AsyncResult::err(e), 59 | } 60 | } 61 | } 62 | 63 | struct WithHandlerFut 64 | where 65 | F: Fn(T) -> R, 66 | R: Responder, 67 | T: FromRequest + 'static, 68 | S: 'static, 69 | { 70 | started: bool, 71 | hnd: Rc, 72 | cfg: Rc, 73 | req: HttpRequest, 74 | fut1: Option>>, 75 | fut2: Option>>, 76 | } 77 | 78 | impl Future for WithHandlerFut 79 | where 80 | F: Fn(T) -> R, 81 | R: Responder + 'static, 82 | T: FromRequest + 'static, 83 | S: 'static, 84 | { 85 | type Item = HttpResponse; 86 | type Error = Error; 87 | 88 | fn poll(&mut self) -> Poll { 89 | if let Some(ref mut fut) = self.fut2 { 90 | return fut.poll(); 91 | } 92 | 93 | let item = if !self.started { 94 | self.started = true; 95 | let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); 96 | match reply.into() { 97 | AsyncResultItem::Err(err) => return Err(err), 98 | AsyncResultItem::Ok(msg) => msg, 99 | AsyncResultItem::Future(fut) => { 100 | self.fut1 = Some(fut); 101 | return self.poll(); 102 | } 103 | } 104 | } else { 105 | match self.fut1.as_mut().unwrap().poll()? { 106 | Async::Ready(item) => item, 107 | Async::NotReady => return Ok(Async::NotReady), 108 | } 109 | }; 110 | 111 | let item = match (*self.hnd)(item).respond_to(&self.req) { 112 | Ok(item) => item.into(), 113 | Err(e) => return Err(e.into()), 114 | }; 115 | 116 | match item.into() { 117 | AsyncResultItem::Err(err) => Err(err), 118 | AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), 119 | AsyncResultItem::Future(fut) => { 120 | self.fut2 = Some(fut); 121 | self.poll() 122 | } 123 | } 124 | } 125 | } 126 | 127 | pub(crate) struct WithAsync 128 | where 129 | F: Fn(T) -> R, 130 | R: Future, 131 | I: Responder, 132 | E: Into, 133 | T: FromRequest, 134 | S: 'static, 135 | { 136 | hnd: Rc, 137 | cfg: Rc, 138 | _s: PhantomData, 139 | } 140 | 141 | impl WithAsync 142 | where 143 | F: Fn(T) -> R, 144 | R: Future, 145 | I: Responder, 146 | E: Into, 147 | T: FromRequest, 148 | S: 'static, 149 | { 150 | pub fn new(f: F, cfg: T::Config) -> Self { 151 | WithAsync { 152 | cfg: Rc::new(cfg), 153 | hnd: Rc::new(f), 154 | _s: PhantomData, 155 | } 156 | } 157 | } 158 | 159 | impl Handler for WithAsync 160 | where 161 | F: Fn(T) -> R + 'static, 162 | R: Future + 'static, 163 | I: Responder + 'static, 164 | E: Into + 'static, 165 | T: FromRequest + 'static, 166 | S: 'static, 167 | { 168 | type Result = AsyncResult; 169 | 170 | fn handle(&self, req: &HttpRequest) -> Self::Result { 171 | let mut fut = WithAsyncHandlerFut { 172 | req: req.clone(), 173 | started: false, 174 | hnd: Rc::clone(&self.hnd), 175 | cfg: Rc::clone(&self.cfg), 176 | fut1: None, 177 | fut2: None, 178 | fut3: None, 179 | }; 180 | 181 | match fut.poll() { 182 | Ok(Async::Ready(resp)) => AsyncResult::ok(resp), 183 | Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), 184 | Err(e) => AsyncResult::err(e), 185 | } 186 | } 187 | } 188 | 189 | struct WithAsyncHandlerFut 190 | where 191 | F: Fn(T) -> R, 192 | R: Future + 'static, 193 | I: Responder + 'static, 194 | E: Into + 'static, 195 | T: FromRequest + 'static, 196 | S: 'static, 197 | { 198 | started: bool, 199 | hnd: Rc, 200 | cfg: Rc, 201 | req: HttpRequest, 202 | fut1: Option>>, 203 | fut2: Option, 204 | fut3: Option>>, 205 | } 206 | 207 | impl Future for WithAsyncHandlerFut 208 | where 209 | F: Fn(T) -> R, 210 | R: Future + 'static, 211 | I: Responder + 'static, 212 | E: Into + 'static, 213 | T: FromRequest + 'static, 214 | S: 'static, 215 | { 216 | type Item = HttpResponse; 217 | type Error = Error; 218 | 219 | fn poll(&mut self) -> Poll { 220 | if let Some(ref mut fut) = self.fut3 { 221 | return fut.poll(); 222 | } 223 | 224 | if self.fut2.is_some() { 225 | return match self.fut2.as_mut().unwrap().poll() { 226 | Ok(Async::NotReady) => Ok(Async::NotReady), 227 | Ok(Async::Ready(r)) => match r.respond_to(&self.req) { 228 | Ok(r) => match r.into().into() { 229 | AsyncResultItem::Err(err) => Err(err), 230 | AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), 231 | AsyncResultItem::Future(fut) => { 232 | self.fut3 = Some(fut); 233 | self.poll() 234 | } 235 | }, 236 | Err(e) => Err(e.into()), 237 | }, 238 | Err(e) => Err(e.into()), 239 | }; 240 | } 241 | 242 | let item = if !self.started { 243 | self.started = true; 244 | let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); 245 | match reply.into() { 246 | AsyncResultItem::Err(err) => return Err(err), 247 | AsyncResultItem::Ok(msg) => msg, 248 | AsyncResultItem::Future(fut) => { 249 | self.fut1 = Some(fut); 250 | return self.poll(); 251 | } 252 | } 253 | } else { 254 | match self.fut1.as_mut().unwrap().poll()? { 255 | Async::Ready(item) => item, 256 | Async::NotReady => return Ok(Async::NotReady), 257 | } 258 | }; 259 | 260 | self.fut2 = Some((*self.hnd)(item)); 261 | self.poll() 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | extern crate actix; 2 | 3 | use futures::sync::oneshot; 4 | use futures::sync::oneshot::Sender; 5 | use futures::{Async, Future, Poll}; 6 | use smallvec::SmallVec; 7 | use std::marker::PhantomData; 8 | 9 | use self::actix::dev::{ 10 | AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, 11 | }; 12 | use self::actix::fut::ActorFuture; 13 | use self::actix::{ 14 | Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, 15 | }; 16 | 17 | use body::{Binary, Body}; 18 | use error::{Error, ErrorInternalServerError}; 19 | use httprequest::HttpRequest; 20 | 21 | pub trait ActorHttpContext: 'static { 22 | fn disconnected(&mut self); 23 | fn poll(&mut self) -> Poll>, Error>; 24 | } 25 | 26 | #[derive(Debug)] 27 | pub enum Frame { 28 | Chunk(Option), 29 | Drain(oneshot::Sender<()>), 30 | } 31 | 32 | impl Frame { 33 | pub fn len(&self) -> usize { 34 | match *self { 35 | Frame::Chunk(Some(ref bin)) => bin.len(), 36 | _ => 0, 37 | } 38 | } 39 | } 40 | 41 | /// Execution context for http actors 42 | pub struct HttpContext 43 | where 44 | A: Actor>, 45 | { 46 | inner: ContextParts, 47 | stream: Option>, 48 | request: HttpRequest, 49 | disconnected: bool, 50 | } 51 | 52 | impl ActorContext for HttpContext 53 | where 54 | A: Actor, 55 | { 56 | fn stop(&mut self) { 57 | self.inner.stop(); 58 | } 59 | fn terminate(&mut self) { 60 | self.inner.terminate() 61 | } 62 | fn state(&self) -> ActorState { 63 | self.inner.state() 64 | } 65 | } 66 | 67 | impl AsyncContext for HttpContext 68 | where 69 | A: Actor, 70 | { 71 | #[inline] 72 | fn spawn(&mut self, fut: F) -> SpawnHandle 73 | where 74 | F: ActorFuture + 'static, 75 | { 76 | self.inner.spawn(fut) 77 | } 78 | #[inline] 79 | fn wait(&mut self, fut: F) 80 | where 81 | F: ActorFuture + 'static, 82 | { 83 | self.inner.wait(fut) 84 | } 85 | #[doc(hidden)] 86 | #[inline] 87 | fn waiting(&self) -> bool { 88 | self.inner.waiting() 89 | || self.inner.state() == ActorState::Stopping 90 | || self.inner.state() == ActorState::Stopped 91 | } 92 | #[inline] 93 | fn cancel_future(&mut self, handle: SpawnHandle) -> bool { 94 | self.inner.cancel_future(handle) 95 | } 96 | #[inline] 97 | fn address(&self) -> Addr { 98 | self.inner.address() 99 | } 100 | } 101 | 102 | impl HttpContext 103 | where 104 | A: Actor, 105 | { 106 | #[inline] 107 | /// Create a new HTTP Context from a request and an actor 108 | pub fn create(req: HttpRequest, actor: A) -> Body { 109 | let mb = Mailbox::default(); 110 | let ctx = HttpContext { 111 | inner: ContextParts::new(mb.sender_producer()), 112 | stream: None, 113 | request: req, 114 | disconnected: false, 115 | }; 116 | Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb))) 117 | } 118 | 119 | /// Create a new HTTP Context 120 | pub fn with_factory(req: HttpRequest, f: F) -> Body 121 | where 122 | F: FnOnce(&mut Self) -> A + 'static, 123 | { 124 | let mb = Mailbox::default(); 125 | let mut ctx = HttpContext { 126 | inner: ContextParts::new(mb.sender_producer()), 127 | stream: None, 128 | request: req, 129 | disconnected: false, 130 | }; 131 | 132 | let act = f(&mut ctx); 133 | Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb))) 134 | } 135 | } 136 | 137 | impl HttpContext 138 | where 139 | A: Actor, 140 | { 141 | /// Shared application state 142 | #[inline] 143 | pub fn state(&self) -> &S { 144 | self.request.state() 145 | } 146 | 147 | /// Incoming request 148 | #[inline] 149 | pub fn request(&mut self) -> &mut HttpRequest { 150 | &mut self.request 151 | } 152 | 153 | /// Write payload 154 | #[inline] 155 | pub fn write>(&mut self, data: B) { 156 | if !self.disconnected { 157 | self.add_frame(Frame::Chunk(Some(data.into()))); 158 | } else { 159 | warn!("Trying to write to disconnected response"); 160 | } 161 | } 162 | 163 | /// Indicate end of streaming payload. Also this method calls `Self::close`. 164 | #[inline] 165 | pub fn write_eof(&mut self) { 166 | self.add_frame(Frame::Chunk(None)); 167 | } 168 | 169 | /// Returns drain future 170 | pub fn drain(&mut self) -> Drain { 171 | let (tx, rx) = oneshot::channel(); 172 | self.add_frame(Frame::Drain(tx)); 173 | Drain::new(rx) 174 | } 175 | 176 | /// Check if connection still open 177 | #[inline] 178 | pub fn connected(&self) -> bool { 179 | !self.disconnected 180 | } 181 | 182 | #[inline] 183 | fn add_frame(&mut self, frame: Frame) { 184 | if self.stream.is_none() { 185 | self.stream = Some(SmallVec::new()); 186 | } 187 | if let Some(s) = self.stream.as_mut() { 188 | s.push(frame) 189 | } 190 | } 191 | 192 | /// Handle of the running future 193 | /// 194 | /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. 195 | pub fn handle(&self) -> SpawnHandle { 196 | self.inner.curr_handle() 197 | } 198 | } 199 | 200 | impl AsyncContextParts for HttpContext 201 | where 202 | A: Actor, 203 | { 204 | fn parts(&mut self) -> &mut ContextParts { 205 | &mut self.inner 206 | } 207 | } 208 | 209 | struct HttpContextFut 210 | where 211 | A: Actor>, 212 | { 213 | fut: ContextFut>, 214 | } 215 | 216 | impl HttpContextFut 217 | where 218 | A: Actor>, 219 | { 220 | fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { 221 | let fut = ContextFut::new(ctx, act, mailbox); 222 | HttpContextFut { fut } 223 | } 224 | } 225 | 226 | impl ActorHttpContext for HttpContextFut 227 | where 228 | A: Actor>, 229 | S: 'static, 230 | { 231 | #[inline] 232 | fn disconnected(&mut self) { 233 | self.fut.ctx().disconnected = true; 234 | self.fut.ctx().stop(); 235 | } 236 | 237 | fn poll(&mut self) -> Poll>, Error> { 238 | if self.fut.alive() { 239 | match self.fut.poll() { 240 | Ok(Async::NotReady) | Ok(Async::Ready(())) => (), 241 | Err(_) => return Err(ErrorInternalServerError("error")), 242 | } 243 | } 244 | 245 | // frames 246 | if let Some(data) = self.fut.ctx().stream.take() { 247 | Ok(Async::Ready(Some(data))) 248 | } else if self.fut.alive() { 249 | Ok(Async::NotReady) 250 | } else { 251 | Ok(Async::Ready(None)) 252 | } 253 | } 254 | } 255 | 256 | impl ToEnvelope for HttpContext 257 | where 258 | A: Actor> + Handler, 259 | M: Message + Send + 'static, 260 | M::Result: Send, 261 | { 262 | fn pack(msg: M, tx: Option>) -> Envelope { 263 | Envelope::new(msg, tx) 264 | } 265 | } 266 | 267 | /// Consume a future 268 | pub struct Drain { 269 | fut: oneshot::Receiver<()>, 270 | _a: PhantomData, 271 | } 272 | 273 | impl Drain { 274 | /// Create a drain from a future 275 | pub fn new(fut: oneshot::Receiver<()>) -> Self { 276 | Drain { 277 | fut, 278 | _a: PhantomData, 279 | } 280 | } 281 | } 282 | 283 | impl ActorFuture for Drain { 284 | type Item = (); 285 | type Error = (); 286 | type Actor = A; 287 | 288 | #[inline] 289 | fn poll( 290 | &mut self, _: &mut A, _: &mut ::Context, 291 | ) -> Poll { 292 | self.fut.poll().map_err(|_| ()) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/client/parser.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use futures::{Async, Poll}; 5 | use http::header::{self, HeaderName, HeaderValue}; 6 | use http::{HeaderMap, StatusCode, Version}; 7 | use httparse; 8 | 9 | use error::{ParseError, PayloadError}; 10 | 11 | use server::h1decoder::{EncodingDecoder, HeaderIndex}; 12 | use server::IoStream; 13 | 14 | use super::response::ClientMessage; 15 | use super::ClientResponse; 16 | 17 | const MAX_BUFFER_SIZE: usize = 131_072; 18 | const MAX_HEADERS: usize = 96; 19 | 20 | #[derive(Default)] 21 | pub struct HttpResponseParser { 22 | decoder: Option, 23 | } 24 | 25 | #[derive(Debug, Fail)] 26 | pub enum HttpResponseParserError { 27 | /// Server disconnected 28 | #[fail(display = "Server disconnected")] 29 | Disconnect, 30 | #[fail(display = "{}", _0)] 31 | Error(#[cause] ParseError), 32 | } 33 | 34 | impl HttpResponseParser { 35 | pub fn parse( 36 | &mut self, io: &mut T, buf: &mut BytesMut, 37 | ) -> Poll 38 | where 39 | T: IoStream, 40 | { 41 | // if buf is empty parse_message will always return NotReady, let's avoid that 42 | if buf.is_empty() { 43 | match io.read_available(buf) { 44 | Ok(Async::Ready(true)) => { 45 | return Err(HttpResponseParserError::Disconnect) 46 | } 47 | Ok(Async::Ready(false)) => (), 48 | Ok(Async::NotReady) => return Ok(Async::NotReady), 49 | Err(err) => return Err(HttpResponseParserError::Error(err.into())), 50 | } 51 | } 52 | 53 | loop { 54 | match HttpResponseParser::parse_message(buf) 55 | .map_err(HttpResponseParserError::Error)? 56 | { 57 | Async::Ready((msg, decoder)) => { 58 | self.decoder = decoder; 59 | return Ok(Async::Ready(msg)); 60 | } 61 | Async::NotReady => { 62 | if buf.capacity() >= MAX_BUFFER_SIZE { 63 | return Err(HttpResponseParserError::Error(ParseError::TooLarge)); 64 | } 65 | match io.read_available(buf) { 66 | Ok(Async::Ready(true)) => { 67 | return Err(HttpResponseParserError::Disconnect) 68 | } 69 | Ok(Async::Ready(false)) => (), 70 | Ok(Async::NotReady) => return Ok(Async::NotReady), 71 | Err(err) => { 72 | return Err(HttpResponseParserError::Error(err.into())) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | pub fn parse_payload( 81 | &mut self, io: &mut T, buf: &mut BytesMut, 82 | ) -> Poll, PayloadError> 83 | where 84 | T: IoStream, 85 | { 86 | if self.decoder.is_some() { 87 | loop { 88 | // read payload 89 | let (not_ready, stream_finished) = match io.read_available(buf) { 90 | Ok(Async::Ready(true)) => (false, true), 91 | Ok(Async::Ready(false)) => (false, false), 92 | Ok(Async::NotReady) => (true, false), 93 | Err(err) => return Err(err.into()), 94 | }; 95 | 96 | match self.decoder.as_mut().unwrap().decode(buf) { 97 | Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))), 98 | Ok(Async::Ready(None)) => { 99 | self.decoder.take(); 100 | return Ok(Async::Ready(None)); 101 | } 102 | Ok(Async::NotReady) => { 103 | if not_ready { 104 | return Ok(Async::NotReady); 105 | } 106 | if stream_finished { 107 | return Err(PayloadError::Incomplete); 108 | } 109 | } 110 | Err(err) => return Err(err.into()), 111 | } 112 | } 113 | } else { 114 | Ok(Async::Ready(None)) 115 | } 116 | } 117 | 118 | fn parse_message( 119 | buf: &mut BytesMut, 120 | ) -> Poll<(ClientResponse, Option), ParseError> { 121 | // Unsafe: we read only this data only after httparse parses headers into. 122 | // performance bump for pipeline benchmarks. 123 | let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; 124 | 125 | let (len, version, status, headers_len) = { 126 | let mut parsed: [httparse::Header; MAX_HEADERS] = 127 | unsafe { mem::uninitialized() }; 128 | 129 | let mut resp = httparse::Response::new(&mut parsed); 130 | match resp.parse(buf)? { 131 | httparse::Status::Complete(len) => { 132 | let version = if resp.version.unwrap_or(1) == 1 { 133 | Version::HTTP_11 134 | } else { 135 | Version::HTTP_10 136 | }; 137 | HeaderIndex::record(buf, resp.headers, &mut headers); 138 | let status = StatusCode::from_u16(resp.code.unwrap()) 139 | .map_err(|_| ParseError::Status)?; 140 | 141 | (len, version, status, resp.headers.len()) 142 | } 143 | httparse::Status::Partial => return Ok(Async::NotReady), 144 | } 145 | }; 146 | 147 | let slice = buf.split_to(len).freeze(); 148 | 149 | // convert headers 150 | let mut hdrs = HeaderMap::new(); 151 | for idx in headers[..headers_len].iter() { 152 | if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { 153 | // Unsafe: httparse check header value for valid utf-8 154 | let value = unsafe { 155 | HeaderValue::from_shared_unchecked( 156 | slice.slice(idx.value.0, idx.value.1), 157 | ) 158 | }; 159 | hdrs.append(name, value); 160 | } else { 161 | return Err(ParseError::Header); 162 | } 163 | } 164 | 165 | let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { 166 | Some(EncodingDecoder::eof()) 167 | } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { 168 | // Content-Length 169 | if let Ok(s) = len.to_str() { 170 | if let Ok(len) = s.parse::() { 171 | Some(EncodingDecoder::length(len)) 172 | } else { 173 | debug!("illegal Content-Length: {:?}", len); 174 | return Err(ParseError::Header); 175 | } 176 | } else { 177 | debug!("illegal Content-Length: {:?}", len); 178 | return Err(ParseError::Header); 179 | } 180 | } else if chunked(&hdrs)? { 181 | // Chunked encoding 182 | Some(EncodingDecoder::chunked()) 183 | } else { 184 | None 185 | }; 186 | 187 | if let Some(decoder) = decoder { 188 | Ok(Async::Ready(( 189 | ClientResponse::new(ClientMessage { 190 | status, 191 | version, 192 | headers: hdrs, 193 | cookies: None, 194 | }), 195 | Some(decoder), 196 | ))) 197 | } else { 198 | Ok(Async::Ready(( 199 | ClientResponse::new(ClientMessage { 200 | status, 201 | version, 202 | headers: hdrs, 203 | cookies: None, 204 | }), 205 | None, 206 | ))) 207 | } 208 | } 209 | } 210 | 211 | /// Check if request has chunked transfer encoding 212 | pub fn chunked(headers: &HeaderMap) -> Result { 213 | if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) { 214 | if let Ok(s) = encodings.to_str() { 215 | Ok(s.to_lowercase().contains("chunked")) 216 | } else { 217 | Err(ParseError::Header) 218 | } 219 | } else { 220 | Ok(false) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/header/common/cache_control.rs: -------------------------------------------------------------------------------- 1 | use header::{Header, IntoHeaderValue, Writer}; 2 | use header::{fmt_comma_delimited, from_comma_delimited}; 3 | use http::header; 4 | use std::fmt::{self, Write}; 5 | use std::str::FromStr; 6 | 7 | /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) 8 | /// 9 | /// The `Cache-Control` header field is used to specify directives for 10 | /// caches along the request/response chain. Such cache directives are 11 | /// unidirectional in that the presence of a directive in a request does 12 | /// not imply that the same directive is to be given in the response. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Cache-Control = 1#cache-directive 18 | /// cache-directive = token [ "=" ( token / quoted-string ) ] 19 | /// ``` 20 | /// 21 | /// # Example values 22 | /// 23 | /// * `no-cache` 24 | /// * `private, community="UCI"` 25 | /// * `max-age=30` 26 | /// 27 | /// # Examples 28 | /// ```rust 29 | /// use actix_web::HttpResponse; 30 | /// use actix_web::http::header::{CacheControl, CacheDirective}; 31 | /// 32 | /// let mut builder = HttpResponse::Ok(); 33 | /// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); 34 | /// ``` 35 | /// 36 | /// ```rust 37 | /// use actix_web::HttpResponse; 38 | /// use actix_web::http::header::{CacheControl, CacheDirective}; 39 | /// 40 | /// let mut builder = HttpResponse::Ok(); 41 | /// builder.set(CacheControl(vec![ 42 | /// CacheDirective::NoCache, 43 | /// CacheDirective::Private, 44 | /// CacheDirective::MaxAge(360u32), 45 | /// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), 46 | /// ])); 47 | /// ``` 48 | #[derive(PartialEq, Clone, Debug)] 49 | pub struct CacheControl(pub Vec); 50 | 51 | __hyper__deref!(CacheControl => Vec); 52 | 53 | //TODO: this could just be the header! macro 54 | impl Header for CacheControl { 55 | fn name() -> header::HeaderName { 56 | header::CACHE_CONTROL 57 | } 58 | 59 | #[inline] 60 | fn parse(msg: &T) -> Result 61 | where 62 | T: ::HttpMessage, 63 | { 64 | let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; 65 | if !directives.is_empty() { 66 | Ok(CacheControl(directives)) 67 | } else { 68 | Err(::error::ParseError::Header) 69 | } 70 | } 71 | } 72 | 73 | impl fmt::Display for CacheControl { 74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 75 | fmt_comma_delimited(f, &self[..]) 76 | } 77 | } 78 | 79 | impl IntoHeaderValue for CacheControl { 80 | type Error = header::InvalidHeaderValueBytes; 81 | 82 | fn try_into(self) -> Result { 83 | let mut writer = Writer::new(); 84 | let _ = write!(&mut writer, "{}", self); 85 | header::HeaderValue::from_shared(writer.take()) 86 | } 87 | } 88 | 89 | /// `CacheControl` contains a list of these directives. 90 | #[derive(PartialEq, Clone, Debug)] 91 | pub enum CacheDirective { 92 | /// "no-cache" 93 | NoCache, 94 | /// "no-store" 95 | NoStore, 96 | /// "no-transform" 97 | NoTransform, 98 | /// "only-if-cached" 99 | OnlyIfCached, 100 | 101 | // request directives 102 | /// "max-age=delta" 103 | MaxAge(u32), 104 | /// "max-stale=delta" 105 | MaxStale(u32), 106 | /// "min-fresh=delta" 107 | MinFresh(u32), 108 | 109 | // response directives 110 | /// "must-revalidate" 111 | MustRevalidate, 112 | /// "public" 113 | Public, 114 | /// "private" 115 | Private, 116 | /// "proxy-revalidate" 117 | ProxyRevalidate, 118 | /// "s-maxage=delta" 119 | SMaxAge(u32), 120 | 121 | /// Extension directives. Optionally include an argument. 122 | Extension(String, Option), 123 | } 124 | 125 | impl fmt::Display for CacheDirective { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | use self::CacheDirective::*; 128 | fmt::Display::fmt( 129 | match *self { 130 | NoCache => "no-cache", 131 | NoStore => "no-store", 132 | NoTransform => "no-transform", 133 | OnlyIfCached => "only-if-cached", 134 | 135 | MaxAge(secs) => return write!(f, "max-age={}", secs), 136 | MaxStale(secs) => return write!(f, "max-stale={}", secs), 137 | MinFresh(secs) => return write!(f, "min-fresh={}", secs), 138 | 139 | MustRevalidate => "must-revalidate", 140 | Public => "public", 141 | Private => "private", 142 | ProxyRevalidate => "proxy-revalidate", 143 | SMaxAge(secs) => return write!(f, "s-maxage={}", secs), 144 | 145 | Extension(ref name, None) => &name[..], 146 | Extension(ref name, Some(ref arg)) => { 147 | return write!(f, "{}={}", name, arg) 148 | } 149 | }, 150 | f, 151 | ) 152 | } 153 | } 154 | 155 | impl FromStr for CacheDirective { 156 | type Err = Option<::Err>; 157 | fn from_str(s: &str) -> Result::Err>> { 158 | use self::CacheDirective::*; 159 | match s { 160 | "no-cache" => Ok(NoCache), 161 | "no-store" => Ok(NoStore), 162 | "no-transform" => Ok(NoTransform), 163 | "only-if-cached" => Ok(OnlyIfCached), 164 | "must-revalidate" => Ok(MustRevalidate), 165 | "public" => Ok(Public), 166 | "private" => Ok(Private), 167 | "proxy-revalidate" => Ok(ProxyRevalidate), 168 | "" => Err(None), 169 | _ => match s.find('=') { 170 | Some(idx) if idx + 1 < s.len() => { 171 | match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { 172 | ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), 173 | ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), 174 | ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), 175 | ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), 176 | (left, right) => { 177 | Ok(Extension(left.to_owned(), Some(right.to_owned()))) 178 | } 179 | } 180 | } 181 | Some(_) => Err(None), 182 | None => Ok(Extension(s.to_owned(), None)), 183 | }, 184 | } 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use super::*; 191 | use header::Header; 192 | use test::TestRequest; 193 | 194 | #[test] 195 | fn test_parse_multiple_headers() { 196 | let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") 197 | .finish(); 198 | let cache = Header::parse(&req); 199 | assert_eq!( 200 | cache.ok(), 201 | Some(CacheControl(vec![ 202 | CacheDirective::NoCache, 203 | CacheDirective::Private, 204 | ])) 205 | ) 206 | } 207 | 208 | #[test] 209 | fn test_parse_argument() { 210 | let req = 211 | TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") 212 | .finish(); 213 | let cache = Header::parse(&req); 214 | assert_eq!( 215 | cache.ok(), 216 | Some(CacheControl(vec![ 217 | CacheDirective::MaxAge(100), 218 | CacheDirective::Private, 219 | ])) 220 | ) 221 | } 222 | 223 | #[test] 224 | fn test_parse_quote_form() { 225 | let req = 226 | TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); 227 | let cache = Header::parse(&req); 228 | assert_eq!( 229 | cache.ok(), 230 | Some(CacheControl(vec![CacheDirective::MaxAge(200)])) 231 | ) 232 | } 233 | 234 | #[test] 235 | fn test_parse_extension() { 236 | let req = 237 | TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); 238 | let cache = Header::parse(&req); 239 | assert_eq!( 240 | cache.ok(), 241 | Some(CacheControl(vec![ 242 | CacheDirective::Extension("foo".to_owned(), None), 243 | CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), 244 | ])) 245 | ) 246 | } 247 | 248 | #[test] 249 | fn test_parse_bad_syntax() { 250 | let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); 251 | let cache: Result = Header::parse(&req); 252 | assert_eq!(cache.ok(), None) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Actix web is a small, pragmatic, and extremely fast web framework 2 | //! for Rust. 3 | //! 4 | //! ```rust 5 | //! use actix_web::{server, App, Path, Responder}; 6 | //! # use std::thread; 7 | //! 8 | //! fn index(info: Path<(String, u32)>) -> impl Responder { 9 | //! format!("Hello {}! id:{}", info.0, info.1) 10 | //! } 11 | //! 12 | //! fn main() { 13 | //! # thread::spawn(|| { 14 | //! server::new(|| { 15 | //! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) 16 | //! }).bind("127.0.0.1:8080") 17 | //! .unwrap() 18 | //! .run(); 19 | //! # }); 20 | //! } 21 | //! ``` 22 | //! 23 | //! ## Documentation & community resources 24 | //! 25 | //! Besides the API documentation (which you are currently looking 26 | //! at!), several other resources are available: 27 | //! 28 | //! * [User Guide](https://actix.rs/docs/) 29 | //! * [Chat on gitter](https://gitter.im/actix/actix) 30 | //! * [GitHub repository](https://github.com/actix/actix-web) 31 | //! * [Cargo package](https://crates.io/crates/actix-web) 32 | //! 33 | //! To get started navigating the API documentation you may want to 34 | //! consider looking at the following pages: 35 | //! 36 | //! * [App](struct.App.html): This struct represents an actix-web 37 | //! application and is used to configure routes and other common 38 | //! settings. 39 | //! 40 | //! * [HttpServer](server/struct.HttpServer.html): This struct 41 | //! represents an HTTP server instance and is used to instantiate and 42 | //! configure servers. 43 | //! 44 | //! * [HttpRequest](struct.HttpRequest.html) and 45 | //! [HttpResponse](struct.HttpResponse.html): These structs 46 | //! represent HTTP requests and responses and expose various methods 47 | //! for inspecting, creating and otherwise utilizing them. 48 | //! 49 | //! ## Features 50 | //! 51 | //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols 52 | //! * Streaming and pipelining 53 | //! * Keep-alive and slow requests handling 54 | //! * `WebSockets` server/client 55 | //! * Transparent content compression/decompression (br, gzip, deflate) 56 | //! * Configurable request routing 57 | //! * Graceful server shutdown 58 | //! * Multipart streams 59 | //! * SSL support with OpenSSL or `native-tls` 60 | //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) 61 | //! * Built on top of [Actix actor framework](https://github.com/actix/actix) 62 | //! * Supported Rust version: 1.26 or later 63 | //! 64 | //! ## Package feature 65 | //! 66 | //! * `tls` - enables ssl support via `native-tls` crate 67 | //! * `alpn` - enables ssl support via `openssl` crate, require for `http/2` 68 | //! support 69 | //! * `session` - enables session support, includes `ring` crate as 70 | //! dependency 71 | //! * `brotli` - enables `brotli` compression support, requires `c` 72 | //! compiler 73 | //! * `flate2-c` - enables `gzip`, `deflate` compression support, requires 74 | //! `c` compiler 75 | //! * `flate2-rust` - experimental rust based implementation for 76 | //! `gzip`, `deflate` compression. 77 | //! 78 | #![cfg_attr(actix_nightly, feature( 79 | specialization, // for impl ErrorResponse for std::error::Error 80 | extern_prelude, 81 | ))] 82 | #![cfg_attr( 83 | feature = "cargo-clippy", 84 | allow(decimal_literal_representation, suspicious_arithmetic_impl) 85 | )] 86 | #![warn(missing_docs)] 87 | 88 | #[macro_use] 89 | extern crate log; 90 | extern crate base64; 91 | extern crate byteorder; 92 | extern crate bytes; 93 | extern crate regex; 94 | extern crate sha1; 95 | extern crate time; 96 | #[macro_use] 97 | extern crate bitflags; 98 | #[macro_use] 99 | extern crate failure; 100 | #[macro_use] 101 | extern crate lazy_static; 102 | #[macro_use] 103 | extern crate futures; 104 | extern crate cookie; 105 | extern crate futures_cpupool; 106 | extern crate htmlescape; 107 | extern crate http as modhttp; 108 | extern crate httparse; 109 | extern crate language_tags; 110 | extern crate lazycell; 111 | extern crate mime; 112 | extern crate mime_guess; 113 | extern crate mio; 114 | extern crate net2; 115 | extern crate parking_lot; 116 | extern crate rand; 117 | extern crate slab; 118 | extern crate tokio; 119 | extern crate tokio_io; 120 | extern crate tokio_reactor; 121 | extern crate tokio_tcp; 122 | extern crate tokio_timer; 123 | extern crate url; 124 | #[macro_use] 125 | extern crate serde; 126 | #[cfg(feature = "brotli")] 127 | extern crate brotli2; 128 | extern crate encoding; 129 | #[cfg(feature = "flate2")] 130 | extern crate flate2; 131 | extern crate h2 as http2; 132 | extern crate num_cpus; 133 | #[macro_use] 134 | extern crate percent_encoding; 135 | extern crate serde_json; 136 | extern crate smallvec; 137 | #[macro_use] 138 | extern crate actix as actix_inner; 139 | 140 | #[cfg(test)] 141 | #[macro_use] 142 | extern crate serde_derive; 143 | 144 | #[cfg(feature = "tls")] 145 | extern crate native_tls; 146 | #[cfg(feature = "tls")] 147 | extern crate tokio_tls; 148 | 149 | #[cfg(feature = "openssl")] 150 | extern crate openssl; 151 | #[cfg(feature = "openssl")] 152 | extern crate tokio_openssl; 153 | 154 | #[cfg(feature = "rust-tls")] 155 | extern crate rustls; 156 | #[cfg(feature = "rust-tls")] 157 | extern crate tokio_rustls; 158 | #[cfg(feature = "rust-tls")] 159 | extern crate webpki; 160 | #[cfg(feature = "rust-tls")] 161 | extern crate webpki_roots; 162 | 163 | mod application; 164 | mod body; 165 | mod context; 166 | mod de; 167 | mod extensions; 168 | mod extractor; 169 | mod handler; 170 | mod header; 171 | mod helpers; 172 | mod httpcodes; 173 | mod httpmessage; 174 | mod httprequest; 175 | mod httpresponse; 176 | mod info; 177 | mod json; 178 | mod param; 179 | mod payload; 180 | mod pipeline; 181 | mod resource; 182 | mod route; 183 | mod router; 184 | mod scope; 185 | mod serde_urlencoded; 186 | mod uri; 187 | mod with; 188 | 189 | pub mod client; 190 | pub mod error; 191 | pub mod fs; 192 | pub mod middleware; 193 | pub mod multipart; 194 | pub mod pred; 195 | pub mod server; 196 | pub mod test; 197 | pub mod ws; 198 | pub use application::App; 199 | pub use body::{Binary, Body}; 200 | pub use context::HttpContext; 201 | pub use error::{Error, ResponseError, Result}; 202 | pub use extensions::Extensions; 203 | pub use extractor::{Form, Path, Query}; 204 | pub use handler::{ 205 | AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, 206 | }; 207 | pub use httpmessage::HttpMessage; 208 | pub use httprequest::HttpRequest; 209 | pub use httpresponse::HttpResponse; 210 | pub use json::Json; 211 | pub use scope::Scope; 212 | pub use server::Request; 213 | 214 | pub mod actix { 215 | //! Re-exports [actix's](https://docs.rs/actix/) prelude 216 | 217 | extern crate actix; 218 | pub use self::actix::actors::resolver; 219 | pub use self::actix::actors::signal; 220 | pub use self::actix::fut; 221 | pub use self::actix::msgs; 222 | pub use self::actix::prelude::*; 223 | pub use self::actix::{run, spawn}; 224 | } 225 | 226 | #[cfg(feature = "openssl")] 227 | pub(crate) const HAS_OPENSSL: bool = true; 228 | #[cfg(not(feature = "openssl"))] 229 | pub(crate) const HAS_OPENSSL: bool = false; 230 | 231 | #[cfg(feature = "tls")] 232 | pub(crate) const HAS_TLS: bool = true; 233 | #[cfg(not(feature = "tls"))] 234 | pub(crate) const HAS_TLS: bool = false; 235 | 236 | #[cfg(feature = "rust-tls")] 237 | pub(crate) const HAS_RUSTLS: bool = true; 238 | #[cfg(not(feature = "rust-tls"))] 239 | pub(crate) const HAS_RUSTLS: bool = false; 240 | 241 | pub mod dev { 242 | //! The `actix-web` prelude for library developers 243 | //! 244 | //! The purpose of this module is to alleviate imports of many common actix 245 | //! traits by adding a glob import to the top of actix heavy modules: 246 | //! 247 | //! ``` 248 | //! # #![allow(unused_imports)] 249 | //! use actix_web::dev::*; 250 | //! ``` 251 | 252 | pub use body::BodyStream; 253 | pub use context::Drain; 254 | pub use extractor::{FormConfig, PayloadConfig}; 255 | pub use handler::{AsyncResult, Handler}; 256 | pub use httpmessage::{MessageBody, UrlEncoded}; 257 | pub use httpresponse::HttpResponseBuilder; 258 | pub use info::ConnectionInfo; 259 | pub use json::{JsonBody, JsonConfig}; 260 | pub use param::{FromParam, Params}; 261 | pub use payload::{Payload, PayloadBuffer}; 262 | pub use resource::Resource; 263 | pub use route::Route; 264 | pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; 265 | } 266 | 267 | pub mod http { 268 | //! Various HTTP related types 269 | 270 | // re-exports 271 | pub use modhttp::{Method, StatusCode, Version}; 272 | 273 | #[doc(hidden)] 274 | pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; 275 | 276 | pub use cookie::{Cookie, CookieBuilder}; 277 | 278 | pub use helpers::NormalizePath; 279 | 280 | /// Various http headers 281 | pub mod header { 282 | pub use header::*; 283 | pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag}; 284 | } 285 | pub use header::ContentEncoding; 286 | pub use httpresponse::ConnectionType; 287 | } 288 | --------------------------------------------------------------------------------