tiny_http-0.12.0/.cargo_vcs_info.json0000644000000001360000000000100131360ustar { "git": { "sha1": "212b1c45852fef2093dc1374875a9393c55eb4b9" }, "path_in_vcs": "" }tiny_http-0.12.0/.gitattributes000064400000000000000000000007430072674642500146550ustar 00000000000000# Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain tiny_http-0.12.0/.github/workflows/ci.yaml000064400000000000000000000026040072674642500166340ustar 00000000000000on: [push, pull_request] name: CI jobs: clippy_rustfmt: name: Lint & Format runs-on: ubuntu-latest strategy: matrix: features: - default - ssl-openssl - ssl-rustls steps: - uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable components: rustfmt, clippy - name: Clippy uses: actions-rs/cargo@v1 with: command: clippy args: --features ${{ matrix.features }} - name: Format uses: actions-rs/cargo@v1 with: command: fmt args: -- --check test: name: Build & Test runs-on: ubuntu-latest strategy: matrix: rust: - stable - nightly - 1.56 features: - default - ssl-openssl - ssl-rustls steps: - uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Build uses: actions-rs/cargo@v1 with: command: build args: --features ${{ matrix.features }} - name: Test uses: actions-rs/cargo@v1 with: command: test args: --features ${{ matrix.features }} tiny_http-0.12.0/.gitignore000064400000000000000000000000310072674642500137400ustar 00000000000000Cargo.lock target/ .idea tiny_http-0.12.0/CHANGELOG.md000064400000000000000000000156020072674642500135730ustar 00000000000000# Changes ## 0.12.0 * Bumped the minimum compiler version tested by CI to 1.56 - this is necessary due to an increasing number of dependencies introducing Cargo manifest features only supported on newer versions of Rust. * [Add support for UNIX sockets](https://github.com/tiny-http/tiny-http/pull/224) Thanks to @ColonelThirtyTwo for adding support for binding to UNIX sockets when creating a tiny-http server. This change makes a few small breaking API modifications, if you are constructing `ServerConfig` manually you will need to use the new `ListenAddr` type rather than directly supplying a `net::SocketAddr`. Likewise `Server::server_addr()` will now return an enum that can represent either a TCP socket or a UNIX socket. Finally `Request::remote_addr()` now returns an `Option<&SocketAddr>` as UNIX sockets don't ever have a remote host. * [Reduce required dependencies by switching to `httpdate`](https://github.com/tiny-http/tiny-http/pull/228) @esheppa replaced our internal HTTPDate type with the `httpdate` library (used extensively in the community by Hyper, Tokio and others) which reduces our baseline dependency tree from 18 crates to 5! * `TestRequest::path` no longer has a `'static` bound, allowing for fuzzers to generate test request paths at runtime. * Unpinned `zeroize` so it can float around any stable `^1` version. ## 0.11.0 * [Add support for Rustls](https://github.com/tiny-http/tiny-http/pull/218) Thanks to @3xmblzj5 and @travispaul for their help in implementing [`Rustls`](https://github.com/rustls/rustls) as a drop-in replacement for OpenSSL, you can now build `tiny-http` with TLS support without any external dependencies! OpenSSL will remain the default implementation if you just enable the `ssl` feature, but you are strongly encouraged to use `ssl-rustls` where possible! * [Fix incorrect certificate chain loading](https://github.com/tiny-http/tiny-http/commit/876efd6b752e991c699d27d3d0ad9a47e9d35c29) Fix a longstanding bug where we were only loading the first (i.e. the leaf) certificate from any PEM file supplied by the user. ## 0.10.0 * [Replace chrono with time-rs](https://github.com/tiny-http/tiny-http/commit/75ac7758fd0ca660c35f58c2a36edb23a42cda32) `chrono` was only used to store and format `DateTime` into the slightly odd format required by RFC 7231, so to avoid the numerous RUSTSEC advisories generated by the `localtime_r` issue, we can just drop it entirely and switch to `time-rs`. Unfortunately this means we need to **bump our minimum tested compiler version to 1.51**, and as such this change requires a full minor release. ## 0.9.0 * [Rust 2018 Refactor](https://github.com/tiny-http/tiny-http/pull/208) * [Enable prompt responses, before the request has been fully read](https://github.com/tiny-http/tiny-http/pull/207) This isn't an API change, but does result in different behaviour to 0.8.2 and so justifies a minor version bump. HTTP requests now return a boxed `FusedReader` which drops the underlying reader once it reaches EOF, such that the reader no longer needs to be explicitly consumed and the server may now respond with e.g. a "413 Payload too large" without waiting for the whole reader. * Bumped the minimum compiler version tested by CI to 1.48 (the version supported in Debian Bullseye) ## 0.8.2 * [Add TestRequest for writing server tests more easily](https://github.com/tiny-http/tiny-http/pull/203) ## 0.8.1 * [Don't set Transfer-Encoding for 1xx or 204 Responses](https://github.com/tiny-http/tiny-http/pull/198) ## 0.8.0 * [Fix RUSTSEC-2020-0031](https://github.com/tiny-http/tiny-http/pull/190) * [Filter out the same socket-closing errors on flush as on write](https://github.com/tiny-http/tiny-http/pull/192) * [response: Drop the use of EqualReader for TransferEncoding::Identity](https://github.com/tiny-http/tiny-http/pull/183) * [Add unblock method for graceful shutdown](https://github.com/tiny-http/tiny-http/pull/184) * [Response: Don't forget `chunked_threshold`](https://github.com/tiny-http/tiny-http/pull/177) * [Response: Allow manual handling of Range requests](https://github.com/tiny-http/tiny-http/pull/175) * [Feature | Getters for Response Status Code & Data Length Properties](https://github.com/tiny-http/tiny-http/pull/186) ## 0.7.0 * [Fix HTTPS deadlock](https://github.com/tiny-http/tiny-http/pull/151) * [Relicense to MIT/Apache-2.0](https://github.com/tiny-http/tiny-http/pull/163) * [Update `ascii` dependency](https://github.com/tiny-http/tiny-http/pull/165) * [Fix typo in README](https://github.com/tiny-http/tiny-http/pull/171) * [Fix compilation errors in benchmark](https://github.com/tiny-http/tiny-http/pull/170) * [Update `url` dependency](https://github.com/tiny-http/tiny-http/pull/168) * [Update `chunked_transfer` dependency](https://github.com/tiny-http/tiny-http/pull/166) ## 0.6.2 * [Remove AsciiExt usage](https://github.com/tiny-http/tiny-http/pull/152) * [Remove unused EncodingDecoder](https://github.com/tiny-http/tiny-http/pull/153) ## 0.6.1 * [Fix documentation typo](https://github.com/tiny-http/tiny-http/pull/148) * [Expose chunked_threshold on Response](https://github.com/tiny-http/tiny-http/pull/150) ## 0.6.0 * [Bump dependencies](https://github.com/tiny-http/tiny-http/pull/142) * [Fix `next_header_source` alignment](https://github.com/tiny-http/tiny-http/pull/140) ## 0.5.9 * Expanded and changed status code description mapping according to IANA registry: * https://github.com/tiny-http/tiny-http/pull/138 ## 0.5.8 * Update links to reflect repository ownership change: https://github.com/frewsxcv/tiny-http -> https://github.com/tiny-http/tiny-http ## 0.5.7 * Fix using Transfer-Encoding: identity with no content length * https://github.com/tiny-http/tiny-http/pull/126 ## 0.5.6 * Update link to documentation * https://github.com/tiny-http/tiny-http/pull/123 * Fix websockets * https://github.com/tiny-http/tiny-http/pull/124 * Drop the request reader earlier * https://github.com/tiny-http/tiny-http/pull/125 ## 0.5.5 * Start using the log crate * https://github.com/tiny-http/tiny-http/pull/121 * Unblock the accept thread on shutdown * https://github.com/tiny-http/tiny-http/pull/120 ## 0.5.4 * Fix compilation warnings * https://github.com/tiny-http/tiny-http/pull/118 ## 0.5.3 * Add try_recv_timeout function to the server * https://github.com/tiny-http/tiny-http/pull/116 ## 0.5.2 * Update ascii to version 0.7 * https://github.com/tiny-http/tiny-http/pull/114 ## 0.5.1 * Request::respond now returns an IoResult * https://github.com/tiny-http/tiny-http/pull/110 ## 0.5.0 * HTTPS support * https://github.com/tiny-http/tiny-http/pull/107 * Rework the server creation API * https://github.com/tiny-http/tiny-http/pull/106 ## 0.4.1 * Allow binding to a nic by specifying the socket address * https://github.com/tiny-http/tiny-http/pull/103 ## 0.4.0 * Make Method into an enum instead of a character string * https://github.com/tiny-http/tiny-http/pull/102 tiny_http-0.12.0/Cargo.lock0000644000000241520000000000100111150ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ascii" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chunked_transfer" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "fdlimit" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0da54a593b34c71b889ee45f5b5bb900c74148c5f7f8c6a9479ee7899f69603c" dependencies = [ "libc", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "js-sys" version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "once_cell" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "openssl" version = "0.10.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-sys" version = "0.9.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" dependencies = [ "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "pkg-config" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "proc-macro2" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin", "untrusted", "web-sys", "winapi", ] [[package]] name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" [[package]] name = "rustls" version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", "sct", "webpki", ] [[package]] name = "rustls-pemfile" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ "base64", ] [[package]] name = "sct" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", ] [[package]] name = "sha1" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" dependencies = [ "sha1_smol", ] [[package]] name = "sha1_smol" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tiny_http" version = "0.12.0" dependencies = [ "ascii", "chunked_transfer", "fdlimit", "httpdate", "log", "openssl", "rustc-serialize", "rustls", "rustls-pemfile", "sha1", "zeroize", ] [[package]] name = "unicode-ident" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wasm-bindgen" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "web-sys" version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" tiny_http-0.12.0/Cargo.toml0000644000000031710000000000100111360ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "tiny_http" version = "0.12.0" authors = [ "pierre.krieger1708@gmail.com", "Corey Farwell ", ] description = "Low level HTTP server library" documentation = "https://tiny-http.github.io/tiny-http/tiny_http/index.html" readme = "README.md" keywords = [ "http", "server", "web", ] license = "MIT OR Apache-2.0" repository = "https://github.com/tiny-http/tiny-http" [package.metadata.docs.rs] features = ["ssl-openssl"] [dependencies.ascii] version = "1.0" [dependencies.chunked_transfer] version = "1" [dependencies.httpdate] version = "1.0.2" [dependencies.log] version = "0.4.4" [dependencies.openssl] version = "0.10" optional = true [dependencies.rustls] version = "0.20" optional = true [dependencies.rustls-pemfile] version = "0.2.1" optional = true [dependencies.zeroize] version = "1" optional = true [dev-dependencies.fdlimit] version = "0.1" [dev-dependencies.rustc-serialize] version = "0.3" [dev-dependencies.sha1] version = "0.6.0" [features] default = [] ssl = ["ssl-openssl"] ssl-openssl = [ "openssl", "zeroize", ] ssl-rustls = [ "rustls", "rustls-pemfile", "zeroize", ] tiny_http-0.12.0/Cargo.toml.orig000064400000000000000000000017320072674642500146500ustar 00000000000000[package] name = "tiny_http" version = "0.12.0" authors = ["pierre.krieger1708@gmail.com", "Corey Farwell "] description = "Low level HTTP server library" documentation = "https://tiny-http.github.io/tiny-http/tiny_http/index.html" keywords = ["http", "server", "web"] license = "MIT OR Apache-2.0" repository = "https://github.com/tiny-http/tiny-http" edition = "2018" [features] default = [] ssl = ["ssl-openssl"] ssl-openssl = ["openssl", "zeroize"] ssl-rustls = ["rustls", "rustls-pemfile", "zeroize"] [dependencies] ascii = "1.0" chunked_transfer = "1" log = "0.4.4" httpdate = "1.0.2" openssl = { version = "0.10", optional = true } rustls = { version = "0.20", optional = true } rustls-pemfile = { version = "0.2.1", optional = true } zeroize = { version = "1", optional = true } [dev-dependencies] rustc-serialize = "0.3" sha1 = "0.6.0" fdlimit = "0.1" [package.metadata.docs.rs] # Enable just one SSL implementation features = ["ssl-openssl"] tiny_http-0.12.0/LICENSE-APACHE000064400000000000000000000251370072674642500137120ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. tiny_http-0.12.0/LICENSE-MIT000064400000000000000000000020630072674642500134130ustar 00000000000000Copyright (c) 2014-2019 The tiny-http contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tiny_http-0.12.0/README.md000064400000000000000000000106110072674642500132340ustar 00000000000000# tiny-http [![Crate][crate_img]][crate] [![Documentation][docs_img]][docs] ![License][license_img] [![CI Status][ci_badge]][ci_link] [**Documentation**](https://docs.rs/tiny_http) Tiny but strong HTTP server in Rust. Its main objectives are to be 100% compliant with the HTTP standard and to provide an easy way to create an HTTP server. What does **tiny-http** handle? - Accepting and managing connections to the clients - Parsing requests - Requests pipelining - HTTPS (using either OpenSSL or Rustls) - Transfer-Encoding and Content-Encoding - Turning user input (eg. POST input) into a contiguous UTF-8 string (**not implemented yet**) - Ranges (**not implemented yet**) - `Connection: upgrade` (used by websockets) Tiny-http handles everything that is related to client connections and data transfers and encoding. Everything else (parsing the values of the headers, multipart data, routing, etags, cache-control, HTML templates, etc.) must be handled by your code. If you want to create a website in Rust, I strongly recommend using a framework instead of this library. ### Installation Add this to the `Cargo.toml` file of your project: ```toml [dependencies] tiny_http = "0.11" ``` ### Usage ```rust use tiny_http::{Server, Response}; let server = Server::http("0.0.0.0:8000").unwrap(); for request in server.incoming_requests() { println!("received request! method: {:?}, url: {:?}, headers: {:?}", request.method(), request.url(), request.headers() ); let response = Response::from_string("hello world"); request.respond(response); } ``` ### Speed Tiny-http was designed with speed in mind: - Each client connection will be dispatched to a thread pool. Each thread will handle one client. If there is no thread available when a client connects, a new one is created. Threads that are idle for a long time (currently 5 seconds) will automatically die. - If multiple requests from the same client are being pipelined (ie. multiple requests are sent without waiting for the answer), tiny-http will read them all at once and they will all be available via `server.recv()`. Tiny-http will automatically rearrange the responses so that they are sent in the right order. - One exception to the previous statement exists when a request has a large body (currently > 1kB), in which case the request handler will read the body directly from the stream and tiny-http will wait for it to be read before processing the next request. Tiny-http will never wait for a request to be answered to read the next one. - When a client connection has sent its last request (by sending `Connection: close` header), the thread will immediately stop reading from this client and can be reclaimed, even when the request has not yet been answered. The reading part of the socket will also be immediately closed. - Decoding the client's request is done lazily. If you don't read the request's body, it will not be decoded. ### Examples Examples of tiny-http in use: * [heroku-tiny-http-hello-world](https://github.com/frewsxcv/heroku-tiny-http-hello-world) - A simple web application demonstrating how to deploy tiny-http to Heroku * [crate-deps](https://github.com/frewsxcv/crate-deps) - A web service that generates images of dependency graphs for crates hosted on crates.io * [rouille](https://crates.io/crates/rouille) - Web framework built on tiny-http ### License This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. #### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in tiny-http by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [crate_img]: https://img.shields.io/crates/v/tiny_http.svg?logo=rust "Crate Page" [crate]: https://crates.io/crates/tiny_http "Crate Link" [docs]: https://docs.rs/tiny_http "Documentation" [docs_img]: https://docs.rs/tiny_http/badge.svg "Documentation" [license_img]: https://img.shields.io/crates/l/tiny_http.svg "License" [ci_badge]: https://github.com/tiny-http/tiny-http/actions/workflows/ci.yaml/badge.svg "CI Status" [ci_link]: https://github.com/tiny-http/tiny-http/actions/workflows/ci.yaml "Workflow Link" tiny_http-0.12.0/benches/bench.rs000064400000000000000000000040770072674642500150220ustar 00000000000000#![feature(test)] extern crate fdlimit; extern crate test; extern crate tiny_http; use std::io::Write; use std::process::Command; use tiny_http::Method; #[test] #[ignore] // TODO: obtain time fn curl_bench() { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); let num_requests = 10usize; match Command::new("curl") .arg("-s") .arg(format!("http://localhost:{}/?[1-{}]", port, num_requests)) .output() { Ok(p) => p, Err(_) => return, // ignoring test }; drop(server); } #[bench] fn sequential_requests(bencher: &mut test::Bencher) { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); let mut stream = std::net::TcpStream::connect(("127.0.0.1", port)).unwrap(); bencher.iter(|| { (write!(stream, "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")).unwrap(); let request = server.recv().unwrap(); assert_eq!(request.method(), &Method::Get); request.respond(tiny_http::Response::new_empty(tiny_http::StatusCode(204))); }); } #[bench] fn parallel_requests(bencher: &mut test::Bencher) { fdlimit::raise_fd_limit(); let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); bencher.iter(|| { let mut streams = Vec::new(); for _ in 0..1000usize { let mut stream = std::net::TcpStream::connect(("127.0.0.1", port)).unwrap(); (write!( stream, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" )) .unwrap(); streams.push(stream); } loop { let request = match server.try_recv().unwrap() { None => break, Some(rq) => rq, }; assert_eq!(request.method(), &Method::Get); request.respond(tiny_http::Response::new_empty(tiny_http::StatusCode(204))); } }); } tiny_http-0.12.0/examples/hello-world.rs000064400000000000000000000011500072674642500163670ustar 00000000000000extern crate tiny_http; use std::sync::Arc; use std::thread; fn main() { let server = Arc::new(tiny_http::Server::http("0.0.0.0:9975").unwrap()); println!("Now listening on port 9975"); let mut handles = Vec::new(); for _ in 0..4 { let server = server.clone(); handles.push(thread::spawn(move || { for rq in server.incoming_requests() { let response = tiny_http::Response::from_string("hello world".to_string()); let _ = rq.respond(response); } })); } for h in handles { h.join().unwrap(); } } tiny_http-0.12.0/examples/php-cgi-example.php000064400000000000000000000000330072674642500172610ustar 00000000000000 */ fn handle(rq: tiny_http::Request, script: &str) { use std::io::Write; use std::process::Command; let php = Command::new("php-cgi") .arg(script) //.stdin(Ignored) //.extra_io(Ignored) .env("AUTH_TYPE", "") .env( "CONTENT_LENGTH", format!("{}", rq.body_length().unwrap_or(0)), ) .env("CONTENT_TYPE", "") .env("GATEWAY_INTERFACE", "CGI/1.1") .env("PATH_INFO", "") .env("PATH_TRANSLATED", "") .env("QUERY_STRING", format!("{}", rq.url())) .env("REMOTE_ADDR", format!("{}", rq.remote_addr().unwrap())) .env("REMOTE_HOST", "") .env("REMOTE_IDENT", "") .env("REMOTE_USER", "") .env("REQUEST_METHOD", format!("{}", rq.method())) .env("SCRIPT_NAME", script) .env("SERVER_NAME", "tiny-http php-cgi example") .env("SERVER_PORT", format!("{}", rq.remote_addr().unwrap())) .env("SERVER_PROTOCOL", "HTTP/1.1") .env("SERVER_SOFTWARE", "tiny-http php-cgi example") .output() .unwrap(); // note: this is not a good implementation // cgi returns the status code in the headers ; also many headers will be missing // in the response match php.status { status if status.success() => { let mut writer = rq.into_writer(); let writer: &mut dyn Write = &mut *writer; (write!(writer, "HTTP/1.1 200 OK\r\n")).unwrap(); (write!(writer, "{}", php.stdout.clone().as_ascii_str().unwrap())).unwrap(); writer.flush().unwrap(); } _ => { println!( "Error in script execution: {}", php.stderr.clone().as_ascii_str().unwrap() ); } } } fn main() { use std::env; use std::sync::Arc; use std::thread::spawn; let php_script = { let mut args = env::args(); if args.len() < 2 { println!("Usage: php-cgi "); return; } args.nth(1).unwrap() }; let server = Arc::new(tiny_http::Server::http("0.0.0.0:9975").unwrap()); println!("Now listening on port 9975"); let num_cpus = 4; // TODO: dynamically generate this value for _ in 0..num_cpus { let server = server.clone(); let php_script = php_script.clone(); spawn(move || { for rq in server.incoming_requests() { handle(rq, &php_script); } }); } } tiny_http-0.12.0/examples/readme-example.rs000064400000000000000000000007570072674642500170410ustar 00000000000000extern crate tiny_http; fn main() { use tiny_http::{Response, Server}; let server = Server::http("0.0.0.0:8000").unwrap(); for request in server.incoming_requests() { println!( "received request! method: {:?}, url: {:?}, headers: {:?}", request.method(), request.url(), request.headers() ); let response = Response::from_string("hello world"); request.respond(response).expect("Responded"); } } tiny_http-0.12.0/examples/serve-root.rs000064400000000000000000000031320072674642500162460ustar 00000000000000use ascii::AsciiString; use std::fs; use std::path::Path; extern crate ascii; extern crate tiny_http; fn get_content_type(path: &Path) -> &'static str { let extension = match path.extension() { None => return "text/plain", Some(e) => e, }; match extension.to_str().unwrap() { "gif" => "image/gif", "jpg" => "image/jpeg", "jpeg" => "image/jpeg", "png" => "image/png", "pdf" => "application/pdf", "htm" => "text/html; charset=utf8", "html" => "text/html; charset=utf8", "txt" => "text/plain; charset=utf8", _ => "text/plain; charset=utf8", } } fn main() { let server = tiny_http::Server::http("0.0.0.0:8000").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); println!("Now listening on port {}", port); loop { let rq = match server.recv() { Ok(rq) => rq, Err(_) => break, }; println!("{:?}", rq); let url = rq.url().to_string(); let path = Path::new(&url); let file = fs::File::open(&path); if file.is_ok() { let response = tiny_http::Response::from_file(file.unwrap()); let response = response.with_header(tiny_http::Header { field: "Content-Type".parse().unwrap(), value: AsciiString::from_ascii(get_content_type(&path)).unwrap(), }); let _ = rq.respond(response); } else { let rep = tiny_http::Response::new_empty(tiny_http::StatusCode(404)); let _ = rq.respond(rep); } } } tiny_http-0.12.0/examples/ssl-cert.pem000064400000000000000000000025330072674642500160360ustar 00000000000000-----BEGIN CERTIFICATE----- MIIDxzCCAq+gAwIBAgIUVB6JBT6sYv0g4IGfZgjelceiqBQwDQYJKoZIhvcNAQEL BQAwcjELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE5vbmUxDTALBgNVBAcMBE5vbmUx DTALBgNVBAoMBE5vbmUxDTALBgNVBAsMBE5vbmUxEjAQBgNVBAMMCWxvY2FsaG9z dDETMBEGCSqGSIb3DQEJARYETm9uZTAgFw0yMjAxMjgyMDQzMjFaGA8yMDcyMDEx NjIwNDMyMVowcjELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE5vbmUxDTALBgNVBAcM BE5vbmUxDTALBgNVBAoMBE5vbmUxDTALBgNVBAsMBE5vbmUxEjAQBgNVBAMMCWxv Y2FsaG9zdDETMBEGCSqGSIb3DQEJARYETm9uZTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBANgjc9hZtEwve2usjNrc+2w4bT9fJi2uVQ3eHdtECirBxHrm rSbSeOyhvTPmonyp81LQv52KzHDLxwVSmFoJkrIKrnqqSzw/ynuqBpykhV3TKPLK SCZiyqQmGucTIxOXM9ZEB51zCvq+2jL4v2nBueibY2SzXG6MSAjRRC5ezDTYvIMH 12uH0U4a3UMICPTEMluy+mT4S1EGZLTj37+6JQA/1xYzZAifZAGEKRcCd0q5f9IU V8GnnYjptFFswJJF7EBpExZIlxwTn7c4Un8yjYOTAj9Yw6OiAy6MVv8NSF1DeGmY wUFHm6eUUmv+YO/T99sdt1dpdf1+807Fa62L1d8CAwEAAaNTMFEwHQYDVR0OBBYE FCjWLWB1sdWiGdHT/PY4BcuqnJq0MB8GA1UdIwQYMBaAFCjWLWB1sdWiGdHT/PY4 BcuqnJq0MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH+Gafl1 /yhjg4wVniQRFKqdufMmaEkAgJGaB87/Cjb5dyf1ku3ZvM7SX9MyV2R3tw6hncBQ E3XbPhOTJXcohGzpZ5hC043eY+/yAjKgbrSH4c0z/g9iSigX1B/F1hfF4Evx1eR6 WD5CCpA/YLF8Ik09WU2HKFT85sDIygmv0hmuI0dF+9lpqfPguhx6iLOoFyXkbgqF RDWe8V0/GtEnX4PckdyyYk/uFX5aKMeW5dBY6GL9YDRcZvTLsjJi1wj3OcDsr7n5 ULkGWiWdQScpkrGWOPoM72r6yyFi5P/RCBI/p0LBseARXAAedgC8tTK6DpfRXe1M BumieRdjmHDbE+4= -----END CERTIFICATE----- tiny_http-0.12.0/examples/ssl-key.pem000064400000000000000000000032540072674642500156720ustar 00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDYI3PYWbRML3tr rIza3PtsOG0/XyYtrlUN3h3bRAoqwcR65q0m0njsob0z5qJ8qfNS0L+disxwy8cF UphaCZKyCq56qks8P8p7qgacpIVd0yjyykgmYsqkJhrnEyMTlzPWRAedcwr6vtoy +L9pwbnom2Nks1xujEgI0UQuXsw02LyDB9drh9FOGt1DCAj0xDJbsvpk+EtRBmS0 49+/uiUAP9cWM2QIn2QBhCkXAndKuX/SFFfBp52I6bRRbMCSRexAaRMWSJccE5+3 OFJ/Mo2DkwI/WMOjogMujFb/DUhdQ3hpmMFBR5unlFJr/mDv0/fbHbdXaXX9fvNO xWuti9XfAgMBAAECggEBALRhyiHKo71dd0yigh96g96KrSpRV4SSVOuw7wv6md2b P0Yu1F1tFHywcz4ogn02PRtlmjV6DCsq9ltL1lh2WtZ6MamwDAApYOyaNtBuQdvP CgKurU5T7rjWEGe/QevsqddtiUlvJL+lnmch0GYLxwMJBAeb5U1hiBDLzXJBrX1/ xstCJzYC1MO3zRNuoNudpZyMU18BCbk1E+XMu/yPyJJPm5VSxS3Qe0RIX0nqC3hd RjwiTGYIqHX/hTFuZta+lNwVSx/8TJY6wTq69r6mGf0hudXz9A0SEhSVdkU77yFE NLCTRGD8xkF1bJliT57dGofheSrn4+ATR6eMYzsGmYECgYEA/0Ao+dccjMgqiyhR K3TTaikSSvvTOY/muISvovlpDe1U10+HIPMfGIJUVOE7p4oAbg9W70x8hMox+0hN yERLp4Pq9skkwxfmGjA0ooKJt0e0Ux1A5LiboLUPGlBsHwNcLqno/vj4WK/TGdZu 51zG1zvBrSIMLtT5QChsy+zTtsECgYEA2MXlkgy8W7/9BRcdzCXj2Uan5L+H6KT3 fXHYWc5yp0QBl6dqNCtPJ2lIpZ+qrNzJ1QZM56+qPEftOFOmhnz4+U6SAaKc/75n bAL//ggMQwHzMus6ufhXJC3AYSduq9e1hnOC+K7Hc3dPLPkJGjVZVC2kXhACTwuL aBbi5EmWVJ8CgYByk3BRRdgQ8cD3Gi/lW9mSq8EEW6njCs88QIM+msoncENHKvGz Pq7Up5wHRdsrR20N+mDBpgm26bQp4bjYjp+PIE4WXQ/daxrk4oKd+A6tcMhnDpiU krF5IA0ZeMQv36g/YhGucj+4P6R40qKRxDmVX8N+XewuEXeY7wx3NWWLgQKBgQC4 DqA0eDfet49AqTYVxv5F2GZqJe5iLOAvVWDcMBzNxUKM4AufLD7TOeQDLSUgDYAa LnVSK6eh83iKYQx+GNLV7E6wsMAZrjPmVE3EBlVS9+7lhzGgAisLfwVf+LlRk6B/ /shwGwcjFWTWzMVbyXyFqxNrArDTKPw/b19LcugABQKBgQD8ARwEoZRyl8kuLyLh 33FRUAVTvuCU1KvEl9CU6BcmEDNcI/O/IYOeOUXYeIeNyATNH2L8A++i53EptpMd wVxEIE2YBHD2t6+OcTjFSey7/6BejBUExxMVyWnUZMq3Xvf30ecfvacSb/DexqAG yos+VoW4PIphdW3NBfE1hlLDZQ== -----END PRIVATE KEY----- tiny_http-0.12.0/examples/ssl.rs000064400000000000000000000024520072674642500147460ustar 00000000000000extern crate tiny_http; #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))] fn main() { println!("This example requires one of the supported `ssl-*` features to be enabled"); } #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] fn main() { use tiny_http::{Response, Server}; let server = Server::https( "0.0.0.0:8000", tiny_http::SslConfig { certificate: include_bytes!("ssl-cert.pem").to_vec(), private_key: include_bytes!("ssl-key.pem").to_vec(), }, ) .unwrap(); println!( "Note: connecting to this server will likely give you a warning from your browser \ because the connection is unsecure. This is because the certificate used by this \ example is self-signed. With a real certificate, you wouldn't get this warning." ); for request in server.incoming_requests() { assert!(request.secure()); println!( "received request! method: {:?}, url: {:?}, headers: {:?}", request.method(), request.url(), request.headers() ); let response = Response::from_string("hello world"); request .respond(response) .unwrap_or(println!("Failed to respond to request")); } } tiny_http-0.12.0/examples/websockets.rs000064400000000000000000000115420072674642500163160ustar 00000000000000extern crate rustc_serialize; extern crate sha1; extern crate tiny_http; use std::io::Cursor; use std::io::Read; use std::thread::spawn; use rustc_serialize::base64::{Config, Newline, Standard, ToBase64}; fn home_page(port: u16) -> tiny_http::Response>> { tiny_http::Response::from_string(format!( "

This example will receive "Hello" for each byte in the packet being sent. Tiny-http doesn't support decoding websocket frames, so we can't do anything better.

Received:

", port )) .with_header( "Content-type: text/html" .parse::() .unwrap(), ) } /// Turns a Sec-WebSocket-Key into a Sec-WebSocket-Accept. /// Feel free to copy-paste this function, but please use a better error handling. fn convert_key(input: &str) -> String { use sha1::Sha1; let mut input = input.to_string().into_bytes(); let mut bytes = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" .to_string() .into_bytes(); input.append(&mut bytes); let mut sha1 = Sha1::new(); sha1.update(&input); sha1.digest().bytes().to_base64(Config { char_set: Standard, pad: true, line_length: None, newline: Newline::LF, }) } fn main() { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); println!("Server started"); println!( "To try this example, open a browser to http://localhost:{}/", port ); for request in server.incoming_requests() { // we are handling this websocket connection in a new task spawn(move || { // checking the "Upgrade" header to check that it is a websocket match request .headers() .iter() .find(|h| h.field.equiv(&"Upgrade")) .and_then(|hdr| { if hdr.value == "websocket" { Some(hdr) } else { None } }) { None => { // sending the HTML page request.respond(home_page(port)).expect("Responded"); return; } _ => (), }; // getting the value of Sec-WebSocket-Key let key = match request .headers() .iter() .find(|h| h.field.equiv(&"Sec-WebSocket-Key")) .map(|h| h.value.clone()) { None => { let response = tiny_http::Response::new_empty(tiny_http::StatusCode(400)); request.respond(response).expect("Responded"); return; } Some(k) => k, }; // building the "101 Switching Protocols" response let response = tiny_http::Response::new_empty(tiny_http::StatusCode(101)) .with_header("Upgrade: websocket".parse::().unwrap()) .with_header("Connection: Upgrade".parse::().unwrap()) .with_header( "Sec-WebSocket-Protocol: ping" .parse::() .unwrap(), ) .with_header( format!("Sec-WebSocket-Accept: {}", convert_key(key.as_str())) .parse::() .unwrap(), ); // let mut stream = request.upgrade("websocket", response); // loop { let mut out = Vec::new(); match Read::by_ref(&mut stream).take(1).read_to_end(&mut out) { Ok(n) if n >= 1 => { // "Hello" frame let data = [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]; stream.write(&data).ok(); stream.flush().ok(); } Ok(_) => panic!("eof ; should never happen"), Err(e) => { println!("closing connection because: {}", e); return; } }; } }); } } tiny_http-0.12.0/src/client.rs000064400000000000000000000247500072674642500144010ustar 00000000000000use ascii::AsciiString; use std::io::Error as IoError; use std::io::Result as IoResult; use std::io::{BufReader, BufWriter, ErrorKind, Read}; use std::net::SocketAddr; use std::str::FromStr; use crate::common::{HTTPVersion, Method}; use crate::util::RefinedTcpStream; use crate::util::{SequentialReader, SequentialReaderBuilder, SequentialWriterBuilder}; use crate::Request; /// A ClientConnection is an object that will store a socket to a client /// and return Request objects. pub struct ClientConnection { // address of the client remote_addr: IoResult>, // sequence of Readers to the stream, so that the data is not read in // the wrong order source: SequentialReaderBuilder>, // sequence of Writers to the stream, to avoid writing response #2 before // response #1 sink: SequentialWriterBuilder>, // Reader to read the next header from next_header_source: SequentialReader>, // set to true if we know that the previous request is the last one no_more_requests: bool, // true if the connection goes through SSL secure: bool, } /// Error that can happen when reading a request. #[derive(Debug)] enum ReadError { WrongRequestLine, WrongHeader(HTTPVersion), /// the client sent an unrecognized `Expect` header ExpectationFailed(HTTPVersion), ReadIoError(IoError), } impl ClientConnection { /// Creates a new `ClientConnection` that takes ownership of the `TcpStream`. pub fn new( write_socket: RefinedTcpStream, mut read_socket: RefinedTcpStream, ) -> ClientConnection { let remote_addr = read_socket.peer_addr(); let secure = read_socket.secure(); let mut source = SequentialReaderBuilder::new(BufReader::with_capacity(1024, read_socket)); let first_header = source.next().unwrap(); ClientConnection { source, sink: SequentialWriterBuilder::new(BufWriter::with_capacity(1024, write_socket)), remote_addr, next_header_source: first_header, no_more_requests: false, secure, } } /// true if the connection is HTTPS pub fn secure(&self) -> bool { self.secure } /// Reads the next line from self.next_header_source. /// /// Reads until `CRLF` is reached. The next read will start /// at the first byte of the new line. fn read_next_line(&mut self) -> IoResult { let mut buf = Vec::new(); let mut prev_byte_was_cr = false; loop { let byte = self.next_header_source.by_ref().bytes().next(); let byte = match byte { Some(b) => b?, None => return Err(IoError::new(ErrorKind::ConnectionAborted, "Unexpected EOF")), }; if byte == b'\n' && prev_byte_was_cr { buf.pop(); // removing the '\r' return AsciiString::from_ascii(buf) .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII")); } prev_byte_was_cr = byte == b'\r'; buf.push(byte); } } /// Reads a request from the stream. /// Blocks until the header has been read. fn read(&mut self) -> Result { let (method, path, version, headers) = { // reading the request line let (method, path, version) = { let line = self.read_next_line().map_err(ReadError::ReadIoError)?; parse_request_line( line.as_str().trim(), // TODO: remove this conversion )? }; // getting all headers let headers = { let mut headers = Vec::new(); loop { let line = self.read_next_line().map_err(ReadError::ReadIoError)?; if line.is_empty() { break; }; headers.push(match FromStr::from_str(line.as_str().trim()) { // TODO: remove this conversion Ok(h) => h, _ => return Err(ReadError::WrongHeader(version)), }); } headers }; (method, path, version, headers) }; // building the writer for the request let writer = self.sink.next().unwrap(); // follow-up for next potential request let mut data_source = self.source.next().unwrap(); std::mem::swap(&mut self.next_header_source, &mut data_source); // building the next reader let request = crate::request::new_request( self.secure, method, path, version.clone(), headers, *self.remote_addr.as_ref().unwrap(), data_source, writer, ) .map_err(|e| { use crate::request; match e { request::RequestCreationError::CreationIoError(e) => ReadError::ReadIoError(e), request::RequestCreationError::ExpectationFailed => { ReadError::ExpectationFailed(version) } } })?; // return the request Ok(request) } } impl Iterator for ClientConnection { type Item = Request; /// Blocks until the next Request is available. /// Returns None when no new Requests will come from the client. fn next(&mut self) -> Option { use crate::{Response, StatusCode}; // the client sent a "connection: close" header in this previous request // or is using HTTP 1.0, meaning that no new request will come if self.no_more_requests { return None; } loop { let rq = match self.read() { Err(ReadError::WrongRequestLine) => { let writer = self.sink.next().unwrap(); let response = Response::new_empty(StatusCode(400)); response .raw_print(writer, HTTPVersion(1, 1), &[], false, None) .ok(); return None; // we don't know where the next request would start, // se we have to close } Err(ReadError::WrongHeader(ver)) => { let writer = self.sink.next().unwrap(); let response = Response::new_empty(StatusCode(400)); response.raw_print(writer, ver, &[], false, None).ok(); return None; // we don't know where the next request would start, // se we have to close } Err(ReadError::ReadIoError(ref err)) if err.kind() == ErrorKind::TimedOut => { // request timeout let writer = self.sink.next().unwrap(); let response = Response::new_empty(StatusCode(408)); response .raw_print(writer, HTTPVersion(1, 1), &[], false, None) .ok(); return None; // closing the connection } Err(ReadError::ExpectationFailed(ver)) => { let writer = self.sink.next().unwrap(); let response = Response::new_empty(StatusCode(417)); response.raw_print(writer, ver, &[], true, None).ok(); return None; // TODO: should be recoverable, but needs handling in case of body } Err(ReadError::ReadIoError(_)) => return None, Ok(rq) => rq, }; // checking HTTP version if *rq.http_version() > (1, 1) { let writer = self.sink.next().unwrap(); let response = Response::from_string( "This server only supports HTTP versions 1.0 and 1.1".to_owned(), ) .with_status_code(StatusCode(505)); response .raw_print(writer, HTTPVersion(1, 1), &[], false, None) .ok(); continue; } // updating the status of the connection let connection_header = rq .headers() .iter() .find(|h| h.field.equiv("Connection")) .map(|h| h.value.as_str()); let lowercase = connection_header.map(|h| h.to_ascii_lowercase()); match lowercase { Some(ref val) if val.contains("close") => self.no_more_requests = true, Some(ref val) if val.contains("upgrade") => self.no_more_requests = true, Some(ref val) if !val.contains("keep-alive") && *rq.http_version() == HTTPVersion(1, 0) => { self.no_more_requests = true } None if *rq.http_version() == HTTPVersion(1, 0) => self.no_more_requests = true, _ => (), }; // returning the request return Some(rq); } } } /// Parses a "HTTP/1.1" string. fn parse_http_version(version: &str) -> Result { let (major, minor) = match version { "HTTP/0.9" => (0, 9), "HTTP/1.0" => (1, 0), "HTTP/1.1" => (1, 1), "HTTP/2.0" => (2, 0), "HTTP/3.0" => (3, 0), _ => return Err(ReadError::WrongRequestLine), }; Ok(HTTPVersion(major, minor)) } /// Parses the request line of the request. /// eg. GET / HTTP/1.1 fn parse_request_line(line: &str) -> Result<(Method, String, HTTPVersion), ReadError> { let mut parts = line.split(' '); let method = parts.next().and_then(|w| w.parse().ok()); let path = parts.next().map(ToOwned::to_owned); let version = parts.next().and_then(|w| parse_http_version(w).ok()); method .and_then(|method| Some((method, path?, version?))) .ok_or(ReadError::WrongRequestLine) } #[cfg(test)] mod test { #[test] fn test_parse_request_line() { let (method, path, ver) = super::parse_request_line("GET /hello HTTP/1.1").unwrap(); assert!(method == crate::Method::Get); assert!(path == "/hello"); assert!(ver == crate::common::HTTPVersion(1, 1)); assert!(super::parse_request_line("GET /hello").is_err()); assert!(super::parse_request_line("qsd qsd qsd").is_err()); } } tiny_http-0.12.0/src/common.rs000064400000000000000000000272570072674642500144200ustar 00000000000000use ascii::{AsciiStr, AsciiString, FromAsciiError}; use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; /// Status code of a request or response. #[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)] pub struct StatusCode(pub u16); impl StatusCode { /// Returns the default reason phrase for this status code. /// For example the status code 404 corresponds to "Not Found". pub fn default_reason_phrase(&self) -> &'static str { match self.0 { 100 => "Continue", 101 => "Switching Protocols", 102 => "Processing", 103 => "Early Hints", 200 => "OK", 201 => "Created", 202 => "Accepted", 203 => "Non-Authoritative Information", 204 => "No Content", 205 => "Reset Content", 206 => "Partial Content", 207 => "Multi-Status", 208 => "Already Reported", 226 => "IM Used", 300 => "Multiple Choices", 301 => "Moved Permanently", 302 => "Found", 303 => "See Other", 304 => "Not Modified", 305 => "Use Proxy", 307 => "Temporary Redirect", 308 => "Permanent Redirect", 400 => "Bad Request", 401 => "Unauthorized", 402 => "Payment Required", 403 => "Forbidden", 404 => "Not Found", 405 => "Method Not Allowed", 406 => "Not Acceptable", 407 => "Proxy Authentication Required", 408 => "Request Timeout", 409 => "Conflict", 410 => "Gone", 411 => "Length Required", 412 => "Precondition Failed", 413 => "Payload Too Large", 414 => "URI Too Long", 415 => "Unsupported Media Type", 416 => "Range Not Satisfiable", 417 => "Expectation Failed", 421 => "Misdirected Request", 422 => "Unprocessable Entity", 423 => "Locked", 424 => "Failed Dependency", 426 => "Upgrade Required", 428 => "Precondition Required", 429 => "Too Many Requests", 431 => "Request Header Fields Too Large", 451 => "Unavailable For Legal Reasons", 500 => "Internal Server Error", 501 => "Not Implemented", 502 => "Bad Gateway", 503 => "Service Unavailable", 504 => "Gateway Timeout", 505 => "HTTP Version Not Supported", 506 => "Variant Also Negotiates", 507 => "Insufficient Storage", 508 => "Loop Detected", 510 => "Not Extended", 511 => "Network Authentication Required", _ => "Unknown", } } } impl From for StatusCode { fn from(in_code: i8) -> StatusCode { StatusCode(in_code as u16) } } impl From for StatusCode { fn from(in_code: u8) -> StatusCode { StatusCode(in_code as u16) } } impl From for StatusCode { fn from(in_code: i16) -> StatusCode { StatusCode(in_code as u16) } } impl From for StatusCode { fn from(in_code: u16) -> StatusCode { StatusCode(in_code) } } impl From for StatusCode { fn from(in_code: i32) -> StatusCode { StatusCode(in_code as u16) } } impl From for StatusCode { fn from(in_code: u32) -> StatusCode { StatusCode(in_code as u16) } } impl AsRef for StatusCode { fn as_ref(&self) -> &u16 { &self.0 } } impl PartialEq for StatusCode { fn eq(&self, other: &u16) -> bool { &self.0 == other } } impl PartialEq for u16 { fn eq(&self, other: &StatusCode) -> bool { self == &other.0 } } impl PartialOrd for StatusCode { fn partial_cmp(&self, other: &u16) -> Option { self.0.partial_cmp(other) } } impl PartialOrd for u16 { fn partial_cmp(&self, other: &StatusCode) -> Option { self.partial_cmp(&other.0) } } /// Represents a HTTP header. #[derive(Debug, Clone)] pub struct Header { pub field: HeaderField, pub value: AsciiString, } impl Header { /// Builds a `Header` from two `Vec`s or two `&[u8]`s. /// /// Example: /// /// ``` /// let header = tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/plain"[..]).unwrap(); /// ``` #[allow(clippy::result_unit_err)] pub fn from_bytes(header: B1, value: B2) -> Result where B1: Into> + AsRef<[u8]>, B2: Into> + AsRef<[u8]>, { let header = HeaderField::from_bytes(header).or(Err(()))?; let value = AsciiString::from_ascii(value).or(Err(()))?; Ok(Header { field: header, value, }) } } impl FromStr for Header { type Err = (); fn from_str(input: &str) -> Result { let mut elems = input.splitn(2, ':'); let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?; let value = elems .next() .and_then(|v| AsciiString::from_ascii(v.trim()).ok()) .ok_or(())?; Ok(Header { field, value }) } } impl Display for Header { fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(formatter, "{}: {}", self.field, self.value.as_str()) } } /// Field of a header (eg. `Content-Type`, `Content-Length`, etc.) /// /// Comparison between two `HeaderField`s ignores case. #[derive(Debug, Clone, Eq)] pub struct HeaderField(AsciiString); impl HeaderField { pub fn from_bytes(bytes: B) -> Result> where B: Into> + AsRef<[u8]>, { AsciiString::from_ascii(bytes).map(HeaderField) } pub fn as_str(&self) -> &AsciiStr { &self.0 } pub fn equiv(&self, other: &'static str) -> bool { other.eq_ignore_ascii_case(self.as_str().as_str()) } } impl FromStr for HeaderField { type Err = (); fn from_str(s: &str) -> Result { if s.contains(char::is_whitespace) { Err(()) } else { AsciiString::from_ascii(s).map(HeaderField).map_err(|_| ()) } } } impl Display for HeaderField { fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(formatter, "{}", self.0.as_str()) } } impl PartialEq for HeaderField { fn eq(&self, other: &HeaderField) -> bool { let self_str: &str = self.as_str().as_ref(); let other_str = other.as_str().as_ref(); self_str.eq_ignore_ascii_case(other_str) } } /// HTTP request methods /// /// As per [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.1) and /// [RFC 5789](https://tools.ietf.org/html/rfc5789) #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Method { /// `GET` Get, /// `HEAD` Head, /// `POST` Post, /// `PUT` Put, /// `DELETE` Delete, /// `CONNECT` Connect, /// `OPTIONS` Options, /// `TRACE` Trace, /// `PATCH` Patch, /// Request methods not standardized by the IETF NonStandard(AsciiString), } impl Method { pub fn as_str(&self) -> &str { match *self { Method::Get => "GET", Method::Head => "HEAD", Method::Post => "POST", Method::Put => "PUT", Method::Delete => "DELETE", Method::Connect => "CONNECT", Method::Options => "OPTIONS", Method::Trace => "TRACE", Method::Patch => "PATCH", Method::NonStandard(ref s) => s.as_str(), } } } impl FromStr for Method { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "GET" => Method::Get, "HEAD" => Method::Head, "POST" => Method::Post, "PUT" => Method::Put, "DELETE" => Method::Delete, "CONNECT" => Method::Connect, "OPTIONS" => Method::Options, "TRACE" => Method::Trace, "PATCH" => Method::Patch, s => { let ascii_string = AsciiString::from_ascii(s).map_err(|_| ())?; Method::NonStandard(ascii_string) } }) } } impl Display for Method { fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(formatter, "{}", self.as_str()) } } /// HTTP version (usually 1.0 or 1.1). #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq)] pub struct HTTPVersion(pub u8, pub u8); impl Display for HTTPVersion { fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(formatter, "{}.{}", self.0, self.1) } } impl Ord for HTTPVersion { fn cmp(&self, other: &Self) -> Ordering { let HTTPVersion(my_major, my_minor) = *self; let HTTPVersion(other_major, other_minor) = *other; if my_major != other_major { return my_major.cmp(&other_major); } my_minor.cmp(&other_minor) } } impl PartialOrd for HTTPVersion { fn partial_cmp(&self, other: &HTTPVersion) -> Option { Some(self.cmp(other)) } } impl PartialEq<(u8, u8)> for HTTPVersion { fn eq(&self, &(major, minor): &(u8, u8)) -> bool { self.eq(&HTTPVersion(major, minor)) } } impl PartialEq for (u8, u8) { fn eq(&self, other: &HTTPVersion) -> bool { let &(major, minor) = self; HTTPVersion(major, minor).eq(other) } } impl PartialOrd<(u8, u8)> for HTTPVersion { fn partial_cmp(&self, &(major, minor): &(u8, u8)) -> Option { self.partial_cmp(&HTTPVersion(major, minor)) } } impl PartialOrd for (u8, u8) { fn partial_cmp(&self, other: &HTTPVersion) -> Option { let &(major, minor) = self; HTTPVersion(major, minor).partial_cmp(other) } } impl From<(u8, u8)> for HTTPVersion { fn from((major, minor): (u8, u8)) -> HTTPVersion { HTTPVersion(major, minor) } } #[cfg(test)] mod test { use super::Header; use httpdate::HttpDate; use std::time::{Duration, SystemTime}; #[test] fn test_parse_header() { let header: Header = "Content-Type: text/html".parse().unwrap(); assert!(header.field.equiv(&"content-type")); assert!(header.value.as_str() == "text/html"); assert!("hello world".parse::
().is_err()); } #[test] fn formats_date_correctly() { let http_date = HttpDate::from(SystemTime::UNIX_EPOCH + Duration::from_secs(420895020)); assert_eq!(http_date.to_string(), "Wed, 04 May 1983 11:17:00 GMT") } #[test] fn test_parse_header_with_doublecolon() { let header: Header = "Time: 20: 34".parse().unwrap(); assert!(header.field.equiv(&"time")); assert!(header.value.as_str() == "20: 34"); } // This tests reslstance to RUSTSEC-2020-0031: "HTTP Request smuggling // through malformed Transfer Encoding headers" // (https://rustsec.org/advisories/RUSTSEC-2020-0031.html). #[test] fn test_strict_headers() { assert!("Transfer-Encoding : chunked".parse::
().is_err()); assert!(" Transfer-Encoding: chunked".parse::
().is_err()); assert!("Transfer Encoding: chunked".parse::
().is_err()); assert!(" Transfer\tEncoding : chunked".parse::
().is_err()); assert!("Transfer-Encoding: chunked".parse::
().is_ok()); assert!("Transfer-Encoding: chunked ".parse::
().is_ok()); assert!("Transfer-Encoding: chunked ".parse::
().is_ok()); } } tiny_http-0.12.0/src/connection.rs000064400000000000000000000123260072674642500152560ustar 00000000000000//! Abstractions of Tcp and Unix socket types #[cfg(unix)] use std::os::unix::net as unix_net; use std::{ net::{Shutdown, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, path::PathBuf, }; /// Unified listener. Either a [`TcpListener`] or [`std::os::unix::net::UnixListener`] pub enum Listener { Tcp(TcpListener), #[cfg(unix)] Unix(unix_net::UnixListener), } impl Listener { pub(crate) fn local_addr(&self) -> std::io::Result { match self { Self::Tcp(l) => l.local_addr().map(ListenAddr::from), #[cfg(unix)] Self::Unix(l) => l.local_addr().map(ListenAddr::from), } } pub(crate) fn accept(&self) -> std::io::Result<(Connection, Option)> { match self { Self::Tcp(l) => l .accept() .map(|(conn, addr)| (Connection::from(conn), Some(addr))), #[cfg(unix)] Self::Unix(l) => l.accept().map(|(conn, _)| (Connection::from(conn), None)), } } } impl From for Listener { fn from(s: TcpListener) -> Self { Self::Tcp(s) } } #[cfg(unix)] impl From for Listener { fn from(s: unix_net::UnixListener) -> Self { Self::Unix(s) } } /// Unified connection. Either a [`TcpStream`] or [`std::os::unix::net::UnixStream`]. #[derive(Debug)] pub(crate) enum Connection { Tcp(TcpStream), #[cfg(unix)] Unix(unix_net::UnixStream), } impl std::io::Read for Connection { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { match self { Self::Tcp(s) => s.read(buf), #[cfg(unix)] Self::Unix(s) => s.read(buf), } } } impl std::io::Write for Connection { fn write(&mut self, buf: &[u8]) -> std::io::Result { match self { Self::Tcp(s) => s.write(buf), #[cfg(unix)] Self::Unix(s) => s.write(buf), } } fn flush(&mut self) -> std::io::Result<()> { match self { Self::Tcp(s) => s.flush(), #[cfg(unix)] Self::Unix(s) => s.flush(), } } } impl Connection { /// Gets the peer's address. Some for TCP, None for Unix sockets. pub(crate) fn peer_addr(&mut self) -> std::io::Result> { match self { Self::Tcp(s) => s.peer_addr().map(Some), #[cfg(unix)] Self::Unix(_) => Ok(None), } } pub(crate) fn shutdown(&self, how: Shutdown) -> std::io::Result<()> { match self { Self::Tcp(s) => s.shutdown(how), #[cfg(unix)] Self::Unix(s) => s.shutdown(how), } } pub(crate) fn try_clone(&self) -> std::io::Result { match self { Self::Tcp(s) => s.try_clone().map(Self::from), #[cfg(unix)] Self::Unix(s) => s.try_clone().map(Self::from), } } } impl From for Connection { fn from(s: TcpStream) -> Self { Self::Tcp(s) } } #[cfg(unix)] impl From for Connection { fn from(s: unix_net::UnixStream) -> Self { Self::Unix(s) } } #[derive(Debug, Clone)] pub enum ConfigListenAddr { IP(Vec), #[cfg(unix)] // TODO: use SocketAddr when bind_addr is stabilized Unix(std::path::PathBuf), } impl ConfigListenAddr { pub fn from_socket_addrs(addrs: A) -> std::io::Result { addrs.to_socket_addrs().map(|it| Self::IP(it.collect())) } #[cfg(unix)] pub fn unix_from_path>(path: P) -> Self { Self::Unix(path.into()) } pub(crate) fn bind(&self) -> std::io::Result { match self { Self::IP(a) => TcpListener::bind(a.as_slice()).map(Listener::from), #[cfg(unix)] Self::Unix(a) => unix_net::UnixListener::bind(a).map(Listener::from), } } } /// Unified listen socket address. Either a [`SocketAddr`] or [`std::os::unix::net::SocketAddr`]. #[derive(Debug, Clone)] pub enum ListenAddr { IP(SocketAddr), #[cfg(unix)] Unix(unix_net::SocketAddr), } impl ListenAddr { pub fn to_ip(self) -> Option { match self { Self::IP(s) => Some(s), #[cfg(unix)] Self::Unix(_) => None, } } /// Gets the Unix socket address. /// /// This is also available on non-Unix platforms, for ease of use, but always returns `None`. #[cfg(unix)] pub fn to_unix(self) -> Option { match self { Self::IP(_) => None, Self::Unix(s) => Some(s), } } #[cfg(not(unix))] pub fn to_unix(self) -> Option { None } } impl From for ListenAddr { fn from(s: SocketAddr) -> Self { Self::IP(s) } } #[cfg(unix)] impl From for ListenAddr { fn from(s: unix_net::SocketAddr) -> Self { Self::Unix(s) } } impl std::fmt::Display for ListenAddr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::IP(s) => s.fmt(f), #[cfg(unix)] Self::Unix(s) => std::fmt::Debug::fmt(s, f), } } } tiny_http-0.12.0/src/lib.rs000064400000000000000000000355630072674642500136750ustar 00000000000000//! # Simple usage //! //! ## Creating the server //! //! The easiest way to create a server is to call `Server::http()`. //! //! The `http()` function returns an `IoResult` which will return an error //! in the case where the server creation fails (for example if the listening port is already //! occupied). //! //! ```no_run //! let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); //! ``` //! //! A newly-created `Server` will immediately start listening for incoming connections and HTTP //! requests. //! //! ## Receiving requests //! //! Calling `server.recv()` will block until the next request is available. //! This function returns an `IoResult`, so you need to handle the possible errors. //! //! ```no_run //! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); //! //! loop { //! // blocks until the next request is received //! let request = match server.recv() { //! Ok(rq) => rq, //! Err(e) => { println!("error: {}", e); break } //! }; //! //! // do something with the request //! // ... //! } //! ``` //! //! In a real-case scenario, you will probably want to spawn multiple worker tasks and call //! `server.recv()` on all of them. Like this: //! //! ```no_run //! # use std::sync::Arc; //! # use std::thread; //! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); //! let server = Arc::new(server); //! let mut guards = Vec::with_capacity(4); //! //! for _ in (0 .. 4) { //! let server = server.clone(); //! //! let guard = thread::spawn(move || { //! loop { //! let rq = server.recv().unwrap(); //! //! // ... //! } //! }); //! //! guards.push(guard); //! } //! ``` //! //! If you don't want to block, you can call `server.try_recv()` instead. //! //! ## Handling requests //! //! The `Request` object returned by `server.recv()` contains informations about the client's request. //! The most useful methods are probably `request.method()` and `request.url()` which return //! the requested method (`GET`, `POST`, etc.) and url. //! //! To handle a request, you need to create a `Response` object. See the docs of this object for //! more infos. Here is an example of creating a `Response` from a file: //! //! ```no_run //! # use std::fs::File; //! # use std::path::Path; //! let response = tiny_http::Response::from_file(File::open(&Path::new("image.png")).unwrap()); //! ``` //! //! All that remains to do is call `request.respond()`: //! //! ```no_run //! # use std::fs::File; //! # use std::path::Path; //! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); //! # let request = server.recv().unwrap(); //! # let response = tiny_http::Response::from_file(File::open(&Path::new("image.png")).unwrap()); //! let _ = request.respond(response); //! ``` #![forbid(unsafe_code)] #![deny(rust_2018_idioms)] #![allow(clippy::match_like_matches_macro)] #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] use zeroize::Zeroizing; use std::error::Error; use std::io::Error as IoError; use std::io::ErrorKind as IoErrorKind; use std::io::Result as IoResult; use std::net::{Shutdown, TcpStream, ToSocketAddrs}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; use std::sync::mpsc; use std::sync::Arc; use std::thread; use std::time::Duration; use client::ClientConnection; use connection::Connection; use util::MessagesQueue; pub use common::{HTTPVersion, Header, HeaderField, Method, StatusCode}; pub use connection::{ConfigListenAddr, ListenAddr, Listener}; pub use request::{ReadWrite, Request}; pub use response::{Response, ResponseBox}; pub use test::TestRequest; mod client; mod common; mod connection; mod request; mod response; mod ssl; mod test; mod util; /// The main class of this library. /// /// Destroying this object will immediately close the listening socket and the reading /// part of all the client's connections. Requests that have already been returned by /// the `recv()` function will not close and the responses will be transferred to the client. pub struct Server { // should be false as long as the server exists // when set to true, all the subtasks will close within a few hundreds ms close: Arc, // queue for messages received by child threads messages: Arc>, // result of TcpListener::local_addr() listening_addr: ListenAddr, } enum Message { Error(IoError), NewRequest(Request), } impl From for Message { fn from(e: IoError) -> Message { Message::Error(e) } } impl From for Message { fn from(rq: Request) -> Message { Message::NewRequest(rq) } } // this trait is to make sure that Server implements Share and Send #[doc(hidden)] trait MustBeShareDummy: Sync + Send {} #[doc(hidden)] impl MustBeShareDummy for Server {} pub struct IncomingRequests<'a> { server: &'a Server, } /// Represents the parameters required to create a server. #[derive(Debug, Clone)] pub struct ServerConfig { /// The addresses to try to listen to. pub addr: ConfigListenAddr, /// If `Some`, then the server will use SSL to encode the communications. pub ssl: Option, } /// Configuration of the server for SSL. #[derive(Debug, Clone)] pub struct SslConfig { /// Contains the public certificate to send to clients. pub certificate: Vec, /// Contains the ultra-secret private key used to decode communications. pub private_key: Vec, } impl Server { /// Shortcut for a simple server on a specific address. #[inline] pub fn http(addr: A) -> Result> where A: ToSocketAddrs, { Server::new(ServerConfig { addr: ConfigListenAddr::from_socket_addrs(addr)?, ssl: None, }) } /// Shortcut for an HTTPS server on a specific address. #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] #[inline] pub fn https( addr: A, config: SslConfig, ) -> Result> where A: ToSocketAddrs, { Server::new(ServerConfig { addr: ConfigListenAddr::from_socket_addrs(addr)?, ssl: Some(config), }) } #[cfg(unix)] #[inline] /// Shortcut for a UNIX socket server at a specific path pub fn http_unix( path: &std::path::Path, ) -> Result> { Server::new(ServerConfig { addr: ConfigListenAddr::unix_from_path(path), ssl: None, }) } /// Builds a new server that listens on the specified address. pub fn new(config: ServerConfig) -> Result> { let listener = config.addr.bind()?; Self::from_listener(listener, config.ssl) } /// Builds a new server using the specified TCP listener. /// /// This is useful if you've constructed TcpListener using some less usual method /// such as from systemd. For other cases, you probably want the `new()` function. pub fn from_listener>( listener: L, ssl_config: Option, ) -> Result> { let listener = listener.into(); // building the "close" variable let close_trigger = Arc::new(AtomicBool::new(false)); // building the TcpListener let (server, local_addr) = { let local_addr = listener.local_addr()?; log::debug!("Server listening on {}", local_addr); (listener, local_addr) }; // building the SSL capabilities #[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))] compile_error!( "Features 'ssl-openssl' and 'ssl-rustls' must not be enabled at the same time" ); #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))] type SslContext = (); #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] type SslContext = crate::ssl::SslContextImpl; let ssl: Option = { match ssl_config { #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Some(config) => Some(SslContext::from_pem( config.certificate, Zeroizing::new(config.private_key), )?), #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))] Some(_) => return Err( "Building a server with SSL requires enabling the `ssl` feature in tiny-http" .into(), ), None => None, } }; // creating a task where server.accept() is continuously called // and ClientConnection objects are pushed in the messages queue let messages = MessagesQueue::with_capacity(8); let inside_close_trigger = close_trigger.clone(); let inside_messages = messages.clone(); thread::spawn(move || { // a tasks pool is used to dispatch the connections into threads let tasks_pool = util::TaskPool::new(); log::debug!("Running accept thread"); while !inside_close_trigger.load(Relaxed) { let new_client = match server.accept() { Ok((sock, _)) => { use util::RefinedTcpStream; let (read_closable, write_closable) = match ssl { None => RefinedTcpStream::new(sock), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Some(ref ssl) => { // trying to apply SSL over the connection // if an error occurs, we just close the socket and resume listening let sock = match ssl.accept(sock) { Ok(s) => s, Err(_) => continue, }; RefinedTcpStream::new(sock) } #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))] Some(ref _ssl) => unreachable!(), }; Ok(ClientConnection::new(write_closable, read_closable)) } Err(e) => Err(e), }; match new_client { Ok(client) => { let messages = inside_messages.clone(); let mut client = Some(client); tasks_pool.spawn(Box::new(move || { if let Some(client) = client.take() { // Synchronization is needed for HTTPS requests to avoid a deadlock if client.secure() { let (sender, receiver) = mpsc::channel(); for rq in client { messages.push(rq.with_notify_sender(sender.clone()).into()); receiver.recv().unwrap(); } } else { for rq in client { messages.push(rq.into()); } } } })); } Err(e) => { log::error!("Error accepting new client: {}", e); inside_messages.push(e.into()); break; } } } log::debug!("Terminating accept thread"); }); // result Ok(Server { messages, close: close_trigger, listening_addr: local_addr, }) } /// Returns an iterator for all the incoming requests. /// /// The iterator will return `None` if the server socket is shutdown. #[inline] pub fn incoming_requests(&self) -> IncomingRequests<'_> { IncomingRequests { server: self } } /// Returns the address the server is listening to. #[inline] pub fn server_addr(&self) -> ListenAddr { self.listening_addr.clone() } /// Returns the number of clients currently connected to the server. pub fn num_connections(&self) -> usize { unimplemented!() //self.requests_receiver.lock().len() } /// Blocks until an HTTP request has been submitted and returns it. pub fn recv(&self) -> IoResult { match self.messages.pop() { Some(Message::Error(err)) => Err(err), Some(Message::NewRequest(rq)) => Ok(rq), None => Err(IoError::new(IoErrorKind::Other, "thread unblocked")), } } /// Same as `recv()` but doesn't block longer than timeout pub fn recv_timeout(&self, timeout: Duration) -> IoResult> { match self.messages.pop_timeout(timeout) { Some(Message::Error(err)) => Err(err), Some(Message::NewRequest(rq)) => Ok(Some(rq)), None => Ok(None), } } /// Same as `recv()` but doesn't block. pub fn try_recv(&self) -> IoResult> { match self.messages.try_pop() { Some(Message::Error(err)) => Err(err), Some(Message::NewRequest(rq)) => Ok(Some(rq)), None => Ok(None), } } /// Unblock thread stuck in recv() or incoming_requests(). /// If there are several such threads, only one is unblocked. /// This method allows graceful shutdown of server. pub fn unblock(&self) { self.messages.unblock(); } } impl Iterator for IncomingRequests<'_> { type Item = Request; fn next(&mut self) -> Option { self.server.recv().ok() } } impl Drop for Server { fn drop(&mut self) { self.close.store(true, Relaxed); // Connect briefly to ourselves to unblock the accept thread let maybe_stream = match &self.listening_addr { ListenAddr::IP(addr) => TcpStream::connect(addr).map(Connection::from), #[cfg(unix)] ListenAddr::Unix(addr) => { // TODO: use connect_addr when its stabilized. let path = addr.as_pathname().unwrap(); std::os::unix::net::UnixStream::connect(path).map(Connection::from) } }; if let Ok(stream) = maybe_stream { let _ = stream.shutdown(Shutdown::Both); } #[cfg(unix)] if let ListenAddr::Unix(addr) = &self.listening_addr { if let Some(path) = addr.as_pathname() { let _ = std::fs::remove_file(path); } } } } tiny_http-0.12.0/src/request.rs000064400000000000000000000416350072674642500146140ustar 00000000000000use std::io::Error as IoError; use std::io::{self, Cursor, ErrorKind, Read, Write}; use std::fmt; use std::net::SocketAddr; use std::str::FromStr; use std::sync::mpsc::Sender; use crate::util::{EqualReader, FusedReader}; use crate::{HTTPVersion, Header, Method, Response, StatusCode}; use chunked_transfer::Decoder; /// Represents an HTTP request made by a client. /// /// A `Request` object is what is produced by the server, and is your what /// your code must analyse and answer. /// /// This object implements the `Send` trait, therefore you can dispatch your requests to /// worker threads. /// /// # Pipelining /// /// If a client sends multiple requests in a row (without waiting for the response), then you will /// get multiple `Request` objects simultaneously. This is called *requests pipelining*. /// Tiny-http automatically reorders the responses so that you don't need to worry about the order /// in which you call `respond` or `into_writer`. /// /// This mechanic is disabled if: /// /// - The body of a request is large enough (handling requires pipelining requires storing the /// body of the request in a buffer ; if the body is too big, tiny-http will avoid doing that) /// - A request sends a `Expect: 100-continue` header (which means that the client waits to /// know whether its body will be processed before sending it) /// - A request sends a `Connection: close` header or `Connection: upgrade` header (used for /// websockets), which indicates that this is the last request that will be received on this /// connection /// /// # Automatic cleanup /// /// If a `Request` object is destroyed without `into_writer` or `respond` being called, /// an empty response with a 500 status code (internal server error) will automatically be /// sent back to the client. /// This means that if your code fails during the handling of a request, this "internal server /// error" response will automatically be sent during the stack unwinding. /// /// # Testing /// /// If you want to build fake requests to test your server, use [`TestRequest`](crate::test::TestRequest). pub struct Request { // where to read the body from data_reader: Option>, // if this writer is empty, then the request has been answered response_writer: Option>, remote_addr: Option, // true if HTTPS, false if HTTP secure: bool, method: Method, path: String, http_version: HTTPVersion, headers: Vec
, body_length: Option, // true if a `100 Continue` response must be sent when `as_reader()` is called must_send_continue: bool, // If Some, a message must be sent after responding notify_when_responded: Option>, } struct NotifyOnDrop { sender: Sender<()>, inner: R, } impl Read for NotifyOnDrop { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.inner.read(buf) } } impl Write for NotifyOnDrop { fn write(&mut self, buf: &[u8]) -> io::Result { self.inner.write(buf) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl Drop for NotifyOnDrop { fn drop(&mut self) { self.sender.send(()).unwrap(); } } /// Error that can happen when building a `Request` object. #[derive(Debug)] pub enum RequestCreationError { /// The client sent an `Expect` header that was not recognized by tiny-http. ExpectationFailed, /// Error while reading data from the socket during the creation of the `Request`. CreationIoError(IoError), } impl From for RequestCreationError { fn from(err: IoError) -> RequestCreationError { RequestCreationError::CreationIoError(err) } } /// Builds a new request. /// /// After the request line and headers have been read from the socket, a new `Request` object /// is built. /// /// You must pass a `Read` that will allow the `Request` object to read from the incoming data. /// It is the responsibility of the `Request` to read only the data of the request and not further. /// /// The `Write` object will be used by the `Request` to write the response. #[allow(clippy::too_many_arguments)] pub fn new_request( secure: bool, method: Method, path: String, version: HTTPVersion, headers: Vec
, remote_addr: Option, mut source_data: R, writer: W, ) -> Result where R: Read + Send + 'static, W: Write + Send + 'static, { // finding the transfer-encoding header let transfer_encoding = headers .iter() .find(|h: &&Header| h.field.equiv("Transfer-Encoding")) .map(|h| h.value.clone()); // finding the content-length header let content_length = if transfer_encoding.is_some() { // if transfer-encoding is specified, the Content-Length // header must be ignored (RFC2616 #4.4) None } else { headers .iter() .find(|h: &&Header| h.field.equiv("Content-Length")) .and_then(|h| FromStr::from_str(h.value.as_str()).ok()) }; // true if the client sent a `Expect: 100-continue` header let expects_continue = { match headers .iter() .find(|h: &&Header| h.field.equiv("Expect")) .map(|h| h.value.as_str()) { None => false, Some(v) if v.eq_ignore_ascii_case("100-continue") => true, _ => return Err(RequestCreationError::ExpectationFailed), } }; // true if the client sent a `Connection: upgrade` header let connection_upgrade = { match headers .iter() .find(|h: &&Header| h.field.equiv("Connection")) .map(|h| h.value.as_str()) { Some(v) if v.to_ascii_lowercase().contains("upgrade") => true, _ => false, } }; // we wrap `source_data` around a reading whose nature depends on the transfer-encoding and // content-length headers let reader = if connection_upgrade { // if we have a `Connection: upgrade`, always keeping the whole reader Box::new(source_data) as Box } else if let Some(content_length) = content_length { if content_length == 0 { Box::new(io::empty()) as Box } else if content_length <= 1024 && !expects_continue { // if the content-length is small enough, we just read everything into a buffer let mut buffer = vec![0; content_length]; let mut offset = 0; while offset != content_length { let read = source_data.read(&mut buffer[offset..])?; if read == 0 { // the socket returned EOF, but we were before the expected content-length // aborting let info = "Connection has been closed before we received enough data"; let err = IoError::new(ErrorKind::ConnectionAborted, info); return Err(RequestCreationError::CreationIoError(err)); } offset += read; } Box::new(Cursor::new(buffer)) as Box } else { let (data_reader, _) = EqualReader::new(source_data, content_length); // TODO: Box::new(FusedReader::new(data_reader)) as Box } } else if transfer_encoding.is_some() { // if a transfer-encoding was specified, then "chunked" is ALWAYS applied // over the message (RFC2616 #3.6) Box::new(FusedReader::new(Decoder::new(source_data))) as Box } else { // if we have neither a Content-Length nor a Transfer-Encoding, // assuming that we have no data // TODO: could also be multipart/byteranges Box::new(io::empty()) as Box }; Ok(Request { data_reader: Some(reader), response_writer: Some(Box::new(writer) as Box), remote_addr, secure, method, path, http_version: version, headers, body_length: content_length, must_send_continue: expects_continue, notify_when_responded: None, }) } impl Request { /// Returns true if the request was made through HTTPS. #[inline] pub fn secure(&self) -> bool { self.secure } /// Returns the method requested by the client (eg. `GET`, `POST`, etc.). #[inline] pub fn method(&self) -> &Method { &self.method } /// Returns the resource requested by the client. #[inline] pub fn url(&self) -> &str { &self.path } /// Returns a list of all headers sent by the client. #[inline] pub fn headers(&self) -> &[Header] { &self.headers } /// Returns the HTTP version of the request. #[inline] pub fn http_version(&self) -> &HTTPVersion { &self.http_version } /// Returns the length of the body in bytes. /// /// Returns `None` if the length is unknown. #[inline] pub fn body_length(&self) -> Option { self.body_length } /// Returns the address of the client that sent this request. /// /// The address is always `Some` for TCP listeners, but always `None` for UNIX listeners /// (as the remote address of a UNIX client is almost always unnamed). /// /// Note that this is gathered from the socket. If you receive the request from a proxy, /// this function will return the address of the proxy and not the address of the actual /// user. #[inline] pub fn remote_addr(&self) -> Option<&SocketAddr> { self.remote_addr.as_ref() } /// Sends a response with a `Connection: upgrade` header, then turns the `Request` into a `Stream`. /// /// The main purpose of this function is to support websockets. /// If you detect that the request wants to use some kind of protocol upgrade, you can /// call this function to obtain full control of the socket stream. /// /// If you call this on a non-websocket request, tiny-http will wait until this `Stream` object /// is destroyed before continuing to read or write on the socket. Therefore you should always /// destroy it as soon as possible. pub fn upgrade( mut self, protocol: &str, response: Response, ) -> Box { use crate::util::CustomStream; response .raw_print( self.response_writer.as_mut().unwrap().by_ref(), self.http_version.clone(), &self.headers, false, Some(protocol), ) .ok(); // TODO: unused result self.response_writer.as_mut().unwrap().flush().ok(); // TODO: unused result let stream = CustomStream::new(self.extract_reader_impl(), self.extract_writer_impl()); if let Some(sender) = self.notify_when_responded.take() { let stream = NotifyOnDrop { sender, inner: stream, }; Box::new(stream) as Box } else { Box::new(stream) as Box } } /// Allows to read the body of the request. /// /// # Example /// /// ```no_run /// # extern crate rustc_serialize; /// # extern crate tiny_http; /// # use rustc_serialize::json::Json; /// # use std::io::Read; /// # fn get_content_type(_: &tiny_http::Request) -> &'static str { "" } /// # fn main() { /// # let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); /// let mut request = server.recv().unwrap(); /// /// if get_content_type(&request) == "application/json" { /// let mut content = String::new(); /// request.as_reader().read_to_string(&mut content).unwrap(); /// let json: Json = content.parse().unwrap(); /// } /// # } /// ``` /// /// If the client sent a `Expect: 100-continue` header with the request, calling this /// function will send back a `100 Continue` response. #[inline] pub fn as_reader(&mut self) -> &mut dyn Read { if self.must_send_continue { let msg = Response::new_empty(StatusCode(100)); msg.raw_print( self.response_writer.as_mut().unwrap().by_ref(), self.http_version.clone(), &self.headers, true, None, ) .ok(); self.response_writer.as_mut().unwrap().flush().ok(); self.must_send_continue = false; } self.data_reader.as_mut().unwrap() } /// Turns the `Request` into a writer. /// /// The writer has a raw access to the stream to the user. /// This function is useful for things like CGI. /// /// Note that the destruction of the `Writer` object may trigger /// some events. For exemple if a client has sent multiple requests and the requests /// have been processed in parallel, the destruction of a writer will trigger /// the writing of the next response. /// Therefore you should always destroy the `Writer` as soon as possible. #[inline] pub fn into_writer(mut self) -> Box { let writer = self.extract_writer_impl(); if let Some(sender) = self.notify_when_responded.take() { let writer = NotifyOnDrop { sender, inner: writer, }; Box::new(writer) as Box } else { writer } } /// Extract the response `Writer` object from the Request, dropping this `Writer` has the same side effects /// as the object returned by `into_writer` above. /// /// This may only be called once on a single request. fn extract_writer_impl(&mut self) -> Box { use std::mem; assert!(self.response_writer.is_some()); let mut writer = None; mem::swap(&mut self.response_writer, &mut writer); writer.unwrap() } /// Extract the body `Reader` object from the Request. /// /// This may only be called once on a single request. fn extract_reader_impl(&mut self) -> Box { use std::mem; assert!(self.data_reader.is_some()); let mut reader = None; mem::swap(&mut self.data_reader, &mut reader); reader.unwrap() } /// Sends a response to this request. #[inline] pub fn respond(mut self, response: Response) -> Result<(), IoError> where R: Read, { let res = self.respond_impl(response); if let Some(sender) = self.notify_when_responded.take() { sender.send(()).unwrap(); } res } fn respond_impl(&mut self, response: Response) -> Result<(), IoError> where R: Read, { let mut writer = self.extract_writer_impl(); let do_not_send_body = self.method == Method::Head; Self::ignore_client_closing_errors(response.raw_print( writer.by_ref(), self.http_version.clone(), &self.headers, do_not_send_body, None, ))?; Self::ignore_client_closing_errors(writer.flush()) } fn ignore_client_closing_errors(result: io::Result<()>) -> io::Result<()> { result.or_else(|err| match err.kind() { ErrorKind::BrokenPipe => Ok(()), ErrorKind::ConnectionAborted => Ok(()), ErrorKind::ConnectionRefused => Ok(()), ErrorKind::ConnectionReset => Ok(()), _ => Err(err), }) } pub(crate) fn with_notify_sender(mut self, sender: Sender<()>) -> Self { self.notify_when_responded = Some(sender); self } } impl fmt::Debug for Request { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( formatter, "Request({} {} from {:?})", self.method, self.path, self.remote_addr ) } } impl Drop for Request { fn drop(&mut self) { if self.response_writer.is_some() { let response = Response::empty(500); let _ = self.respond_impl(response); // ignoring any potential error if let Some(sender) = self.notify_when_responded.take() { sender.send(()).unwrap(); } } } } /// Dummy trait that regroups the `Read` and `Write` traits. /// /// Automatically implemented on all types that implement both `Read` and `Write`. pub trait ReadWrite: Read + Write {} impl ReadWrite for T where T: Read + Write {} #[cfg(test)] mod tests { use super::Request; #[test] fn must_be_send() { #![allow(dead_code)] fn f(_: &T) {} fn bar(rq: &Request) { f(rq); } } } tiny_http-0.12.0/src/response.rs000064400000000000000000000413520072674642500147560ustar 00000000000000use crate::common::{HTTPVersion, Header, StatusCode}; use httpdate::HttpDate; use std::cmp::Ordering; use std::sync::mpsc::Receiver; use std::io::Result as IoResult; use std::io::{self, Cursor, Read, Write}; use std::fs::File; use std::str::FromStr; use std::time::SystemTime; /// Object representing an HTTP response whose purpose is to be given to a `Request`. /// /// Some headers cannot be changed. Trying to define the value /// of one of these will have no effect: /// /// - `Connection` /// - `Trailer` /// - `Transfer-Encoding` /// - `Upgrade` /// /// Some headers have special behaviors: /// /// - `Content-Encoding`: If you define this header, the library /// will assume that the data from the `Read` object has the specified encoding /// and will just pass-through. /// /// - `Content-Length`: The length of the data should be set manually /// using the `Reponse` object's API. Attempting to set the value of this /// header will be equivalent to modifying the size of the data but the header /// itself may not be present in the final result. /// /// - `Content-Type`: You may only set this header to one value at a time. If you /// try to set it more than once, the existing value will be overwritten. This /// behavior differs from the default for most headers, which is to allow them to /// be set multiple times in the same response. /// pub struct Response { reader: R, status_code: StatusCode, headers: Vec
, data_length: Option, chunked_threshold: Option, } /// A `Response` without a template parameter. pub type ResponseBox = Response>; /// Transfer encoding to use when sending the message. /// Note that only *supported* encoding are listed here. #[derive(Copy, Clone)] enum TransferEncoding { Identity, Chunked, } impl FromStr for TransferEncoding { type Err = (); fn from_str(input: &str) -> Result { if input.eq_ignore_ascii_case("identity") { Ok(TransferEncoding::Identity) } else if input.eq_ignore_ascii_case("chunked") { Ok(TransferEncoding::Chunked) } else { Err(()) } } } /// Builds a Date: header with the current date. fn build_date_header() -> Header { let d = HttpDate::from(SystemTime::now()); Header::from_bytes(&b"Date"[..], &d.to_string().into_bytes()[..]).unwrap() } fn write_message_header( mut writer: W, http_version: &HTTPVersion, status_code: &StatusCode, headers: &[Header], ) -> IoResult<()> where W: Write, { // writing status line write!( &mut writer, "HTTP/{}.{} {} {}\r\n", http_version.0, http_version.1, status_code.0, status_code.default_reason_phrase() )?; // writing headers for header in headers.iter() { writer.write_all(header.field.as_str().as_ref())?; write!(&mut writer, ": ")?; writer.write_all(header.value.as_str().as_ref())?; write!(&mut writer, "\r\n")?; } // separator between header and data write!(&mut writer, "\r\n")?; Ok(()) } fn choose_transfer_encoding( status_code: StatusCode, request_headers: &[Header], http_version: &HTTPVersion, entity_length: &Option, has_additional_headers: bool, chunked_threshold: usize, ) -> TransferEncoding { use crate::util; // HTTP 1.0 doesn't support other encoding if *http_version <= (1, 0) { return TransferEncoding::Identity; } // Per section 3.3.1 of RFC7230: // A server MUST NOT send a Transfer-Encoding header field in any response with a status code // of 1xx (Informational) or 204 (No Content). if status_code.0 < 200 || status_code.0 == 204 { return TransferEncoding::Identity; } // parsing the request's TE header let user_request = request_headers .iter() // finding TE .find(|h| h.field.equiv("TE")) // getting its value .map(|h| h.value.clone()) // getting the corresponding TransferEncoding .and_then(|value| { // getting list of requested elements let mut parse = util::parse_header_value(value.as_str()); // TODO: remove conversion // sorting elements by most priority parse.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal)); // trying to parse each requested encoding for value in parse.iter() { // q=0 are ignored if value.1 <= 0.0 { continue; } if let Ok(te) = TransferEncoding::from_str(value.0) { return Some(te); } } // encoding not found None }); if let Some(user_request) = user_request { return user_request; } // if we have additional headers, using chunked if has_additional_headers { return TransferEncoding::Chunked; } // if we don't have a Content-Length, or if the Content-Length is too big, using chunks writer if entity_length .as_ref() .map_or(true, |val| *val >= chunked_threshold) { return TransferEncoding::Chunked; } // Identity by default TransferEncoding::Identity } impl Response where R: Read, { /// Creates a new Response object. /// /// The `additional_headers` argument is a receiver that /// may provide headers even after the response has been sent. /// /// All the other arguments are straight-forward. pub fn new( status_code: StatusCode, headers: Vec
, data: R, data_length: Option, additional_headers: Option>, ) -> Response { let mut response = Response { reader: data, status_code, headers: Vec::with_capacity(16), data_length, chunked_threshold: None, }; for h in headers { response.add_header(h) } // dummy implementation if let Some(additional_headers) = additional_headers { for h in additional_headers.iter() { response.add_header(h) } } response } /// Set a threshold for `Content-Length` where we chose chunked /// transfer. Notice that chunked transfer might happen regardless of /// this threshold, for instance when the request headers indicate /// it is wanted or when there is no `Content-Length`. pub fn with_chunked_threshold(mut self, length: usize) -> Response { self.chunked_threshold = Some(length); self } /// Convert the response into the underlying `Read` type. /// /// This is mainly useful for testing as it must consume the `Response`. pub fn into_reader(self) -> R { self.reader } /// The current `Content-Length` threshold for switching over to /// chunked transfer. The default is 32768 bytes. Notice that /// chunked transfer is mutually exclusive with sending a /// `Content-Length` header as per the HTTP spec. pub fn chunked_threshold(&self) -> usize { self.chunked_threshold.unwrap_or(32768) } /// Adds a header to the list. /// Does all the checks. pub fn add_header(&mut self, header: H) where H: Into
, { let header = header.into(); // ignoring forbidden headers if header.field.equiv("Connection") || header.field.equiv("Trailer") || header.field.equiv("Transfer-Encoding") || header.field.equiv("Upgrade") { return; } // if the header is Content-Length, setting the data length if header.field.equiv("Content-Length") { if let Ok(val) = usize::from_str(header.value.as_str()) { self.data_length = Some(val) } return; // if the header is Content-Type and it's already set, overwrite it } else if header.field.equiv("Content-Type") { if let Some(content_type_header) = self .headers .iter_mut() .find(|h| h.field.equiv("Content-Type")) { content_type_header.value = header.value; return; } } self.headers.push(header); } /// Returns the same request, but with an additional header. /// /// Some headers cannot be modified and some other have a /// special behavior. See the documentation above. #[inline] pub fn with_header(mut self, header: H) -> Response where H: Into
, { self.add_header(header.into()); self } /// Returns the same request, but with a different status code. #[inline] pub fn with_status_code(mut self, code: S) -> Response where S: Into, { self.status_code = code.into(); self } /// Returns the same request, but with different data. pub fn with_data(self, reader: S, data_length: Option) -> Response where S: Read, { Response { reader, headers: self.headers, status_code: self.status_code, data_length, chunked_threshold: self.chunked_threshold, } } /// Prints the HTTP response to a writer. /// /// This function is the one used to send the response to the client's socket. /// Therefore you shouldn't expect anything pretty-printed or even readable. /// /// The HTTP version and headers passed as arguments are used to /// decide which features (most notably, encoding) to use. /// /// Note: does not flush the writer. pub fn raw_print( mut self, mut writer: W, http_version: HTTPVersion, request_headers: &[Header], do_not_send_body: bool, upgrade: Option<&str>, ) -> IoResult<()> { let mut transfer_encoding = Some(choose_transfer_encoding( self.status_code, request_headers, &http_version, &self.data_length, false, /* TODO */ self.chunked_threshold(), )); // add `Date` if not in the headers if !self.headers.iter().any(|h| h.field.equiv("Date")) { self.headers.insert(0, build_date_header()); } // add `Server` if not in the headers if !self.headers.iter().any(|h| h.field.equiv("Server")) { self.headers.insert( 0, Header::from_bytes(&b"Server"[..], &b"tiny-http (Rust)"[..]).unwrap(), ); } // handling upgrade if let Some(upgrade) = upgrade { self.headers.insert( 0, Header::from_bytes(&b"Upgrade"[..], upgrade.as_bytes()).unwrap(), ); self.headers.insert( 0, Header::from_bytes(&b"Connection"[..], &b"upgrade"[..]).unwrap(), ); transfer_encoding = None; } // if the transfer encoding is identity, the content length must be known ; therefore if // we don't know it, we buffer the entire response first here // while this is an expensive operation, it is only ever needed for clients using HTTP 1.0 let (mut reader, data_length): (Box, _) = match (self.data_length, transfer_encoding) { (Some(l), _) => (Box::new(self.reader), Some(l)), (None, Some(TransferEncoding::Identity)) => { let mut buf = Vec::new(); self.reader.read_to_end(&mut buf)?; let l = buf.len(); (Box::new(Cursor::new(buf)), Some(l)) } _ => (Box::new(self.reader), None), }; // checking whether to ignore the body of the response let do_not_send_body = do_not_send_body || match self.status_code.0 { // status code 1xx, 204 and 304 MUST not include a body 100..=199 | 204 | 304 => true, _ => false, }; // preparing headers for transfer match transfer_encoding { Some(TransferEncoding::Chunked) => self .headers .push(Header::from_bytes(&b"Transfer-Encoding"[..], &b"chunked"[..]).unwrap()), Some(TransferEncoding::Identity) => { assert!(data_length.is_some()); let data_length = data_length.unwrap(); self.headers.push( Header::from_bytes( &b"Content-Length"[..], format!("{}", data_length).as_bytes(), ) .unwrap(), ) } _ => (), }; // sending headers write_message_header( writer.by_ref(), &http_version, &self.status_code, &self.headers, )?; // sending the body if !do_not_send_body { match transfer_encoding { Some(TransferEncoding::Chunked) => { use chunked_transfer::Encoder; let mut writer = Encoder::new(writer); io::copy(&mut reader, &mut writer)?; } Some(TransferEncoding::Identity) => { assert!(data_length.is_some()); let data_length = data_length.unwrap(); if data_length >= 1 { io::copy(&mut reader, &mut writer)?; } } _ => (), } } Ok(()) } /// Retrieves the current value of the `Response` status code pub fn status_code(&self) -> StatusCode { self.status_code } /// Retrieves the current value of the `Response` data length pub fn data_length(&self) -> Option { self.data_length } /// Retrieves the current list of `Response` headers pub fn headers(&self) -> &[Header] { &self.headers } } impl Response where R: Read + Send + 'static, { /// Turns this response into a `Response>`. pub fn boxed(self) -> ResponseBox { Response { reader: Box::new(self.reader) as Box, status_code: self.status_code, headers: self.headers, data_length: self.data_length, chunked_threshold: self.chunked_threshold, } } } impl Response { /// Builds a new `Response` from a `File`. /// /// The `Content-Type` will **not** be automatically detected, /// you must set it yourself. pub fn from_file(file: File) -> Response { let file_size = file.metadata().ok().map(|v| v.len() as usize); Response::new( StatusCode(200), Vec::with_capacity(0), file, file_size, None, ) } } impl Response>> { pub fn from_data(data: D) -> Response>> where D: Into>, { let data = data.into(); let data_len = data.len(); Response::new( StatusCode(200), Vec::with_capacity(0), Cursor::new(data), Some(data_len), None, ) } pub fn from_string(data: S) -> Response>> where S: Into, { let data = data.into(); let data_len = data.len(); Response::new( StatusCode(200), vec![ Header::from_bytes(&b"Content-Type"[..], &b"text/plain; charset=UTF-8"[..]) .unwrap(), ], Cursor::new(data.into_bytes()), Some(data_len), None, ) } } impl Response { /// Builds an empty `Response` with the given status code. pub fn empty(status_code: S) -> Response where S: Into, { Response::new( status_code.into(), Vec::with_capacity(0), io::empty(), Some(0), None, ) } /// DEPRECATED. Use `empty` instead. pub fn new_empty(status_code: StatusCode) -> Response { Response::empty(status_code) } } impl Clone for Response { fn clone(&self) -> Response { Response { reader: io::empty(), status_code: self.status_code, headers: self.headers.clone(), data_length: self.data_length, chunked_threshold: self.chunked_threshold, } } } tiny_http-0.12.0/src/ssl/openssl.rs000064400000000000000000000065310072674642500154040ustar 00000000000000use crate::connection::Connection; use crate::util::refined_tcp_stream::Stream as RefinedStream; use std::error::Error; use std::io::{Read, Write}; use std::net::{Shutdown, SocketAddr}; use std::sync::{Arc, Mutex}; use zeroize::Zeroizing; pub(crate) struct OpenSslStream { inner: openssl::ssl::SslStream, } /// An OpenSSL stream which has been split into two mutually exclusive streams (e.g. for read / write) pub(crate) struct SplitOpenSslStream(Arc>); // These struct methods form the implict contract for swappable TLS implementations impl SplitOpenSslStream { pub(crate) fn peer_addr(&mut self) -> std::io::Result> { self.0.lock().unwrap().inner.get_mut().peer_addr() } pub(crate) fn shutdown(&mut self, how: Shutdown) -> std::io::Result<()> { self.0.lock().unwrap().inner.get_mut().shutdown(how) } } impl Clone for SplitOpenSslStream { fn clone(&self) -> Self { Self(self.0.clone()) } } impl Read for SplitOpenSslStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.lock().unwrap().read(buf) } } impl Write for SplitOpenSslStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.lock().unwrap().write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.0.lock().unwrap().flush() } } impl Read for OpenSslStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.inner.read(buf) } } impl Write for OpenSslStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.inner.write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.inner.flush() } } pub(crate) struct OpenSslContext(openssl::ssl::SslContext); impl OpenSslContext { pub fn from_pem( certificates: Vec, private_key: Zeroizing>, ) -> Result> { use openssl::pkey::PKey; use openssl::ssl::{self, SslVerifyMode}; use openssl::x509::X509; let mut ctx = openssl::ssl::SslContext::builder(ssl::SslMethod::tls())?; ctx.set_cipher_list("DEFAULT")?; let certificate_chain = X509::stack_from_pem(&certificates)?; if certificate_chain.is_empty() { return Err("Couldn't extract certificate chain from config.".into()); } // The leaf certificate must always be first in the PEM file ctx.set_certificate(&certificate_chain[0])?; for chain_cert in certificate_chain.into_iter().skip(1) { ctx.add_extra_chain_cert(chain_cert)?; } let key = PKey::private_key_from_pem(&private_key)?; ctx.set_private_key(&key)?; ctx.set_verify(SslVerifyMode::NONE); ctx.check_private_key()?; Ok(Self(ctx.build())) } pub fn accept( &self, stream: Connection, ) -> Result> { use openssl::ssl::Ssl; let session = Ssl::new(&self.0).expect("Failed to create new OpenSSL session"); let stream = session.accept(stream)?; Ok(OpenSslStream { inner: stream }) } } impl From for RefinedStream { fn from(stream: OpenSslStream) -> Self { RefinedStream::Https(SplitOpenSslStream(Arc::new(Mutex::new(stream)))) } } tiny_http-0.12.0/src/ssl/rustls.rs000064400000000000000000000070450072674642500152560ustar 00000000000000use crate::connection::Connection; use crate::util::refined_tcp_stream::Stream as RefinedStream; use std::error::Error; use std::io::{Read, Write}; use std::net::{Shutdown, SocketAddr}; use std::sync::{Arc, Mutex}; use zeroize::Zeroizing; /// A wrapper around an owned Rustls connection and corresponding stream. /// /// Uses an internal Mutex to permit disparate reader & writer threads to access the stream independently. pub(crate) struct RustlsStream( Arc>>, ); impl RustlsStream { pub(crate) fn peer_addr(&mut self) -> std::io::Result> { self.0 .lock() .expect("Failed to lock SSL stream mutex") .sock .peer_addr() } pub(crate) fn shutdown(&mut self, how: Shutdown) -> std::io::Result<()> { self.0 .lock() .expect("Failed to lock SSL stream mutex") .sock .shutdown(how) } } impl Clone for RustlsStream { fn clone(&self) -> Self { Self(self.0.clone()) } } impl Read for RustlsStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0 .lock() .expect("Failed to lock SSL stream mutex") .read(buf) } } impl Write for RustlsStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0 .lock() .expect("Failed to lock SSL stream mutex") .write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.0 .lock() .expect("Failed to lock SSL stream mutex") .flush() } } pub(crate) struct RustlsContext(Arc); impl RustlsContext { pub(crate) fn from_pem( certificates: Vec, private_key: Zeroizing>, ) -> Result> { let certificate_chain: Vec = rustls_pemfile::certs(&mut certificates.as_slice())? .into_iter() .map(|bytes| rustls::Certificate(bytes)) .collect(); if certificate_chain.is_empty() { return Err("Couldn't extract certificate chain from config.".into()); } let private_key = rustls::PrivateKey({ let pkcs8_keys = rustls_pemfile::pkcs8_private_keys( &mut private_key.clone().as_slice(), ) .expect("file contains invalid pkcs8 private key (encrypted keys are not supported)"); if let Some(pkcs8_key) = pkcs8_keys.first() { pkcs8_key.clone() } else { let rsa_keys = rustls_pemfile::rsa_private_keys(&mut private_key.as_slice()) .expect("file contains invalid rsa private key"); rsa_keys[0].clone() } }); let tls_conf = rustls::ServerConfig::builder() .with_safe_defaults() .with_no_client_auth() .with_single_cert(certificate_chain, private_key)?; Ok(Self(Arc::new(tls_conf))) } pub(crate) fn accept( &self, stream: Connection, ) -> Result> { let connection = rustls::ServerConnection::new(self.0.clone())?; Ok(RustlsStream(Arc::new(Mutex::new( rustls::StreamOwned::new(connection, stream), )))) } } impl From for RefinedStream { fn from(stream: RustlsStream) -> Self { Self::Https(stream) } } tiny_http-0.12.0/src/ssl.rs000064400000000000000000000017060072674642500137200ustar 00000000000000//! Modules providing SSL/TLS implementations. For backwards compatibility, OpenSSL is the default //! implementation, but Rustls is highly recommended as a pure Rust alternative. //! //! In order to simplify the swappable implementations these SSL/TLS modules adhere to an implicit //! trait contract and specific implementations are re-exported as [`SslContextImpl`] and [`SslStream`]. //! The concrete type of these aliases will depend on which module you enable in `Cargo.toml`. #[cfg(feature = "ssl-openssl")] pub(crate) mod openssl; #[cfg(feature = "ssl-openssl")] pub(crate) use self::openssl::OpenSslContext as SslContextImpl; #[cfg(feature = "ssl-openssl")] pub(crate) use self::openssl::SplitOpenSslStream as SslStream; #[cfg(feature = "ssl-rustls")] pub(crate) mod rustls; #[cfg(feature = "ssl-rustls")] pub(crate) use self::rustls::RustlsContext as SslContextImpl; #[cfg(feature = "ssl-rustls")] pub(crate) use self::rustls::RustlsStream as SslStream; tiny_http-0.12.0/src/test.rs000064400000000000000000000073240072674642500141000ustar 00000000000000use crate::{request::new_request, HTTPVersion, Header, HeaderField, Method, Request}; use ascii::AsciiString; use std::net::SocketAddr; use std::str::FromStr; /// A simpler version of [`Request`] that is useful for testing. No data actually goes anywhere. /// /// By default, `TestRequest` pretends to be an insecure GET request for the server root (`/`) /// with no headers. To create a `TestRequest` with different parameters, use the builder pattern: /// /// ``` /// # use tiny_http::{Method, TestRequest}; /// let request = TestRequest::new() /// .with_method(Method::Post) /// .with_path("/api/widgets") /// .with_body("42"); /// ``` /// /// Then, convert the `TestRequest` into a real `Request` and pass it to the server under test: /// /// ``` /// # use tiny_http::{Method, Request, Response, Server, StatusCode, TestRequest}; /// # use std::io::Cursor; /// # let request = TestRequest::new() /// # .with_method(Method::Post) /// # .with_path("/api/widgets") /// # .with_body("42"); /// # struct TestServer { /// # listener: Server, /// # } /// # let server = TestServer { /// # listener: Server::http("0.0.0.0:0").unwrap(), /// # }; /// # impl TestServer { /// # fn handle_request(&self, request: Request) -> Response>> { /// # Response::from_string("test") /// # } /// # } /// let response = server.handle_request(request.into()); /// assert_eq!(response.status_code(), StatusCode(200)); /// ``` pub struct TestRequest { body: &'static str, remote_addr: SocketAddr, // true if HTTPS, false if HTTP secure: bool, method: Method, path: String, http_version: HTTPVersion, headers: Vec
, } impl From for Request { fn from(mut mock: TestRequest) -> Request { // if the user didn't set the Content-Length header, then set it for them // otherwise, leave it alone (it may be under test) if !mock .headers .iter_mut() .any(|h| h.field.equiv("Content-Length")) { mock.headers.push(Header { field: HeaderField::from_str("Content-Length").unwrap(), value: AsciiString::from_ascii(mock.body.len().to_string()).unwrap(), }); } new_request( mock.secure, mock.method, mock.path, mock.http_version, mock.headers, Some(mock.remote_addr), mock.body.as_bytes(), std::io::sink(), ) .unwrap() } } impl Default for TestRequest { fn default() -> Self { TestRequest { body: "", remote_addr: "127.0.0.1:23456".parse().unwrap(), secure: false, method: Method::Get, path: "/".to_string(), http_version: HTTPVersion::from((1, 1)), headers: Vec::new(), } } } impl TestRequest { pub fn new() -> Self { TestRequest::default() } pub fn with_body(mut self, body: &'static str) -> Self { self.body = body; self } pub fn with_remote_addr(mut self, remote_addr: SocketAddr) -> Self { self.remote_addr = remote_addr; self } pub fn with_https(mut self) -> Self { self.secure = true; self } pub fn with_method(mut self, method: Method) -> Self { self.method = method; self } pub fn with_path(mut self, path: &str) -> Self { self.path = path.to_string(); self } pub fn with_http_version(mut self, version: HTTPVersion) -> Self { self.http_version = version; self } pub fn with_header(mut self, header: Header) -> Self { self.headers.push(header); self } } tiny_http-0.12.0/src/util/custom_stream.rs000064400000000000000000000012700072674642500167550ustar 00000000000000use std::io::Result as IoResult; use std::io::{Read, Write}; pub struct CustomStream { reader: R, writer: W, } impl CustomStream where R: Read, W: Write, { pub fn new(reader: R, writer: W) -> CustomStream { CustomStream { reader, writer } } } impl Read for CustomStream where R: Read, { fn read(&mut self, buf: &mut [u8]) -> IoResult { self.reader.read(buf) } } impl Write for CustomStream where W: Write, { fn write(&mut self, buf: &[u8]) -> IoResult { self.writer.write(buf) } fn flush(&mut self) -> IoResult<()> { self.writer.flush() } } tiny_http-0.12.0/src/util/equal_reader.rs000064400000000000000000000060160072674642500165240ustar 00000000000000use std::io::Read; use std::io::Result as IoResult; use std::sync::mpsc::channel; use std::sync::mpsc::{Receiver, Sender}; /// A `Reader` that reads exactly the number of bytes from a sub-reader. /// /// If the limit is reached, it returns EOF. If the limit is not reached /// when the destructor is called, the remaining bytes will be read and /// thrown away. pub struct EqualReader where R: Read, { reader: R, size: usize, last_read_signal: Sender>, } impl EqualReader where R: Read, { pub fn new(reader: R, size: usize) -> (EqualReader, Receiver>) { let (tx, rx) = channel(); let r = EqualReader { reader, size, last_read_signal: tx, }; (r, rx) } } impl Read for EqualReader where R: Read, { fn read(&mut self, buf: &mut [u8]) -> IoResult { if self.size == 0 { return Ok(0); } let buf = if buf.len() < self.size { buf } else { &mut buf[..self.size] }; match self.reader.read(buf) { Ok(len) => { self.size -= len; Ok(len) } err @ Err(_) => err, } } } impl Drop for EqualReader where R: Read, { fn drop(&mut self) { let mut remaining_to_read = self.size; while remaining_to_read > 0 { let mut buf = vec![0; remaining_to_read]; match self.reader.read(&mut buf) { Err(e) => { self.last_read_signal.send(Err(e)).ok(); break; } Ok(0) => { self.last_read_signal.send(Ok(())).ok(); break; } Ok(other) => { remaining_to_read -= other; } } } } } #[cfg(test)] mod tests { use super::EqualReader; use std::io::Read; #[test] fn test_limit() { use std::io::Cursor; let mut org_reader = Cursor::new("hello world".to_string().into_bytes()); { let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5); let mut string = String::new(); equal_reader.read_to_string(&mut string).unwrap(); assert_eq!(string, "hello"); } let mut string = String::new(); org_reader.read_to_string(&mut string).unwrap(); assert_eq!(string, " world"); } #[test] fn test_not_enough() { use std::io::Cursor; let mut org_reader = Cursor::new("hello world".to_string().into_bytes()); { let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5); let mut vec = [0]; equal_reader.read_exact(&mut vec).unwrap(); assert_eq!(vec[0], b'h'); } let mut string = String::new(); org_reader.read_to_string(&mut string).unwrap(); assert_eq!(string, " world"); } } tiny_http-0.12.0/src/util/fused_reader.rs000064400000000000000000000023460072674642500165250ustar 00000000000000use std::io::{IoSliceMut, Read, Result as IoResult}; /// Wraps another reader and provides "fused" behavior. /// When the underlying reader reaches EOF, it is dropped /// and the fused reader becomes an empty stub. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FusedReader { inner: Option, } impl FusedReader { pub fn new(inner: R) -> Self { Self { inner: Some(inner) } } #[allow(dead_code)] pub fn into_inner(self) -> Option { self.inner } } impl Read for FusedReader { fn read(&mut self, buf: &mut [u8]) -> IoResult { match &mut self.inner { Some(r) => { let l = r.read(buf)?; if l == 0 { self.inner = None; } Ok(l) } None => Ok(0), } } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> IoResult { match &mut self.inner { Some(r) => { let l = r.read_vectored(bufs)?; if l == 0 { self.inner = None; } Ok(l) } None => Ok(0), } } } tiny_http-0.12.0/src/util/messages_queue.rs000064400000000000000000000053630072674642500171120ustar 00000000000000use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; enum Control { Elem(T), Unblock, } pub struct MessagesQueue where T: Send, { queue: Mutex>>, condvar: Condvar, } impl MessagesQueue where T: Send, { pub fn with_capacity(capacity: usize) -> Arc> { Arc::new(MessagesQueue { queue: Mutex::new(VecDeque::with_capacity(capacity)), condvar: Condvar::new(), }) } /// Pushes an element to the queue. pub fn push(&self, value: T) { let mut queue = self.queue.lock().unwrap(); queue.push_back(Control::Elem(value)); self.condvar.notify_one(); } /// Unblock one thread stuck in pop loop. pub fn unblock(&self) { let mut queue = self.queue.lock().unwrap(); queue.push_back(Control::Unblock); self.condvar.notify_one(); } /// Pops an element. Blocks until one is available. /// Returns None in case unblock() was issued. pub fn pop(&self) -> Option { let mut queue = self.queue.lock().unwrap(); loop { match queue.pop_front() { Some(Control::Elem(value)) => return Some(value), Some(Control::Unblock) => return None, None => (), } queue = self.condvar.wait(queue).unwrap(); } } /// Tries to pop an element without blocking. pub fn try_pop(&self) -> Option { let mut queue = self.queue.lock().unwrap(); match queue.pop_front() { Some(Control::Elem(value)) => Some(value), Some(Control::Unblock) | None => None, } } /// Tries to pop an element without blocking /// more than the specified timeout duration /// or unblock() was issued pub fn pop_timeout(&self, timeout: Duration) -> Option { let mut queue = self.queue.lock().unwrap(); let mut duration = timeout; loop { match queue.pop_front() { Some(Control::Elem(value)) => return Some(value), Some(Control::Unblock) => return None, None => (), } let now = Instant::now(); let (_queue, result) = self.condvar.wait_timeout(queue, timeout).unwrap(); queue = _queue; let sleep_time = now.elapsed(); duration = if duration > sleep_time { duration - sleep_time } else { Duration::from_millis(0) }; if result.timed_out() || (duration.as_secs() == 0 && duration.subsec_nanos() < 1_000_000) { return None; } } } } tiny_http-0.12.0/src/util/mod.rs000064400000000000000000000035160072674642500146540ustar 00000000000000pub use self::custom_stream::CustomStream; pub use self::equal_reader::EqualReader; pub use self::fused_reader::FusedReader; pub use self::messages_queue::MessagesQueue; pub use self::refined_tcp_stream::RefinedTcpStream; pub use self::sequential::{SequentialReader, SequentialReaderBuilder}; pub use self::sequential::{SequentialWriter, SequentialWriterBuilder}; pub use self::task_pool::TaskPool; use std::str::FromStr; mod custom_stream; mod equal_reader; mod fused_reader; mod messages_queue; pub(crate) mod refined_tcp_stream; mod sequential; mod task_pool; /// Parses a the value of a header. /// Suitable for `Accept-*`, `TE`, etc. /// /// For example with `text/plain, image/png; q=1.5` this function would /// return `[ ("text/plain", 1.0), ("image/png", 1.5) ]` pub fn parse_header_value(input: &str) -> Vec<(&str, f32)> { input .split(',') .filter_map(|elem| { let mut params = elem.split(';'); let t = params.next()?; let mut value = 1.0_f32; for p in params { if p.trim_start().starts_with("q=") { if let Ok(val) = f32::from_str(p.trim_start()[2..].trim()) { value = val; break; } } } Some((t.trim(), value)) }) .collect() } #[cfg(test)] mod test { #[test] #[allow(clippy::float_cmp)] fn test_parse_header() { let result = super::parse_header_value("text/html, text/plain; q=1.5 , image/png ; q=2.0"); assert_eq!(result.len(), 3); assert_eq!(result[0].0, "text/html"); assert_eq!(result[0].1, 1.0); assert_eq!(result[1].0, "text/plain"); assert_eq!(result[1].1, 1.5); assert_eq!(result[2].0, "image/png"); assert_eq!(result[2].1, 2.0); } } tiny_http-0.12.0/src/util/refined_tcp_stream.rs000064400000000000000000000077710072674642500177410ustar 00000000000000use std::io::Result as IoResult; use std::io::{Read, Write}; use std::net::{Shutdown, SocketAddr}; use crate::connection::Connection; #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] use crate::ssl::SslStream; pub(crate) enum Stream { Http(Connection), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Https(SslStream), } impl Clone for Stream { fn clone(&self) -> Self { match self { Stream::Http(tcp_stream) => Stream::Http(tcp_stream.try_clone().unwrap()), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => Stream::Https(ssl_stream.clone()), } } } impl From for Stream { fn from(tcp_stream: Connection) -> Self { Stream::Http(tcp_stream) } } impl Stream { fn secure(&self) -> bool { match self { Stream::Http(_) => false, #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(_) => true, } } fn peer_addr(&mut self) -> IoResult> { match self { Stream::Http(tcp_stream) => tcp_stream.peer_addr(), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => ssl_stream.peer_addr(), } } fn shutdown(&mut self, how: Shutdown) -> IoResult<()> { match self { Stream::Http(tcp_stream) => tcp_stream.shutdown(how), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => ssl_stream.shutdown(how), } } } impl Read for Stream { fn read(&mut self, buf: &mut [u8]) -> IoResult { match self { Stream::Http(tcp_stream) => tcp_stream.read(buf), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => ssl_stream.read(buf), } } } impl Write for Stream { fn write(&mut self, buf: &[u8]) -> IoResult { match self { Stream::Http(tcp_stream) => tcp_stream.write(buf), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => ssl_stream.write(buf), } } fn flush(&mut self) -> IoResult<()> { match self { Stream::Http(tcp_stream) => tcp_stream.flush(), #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))] Stream::Https(ssl_stream) => ssl_stream.flush(), } } } pub struct RefinedTcpStream { stream: Stream, close_read: bool, close_write: bool, } impl RefinedTcpStream { pub(crate) fn new(stream: S) -> (RefinedTcpStream, RefinedTcpStream) where S: Into, { let stream: Stream = stream.into(); let (read, write) = (stream.clone(), stream); let read = RefinedTcpStream { stream: read, close_read: true, close_write: false, }; let write = RefinedTcpStream { stream: write, close_read: false, close_write: true, }; (read, write) } /// Returns true if this struct wraps around a secure connection. #[inline] pub(crate) fn secure(&self) -> bool { self.stream.secure() } pub(crate) fn peer_addr(&mut self) -> IoResult> { self.stream.peer_addr() } } impl Drop for RefinedTcpStream { fn drop(&mut self) { if self.close_read { self.stream.shutdown(Shutdown::Read).ok(); } if self.close_write { self.stream.shutdown(Shutdown::Write).ok(); } } } impl Read for RefinedTcpStream { fn read(&mut self, buf: &mut [u8]) -> IoResult { self.stream.read(buf) } } impl Write for RefinedTcpStream { fn write(&mut self, buf: &[u8]) -> IoResult { self.stream.write(buf) } fn flush(&mut self) -> IoResult<()> { self.stream.flush() } } tiny_http-0.12.0/src/util/sequential.rs000064400000000000000000000102540072674642500162440ustar 00000000000000use std::io::Result as IoResult; use std::io::{Read, Write}; use std::sync::mpsc::channel; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::mem; pub struct SequentialReaderBuilder where R: Read + Send, { inner: SequentialReaderBuilderInner, } enum SequentialReaderBuilderInner where R: Read + Send, { First(R), NotFirst(Receiver), } pub struct SequentialReader where R: Read + Send, { inner: SequentialReaderInner, next: Sender, } enum SequentialReaderInner where R: Read + Send, { MyTurn(R), Waiting(Receiver), Empty, } pub struct SequentialWriterBuilder where W: Write + Send, { writer: Arc>, next_trigger: Option>, } pub struct SequentialWriter where W: Write + Send, { trigger: Option>, writer: Arc>, on_finish: Sender<()>, } impl SequentialReaderBuilder { pub fn new(reader: R) -> SequentialReaderBuilder { SequentialReaderBuilder { inner: SequentialReaderBuilderInner::First(reader), } } } impl SequentialWriterBuilder { pub fn new(writer: W) -> SequentialWriterBuilder { SequentialWriterBuilder { writer: Arc::new(Mutex::new(writer)), next_trigger: None, } } } impl Iterator for SequentialReaderBuilder { type Item = SequentialReader; fn next(&mut self) -> Option> { let (tx, rx) = channel(); let inner = mem::replace(&mut self.inner, SequentialReaderBuilderInner::NotFirst(rx)); match inner { SequentialReaderBuilderInner::First(reader) => Some(SequentialReader { inner: SequentialReaderInner::MyTurn(reader), next: tx, }), SequentialReaderBuilderInner::NotFirst(previous) => Some(SequentialReader { inner: SequentialReaderInner::Waiting(previous), next: tx, }), } } } impl Iterator for SequentialWriterBuilder { type Item = SequentialWriter; fn next(&mut self) -> Option> { let (tx, rx) = channel(); let mut next_next_trigger = Some(rx); ::std::mem::swap(&mut next_next_trigger, &mut self.next_trigger); Some(SequentialWriter { trigger: next_next_trigger, writer: self.writer.clone(), on_finish: tx, }) } } impl Read for SequentialReader { fn read(&mut self, buf: &mut [u8]) -> IoResult { let mut reader = match self.inner { SequentialReaderInner::MyTurn(ref mut reader) => return reader.read(buf), SequentialReaderInner::Waiting(ref mut recv) => recv.recv().unwrap(), SequentialReaderInner::Empty => unreachable!(), }; let result = reader.read(buf); self.inner = SequentialReaderInner::MyTurn(reader); result } } impl Write for SequentialWriter { fn write(&mut self, buf: &[u8]) -> IoResult { if let Some(v) = self.trigger.as_mut() { v.recv().unwrap() } self.trigger = None; self.writer.lock().unwrap().write(buf) } fn flush(&mut self) -> IoResult<()> { if let Some(v) = self.trigger.as_mut() { v.recv().unwrap() } self.trigger = None; self.writer.lock().unwrap().flush() } } impl Drop for SequentialReader where R: Read + Send, { fn drop(&mut self) { let inner = mem::replace(&mut self.inner, SequentialReaderInner::Empty); match inner { SequentialReaderInner::MyTurn(reader) => { self.next.send(reader).ok(); } SequentialReaderInner::Waiting(recv) => { let reader = recv.recv().unwrap(); self.next.send(reader).ok(); } SequentialReaderInner::Empty => (), } } } impl Drop for SequentialWriter where W: Write + Send, { fn drop(&mut self) { self.on_finish.send(()).ok(); } } tiny_http-0.12.0/src/util/task_pool.rs000064400000000000000000000075650072674642500161000ustar 00000000000000use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::thread; use std::time::Duration; /// Manages a collection of threads. /// /// A new thread is created every time all the existing threads are full. /// Any idle thread will automatically die after a few seconds. pub struct TaskPool { sharing: Arc, } struct Sharing { // list of the tasks to be done by worker threads todo: Mutex>>, // condvar that will be notified whenever a task is added to `todo` condvar: Condvar, // number of total worker threads running active_tasks: AtomicUsize, // number of idle worker threads waiting_tasks: AtomicUsize, } /// Minimum number of active threads. static MIN_THREADS: usize = 4; struct Registration<'a> { nb: &'a AtomicUsize, } impl<'a> Registration<'a> { fn new(nb: &'a AtomicUsize) -> Registration<'a> { nb.fetch_add(1, Ordering::Release); Registration { nb } } } impl<'a> Drop for Registration<'a> { fn drop(&mut self) { self.nb.fetch_sub(1, Ordering::Release); } } impl TaskPool { pub fn new() -> TaskPool { let pool = TaskPool { sharing: Arc::new(Sharing { todo: Mutex::new(VecDeque::new()), condvar: Condvar::new(), active_tasks: AtomicUsize::new(0), waiting_tasks: AtomicUsize::new(0), }), }; for _ in 0..MIN_THREADS { pool.add_thread(None) } pool } /// Executes a function in a thread. /// If no thread is available, spawns a new one. pub fn spawn(&self, code: Box) { let mut queue = self.sharing.todo.lock().unwrap(); if self.sharing.waiting_tasks.load(Ordering::Acquire) == 0 { self.add_thread(Some(code)); } else { queue.push_back(code); self.sharing.condvar.notify_one(); } } fn add_thread(&self, initial_fn: Option>) { let sharing = self.sharing.clone(); thread::spawn(move || { let sharing = sharing; let _active_guard = Registration::new(&sharing.active_tasks); if let Some(mut f) = initial_fn { f(); } loop { let mut task: Box = { let mut todo = sharing.todo.lock().unwrap(); let task; loop { if let Some(poped_task) = todo.pop_front() { task = poped_task; break; } let _waiting_guard = Registration::new(&sharing.waiting_tasks); let received = if sharing.active_tasks.load(Ordering::Acquire) <= MIN_THREADS { todo = sharing.condvar.wait(todo).unwrap(); true } else { let (new_lock, waitres) = sharing .condvar .wait_timeout(todo, Duration::from_millis(5000)) .unwrap(); todo = new_lock; !waitres.timed_out() }; if !received && todo.is_empty() { return; } } task }; task(); } }); } } impl Drop for TaskPool { fn drop(&mut self) { self.sharing .active_tasks .store(999_999_999, Ordering::Release); self.sharing.condvar.notify_all(); } } tiny_http-0.12.0/tests/input-tests.rs000064400000000000000000000075050072674642500157740ustar 00000000000000extern crate tiny_http; use std::io::{Read, Write}; use std::net::Shutdown; use std::sync::mpsc; use std::thread; #[allow(dead_code)] mod support; #[test] fn basic_string_input() { let (server, client) = support::new_one_server_one_client(); { let mut client = client; (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/plain; charset=utf8\r\nContent-Length: 5\r\n\r\nhello")).unwrap(); } let mut request = server.recv().unwrap(); let mut output = String::new(); request.as_reader().read_to_string(&mut output).unwrap(); assert_eq!(output, "hello"); } #[test] fn wrong_content_length() { let (server, client) = support::new_one_server_one_client(); { let mut client = client; (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/plain; charset=utf8\r\nContent-Length: 3\r\n\r\nhello")).unwrap(); } let mut request = server.recv().unwrap(); let mut output = String::new(); request.as_reader().read_to_string(&mut output).unwrap(); assert_eq!(output, "hel"); } #[test] fn expect_100_continue() { let (server, client) = support::new_one_server_one_client(); let mut client = client; (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nExpect: 100-continue\r\nContent-Type: text/plain; charset=utf8\r\nContent-Length: 5\r\n\r\n")).unwrap(); client.flush().unwrap(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { let mut request = server.recv().unwrap(); let mut output = String::new(); request.as_reader().read_to_string(&mut output).unwrap(); assert_eq!(output, "hello"); tx.send(()).unwrap(); }); // client.set_keepalive(Some(3)).unwrap(); FIXME: reenable this let mut content = vec![0; 12]; client.read_exact(&mut content).unwrap(); assert!(&content[9..].starts_with(b"100")); // 100 status code (write!(client, "hello")).unwrap(); client.flush().unwrap(); client.shutdown(Shutdown::Write).unwrap(); rx.recv().unwrap(); } #[test] fn unsupported_expect_header() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nExpect: 189-dummy\r\nContent-Type: text/plain; charset=utf8\r\n\r\n")).unwrap(); // client.set_keepalive(Some(3)).unwrap(); FIXME: reenable this let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(&content[9..].starts_with("417")); // 417 status code } #[test] fn invalid_header_name() { let mut client = support::new_client_to_hello_world_server(); // note the space hidden in the Content-Length, which is invalid (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nContent-Type: text/plain; charset=utf8\r\nContent-Length : 5\r\n\r\nhello")).unwrap(); let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(&content[9..].starts_with("400 Bad Request")); // 400 status code } #[test] fn custom_content_type_response_header() { let (server, mut stream) = support::new_one_server_one_client(); write!( stream, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" ) .unwrap(); let request = server.recv().unwrap(); request .respond( tiny_http::Response::from_string("{\"custom\": \"Content-Type\"}").with_header( "Content-Type: application/json" .parse::() .unwrap(), ), ) .unwrap(); let mut content = String::new(); stream.read_to_string(&mut content).unwrap(); assert!(content.ends_with("{\"custom\": \"Content-Type\"}")); assert_ne!(content.find("Content-Type: application/json"), None); } tiny_http-0.12.0/tests/network.rs000064400000000000000000000152370072674642500151670ustar 00000000000000extern crate tiny_http; use std::io::{Read, Write}; use std::net::{Shutdown, TcpStream}; use std::thread; use std::time::Duration; #[allow(dead_code)] mod support; #[test] fn connection_close_header() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")).unwrap(); thread::sleep(Duration::from_millis(1000)); (write!(client, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n")).unwrap(); // if the connection was not closed, this will err with timeout // client.set_keepalive(Some(1)).unwrap(); FIXME: reenable this let mut out = Vec::new(); client.read_to_end(&mut out).unwrap(); } #[test] fn http_1_0_connection_close() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n")).unwrap(); // if the connection was not closed, this will err with timeout // client.set_keepalive(Some(1)).unwrap(); FIXME: reenable this let mut out = Vec::new(); client.read_to_end(&mut out).unwrap(); } #[test] fn detect_connection_closed() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")).unwrap(); thread::sleep(Duration::from_millis(1000)); client.shutdown(Shutdown::Write).unwrap(); // if the connection was not closed, this will err with timeout // client.set_keepalive(Some(1)).unwrap(); FIXME: reenable this let mut out = Vec::new(); client.read_to_end(&mut out).unwrap(); } #[test] fn poor_network_test() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "G")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "ET /he")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "llo HT")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "TP/1.")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "1\r\nHo")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "st: localho")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "st\r\nConnec")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "tion: close\r")).unwrap(); thread::sleep(Duration::from_millis(100)); (write!(client, "\n\r")).unwrap(); thread::sleep(Duration::from_millis(100)); (writeln!(client)).unwrap(); // client.set_keepalive(Some(2)).unwrap(); FIXME: reenable this let mut data = String::new(); client.read_to_string(&mut data).unwrap(); assert!(data.ends_with("hello world")); } #[test] fn pipelining_test() { let mut client = support::new_client_to_hello_world_server(); (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")).unwrap(); (write!(client, "GET /hello HTTP/1.1\r\nHost: localhost\r\n\r\n")).unwrap(); (write!( client, "GET /world HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" )) .unwrap(); // client.set_keepalive(Some(2)).unwrap(); FIXME: reenable this let mut data = String::new(); client.read_to_string(&mut data).unwrap(); assert_eq!(data.split("hello world").count(), 4); } #[test] fn server_crash_results_in_response() { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); let mut client = TcpStream::connect(("127.0.0.1", port)).unwrap(); thread::spawn(move || { server.recv().unwrap(); // oops, server crash }); (write!( client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" )) .unwrap(); // client.set_keepalive(Some(2)).unwrap(); FIXME: reenable this let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(&content[9..].starts_with('5')); // 5xx status code } #[test] fn responses_reordered() { let (server, mut client) = support::new_one_server_one_client(); (write!(client, "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")).unwrap(); (write!( client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" )) .unwrap(); thread::spawn(move || { let rq1 = server.recv().unwrap(); let rq2 = server.recv().unwrap(); thread::spawn(move || { rq2.respond(tiny_http::Response::from_string( "second request".to_owned(), )) .unwrap(); }); thread::sleep(Duration::from_millis(100)); thread::spawn(move || { rq1.respond(tiny_http::Response::from_string("first request".to_owned())) .unwrap(); }); }); // client.set_keepalive(Some(2)).unwrap(); FIXME: reenable this let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(content.ends_with("second request")); } #[test] fn no_transfer_encoding_on_204() { let (server, mut client) = support::new_one_server_one_client(); (write!( client, "GET / HTTP/1.1\r\nHost: localhost\r\nTE: chunked\r\nConnection: close\r\n\r\n" )) .unwrap(); thread::spawn(move || { let rq = server.recv().unwrap(); let resp = tiny_http::Response::empty(tiny_http::StatusCode(204)); rq.respond(resp).unwrap(); }); let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(content.starts_with("HTTP/1.1 204")); assert!(!content.contains("Transfer-Encoding: chunked")); } /* FIXME: uncomment and fix #[test] fn connection_timeout() { let (server, mut client) = { let server = tiny_http::ServerBuilder::new() .with_client_connections_timeout(3000) .with_random_port().build().unwrap(); let port = server.server_addr().port(); let client = TcpStream::connect(("127.0.0.1", port)).unwrap(); (server, client) }; let (tx_stop, rx_stop) = mpsc::channel(); // executing server in parallel thread::spawn(move || { loop { server.try_recv(); thread::sleep(Duration::from_millis(100)); if rx_stop.try_recv().is_ok() { break } } }); // waiting for the 408 response let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(&content[9..].starts_with("408")); // stopping server tx_stop.send(()); } */ #[test] fn chunked_threshold() { let resp = tiny_http::Response::from_string("test".to_string()); assert_eq!(resp.chunked_threshold(), 32768); assert_eq!(resp.with_chunked_threshold(42).chunked_threshold(), 42); } tiny_http-0.12.0/tests/non-chunked-buffering.rs000064400000000000000000000050660072674642500176530ustar 00000000000000extern crate tiny_http; use std::io::{Cursor, Read, Write}; use std::sync::{ atomic::{ AtomicUsize, Ordering::{AcqRel, Acquire}, }, Arc, }; #[allow(dead_code)] mod support; struct MeteredReader { inner: T, position: Arc, } impl Read for MeteredReader where T: Read, { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { match self.inner.read(buf) { Ok(read) => { self.position.fetch_add(read, AcqRel); Ok(read) } e => e, } } } type Reader = MeteredReader>; fn big_response_reader() -> Reader { let big_body = "ABCDEFGHIJKLMNOPQRSTUVXYZ".repeat(1024 * 1024 * 16); MeteredReader { inner: Cursor::new(big_body), position: Arc::new(AtomicUsize::new(0)), } } fn identity_served(r: &mut Reader) -> tiny_http::Response<&mut Reader> { let body_len = r.inner.get_ref().len(); tiny_http::Response::empty(200) .with_chunked_threshold(std::usize::MAX) .with_data(r, Some(body_len)) } /// Checks that a body-Read:er is not called when the client has disconnected #[test] fn responding_to_closed_client() { let (server, mut stream) = support::new_one_server_one_client(); write!( stream, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" ) .unwrap(); let request = server.recv().unwrap(); // Client already disconnected drop(stream); let mut reader = big_response_reader(); request .respond(identity_served(&mut reader)) .expect("Successful"); assert!(reader.position.load(Acquire) < 1024 * 1024); } /// Checks that a slow client does not cause data to be consumed and buffered from a reader #[test] fn responding_to_non_consuming_client() { let (server, mut stream) = support::new_one_server_one_client(); write!( stream, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" ) .unwrap(); let request = server.recv().unwrap(); let mut reader = big_response_reader(); let position = reader.position.clone(); // Client still connected, but not reading anything std::thread::spawn(move || { request .respond(identity_served(&mut reader)) .expect("Successful"); }); std::thread::sleep(std::time::Duration::from_millis(100)); // It seems the client TCP socket can buffer quite a lot, so we need to be permissive assert!(position.load(Acquire) < 8 * 1024 * 1024); drop(stream); } tiny_http-0.12.0/tests/promptness.rs000064400000000000000000000157050072674642500157100ustar 00000000000000extern crate tiny_http; use std::io::{copy, Read, Write}; use std::net::{Shutdown, TcpStream}; use std::ops::Deref; use std::sync::mpsc::channel; use std::sync::Arc; use std::thread::{sleep, spawn}; use std::time::Duration; use tiny_http::{Response, Server}; /// Stream that produces bytes very slowly #[derive(Debug, Copy, Clone, PartialEq, Eq)] struct SlowByteSrc { val: u8, len: usize, } impl<'b> Read for SlowByteSrc { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { sleep(Duration::from_millis(100)); let l = self.len.min(buf.len()).min(1000); for v in buf[..l].iter_mut() { *v = self.val; } self.len -= l; Ok(l) } } /// crude impl of http `Transfer-Encoding: chunked` fn encode_chunked(data: &mut dyn Read, output: &mut dyn Write) { let mut buf = [0u8; 4096]; loop { let l = data.read(&mut buf).unwrap(); write!(output, "{:X}\r\n", l).unwrap(); output.write_all(&buf[..l]).unwrap(); write!(output, "\r\n").unwrap(); if l == 0 { break; } } } mod prompt_pipelining { use super::*; /// Check that pipelined requests on the same connection are received promptly. fn assert_requests_parsed_promptly( req_cnt: usize, req_body: &'static [u8], timeout: Duration, req_writer: impl FnOnce(&mut dyn Write) + Send + 'static, ) { let resp_body = SlowByteSrc { val: 42, len: 1000_000, }; // very slow response body let server = Server::http("0.0.0.0:0").unwrap(); let mut client = TcpStream::connect(server.server_addr().to_ip().unwrap()).unwrap(); let (svr_send, svr_rcv) = channel(); spawn(move || { for _ in 0..req_cnt { let mut req = server.recv().unwrap(); // read the whole body of the request let mut body = Vec::new(); req.as_reader().read_to_end(&mut body).unwrap(); assert_eq!(req_body, body.as_slice()); // The next pipelined request should now be available for parsing, // while we send the (possibly slow) response in another thread spawn(move || { req.respond(Response::empty(200).with_data(resp_body, Some(resp_body.len))) }); } svr_send.send(()).unwrap(); }); spawn(move || req_writer(&mut client)); // requests must be sent and received quickly (before timeout expires) svr_rcv .recv_timeout(timeout) .expect("Server did not finish reading pipelined requests quickly enough"); } #[test] fn empty() { assert_requests_parsed_promptly(5, &[], Duration::from_millis(200), move |wr| { for _ in 0..5 { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Connection: keep-alive\r\n\r\n").unwrap(); } }); } #[test] fn content_length_short() { let body = &[65u8; 100]; // short but not trivial assert_requests_parsed_promptly(5, body, Duration::from_millis(200), move |wr| { for _ in 0..5 { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Connection: keep-alive\r\n").unwrap(); write!(wr, "Content-Length: {}\r\n\r\n", body.len()).unwrap(); wr.write_all(body).unwrap(); } }); } #[test] fn content_length_long() { let body = &[65u8; 10000]; // long enough that it won't be buffered assert_requests_parsed_promptly(5, body, Duration::from_millis(200), move |wr| { for _ in 0..5 { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Connection: keep-alive\r\n").unwrap(); write!(wr, "Content-Length: {}\r\n\r\n", body.len()).unwrap(); wr.write_all(body).unwrap(); } }); } #[test] fn chunked() { let body = &[65u8; 10000]; assert_requests_parsed_promptly(5, body, Duration::from_millis(200), move |wr| { for _ in 0..5 { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Connection: keep-alive\r\n").unwrap(); write!(wr, "Transfer-Encoding: chunked\r\n\r\n").unwrap(); encode_chunked(&mut &body[..], wr); } }); } } mod prompt_responses { use super::*; /// Check that response is sent promptly without waiting for full request body. fn assert_responds_promptly( timeout: Duration, req_writer: impl FnOnce(&mut dyn Write) + Send + 'static, ) { let server = Server::http("0.0.0.0:0").unwrap(); let client = TcpStream::connect(server.server_addr().to_ip().unwrap()).unwrap(); spawn(move || loop { // server attempts to respond immediately let req = server.recv().unwrap(); req.respond(Response::empty(400)).unwrap(); }); let client = Arc::new(client); let client_write = Arc::clone(&client); // request written (possibly very slowly) in another thread spawn(move || req_writer(&mut client_write.deref())); // response should arrive quickly (before timeout expires) client.set_read_timeout(Some(timeout)).unwrap(); let resp = client.deref().read(&mut [0u8; 4096]); client.shutdown(Shutdown::Both).unwrap(); assert!(resp.is_ok(), "Server response was not sent promptly"); } static SLOW_BODY: SlowByteSrc = SlowByteSrc { val: 65, len: 1000_000, }; #[test] fn content_length_http11() { assert_responds_promptly(Duration::from_millis(200), move |wr| { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Content-Length: {}\r\n\r\n", SLOW_BODY.len).unwrap(); copy(&mut SLOW_BODY.clone(), wr).unwrap(); }); } #[test] fn content_length_http10() { assert_responds_promptly(Duration::from_millis(200), move |wr| { write!(wr, "GET / HTTP/1.0\r\n").unwrap(); write!(wr, "Content-Length: {}\r\n\r\n", SLOW_BODY.len).unwrap(); copy(&mut SLOW_BODY.clone(), wr).unwrap(); }); } #[test] fn expect_continue() { assert_responds_promptly(Duration::from_millis(200), move |wr| { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Expect: 100 continue\r\n").unwrap(); write!(wr, "Content-Length: {}\r\n\r\n", SLOW_BODY.len).unwrap(); copy(&mut SLOW_BODY.clone(), wr).unwrap(); }); } #[test] fn chunked() { assert_responds_promptly(Duration::from_millis(200), move |wr| { write!(wr, "GET / HTTP/1.1\r\n").unwrap(); write!(wr, "Transfer-Encoding: chunked\r\n\r\n").unwrap(); encode_chunked(&mut SLOW_BODY.clone(), wr); }); } } tiny_http-0.12.0/tests/simple-test.rs000064400000000000000000000013250072674642500157350ustar 00000000000000extern crate tiny_http; use std::io::{Read, Write}; #[allow(dead_code)] mod support; #[test] fn basic_handling() { let (server, mut stream) = support::new_one_server_one_client(); write!( stream, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" ) .unwrap(); let request = server.recv().unwrap(); assert!(*request.method() == tiny_http::Method::Get); //assert!(request.url() == "/"); request .respond(tiny_http::Response::from_string("hello world".to_owned())) .unwrap(); server.try_recv().unwrap(); let mut content = String::new(); stream.read_to_string(&mut content).unwrap(); assert!(content.ends_with("hello world")); } tiny_http-0.12.0/tests/support/mod.rs000064400000000000000000000023720072674642500157650ustar 00000000000000use std::net::TcpStream; use std::thread; use std::time::Duration; /// Creates a server and a client connected to the server. pub fn new_one_server_one_client() -> (tiny_http::Server, TcpStream) { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); let client = TcpStream::connect(("127.0.0.1", port)).unwrap(); (server, client) } /// Creates a "hello world" server with a client connected to the server. /// /// The server will automatically close after 3 seconds. pub fn new_client_to_hello_world_server() -> TcpStream { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let port = server.server_addr().to_ip().unwrap().port(); let client = TcpStream::connect(("127.0.0.1", port)).unwrap(); thread::spawn(move || { let mut cycles = 3 * 1000 / 20; loop { if let Some(rq) = server.try_recv().unwrap() { let response = tiny_http::Response::from_string("hello world".to_string()); rq.respond(response).unwrap(); } thread::sleep(Duration::from_millis(20)); cycles -= 1; if cycles == 0 { break; } } }); client } tiny_http-0.12.0/tests/unblock-test.rs000064400000000000000000000015140072674642500161010ustar 00000000000000extern crate tiny_http; use std::sync::Arc; use std::thread; #[test] fn unblock_server() { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let s = Arc::new(server); let s1 = s.clone(); thread::spawn(move || s1.unblock()); // Without unblock this would hang forever for _rq in s.incoming_requests() {} } #[test] fn unblock_threads() { let server = tiny_http::Server::http("0.0.0.0:0").unwrap(); let s = Arc::new(server); let s1 = s.clone(); let s2 = s.clone(); let h1 = thread::spawn(move || for _rq in s1.incoming_requests() {}); let h2 = thread::spawn(move || for _rq in s2.incoming_requests() {}); // Graceful shutdown; removing even one of the // unblock calls prevents termination s.unblock(); s.unblock(); h1.join().unwrap(); h2.join().unwrap(); } tiny_http-0.12.0/tests/unix-test.rs000064400000000000000000000020250072674642500154250ustar 00000000000000#![cfg(unix)] extern crate tiny_http; use std::{ io::{Read, Write}, os::unix::net::UnixStream, path::{Path, PathBuf}, }; #[allow(dead_code)] mod support; #[test] fn unix_basic_handling() { let server = tiny_http::Server::http_unix(Path::new("/tmp/tiny-http-test.sock")).unwrap(); let path: PathBuf = server .server_addr() .to_unix() .unwrap() .as_pathname() .unwrap() .into(); let mut client = UnixStream::connect(path).unwrap(); write!( client, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" ) .unwrap(); let request = server.recv().unwrap(); assert!(*request.method() == tiny_http::Method::Get); //assert!(request.url() == "/"); request .respond(tiny_http::Response::from_string("hello world".to_owned())) .unwrap(); server.try_recv().unwrap(); let mut content = String::new(); client.read_to_string(&mut content).unwrap(); assert!(content.ends_with("hello world")); }