├── .cargo └── config ├── .github ├── dependabot.yml └── pull_request_template.md ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY-POLICY.md ├── build.rs ├── coverage_config_aarch64.json ├── coverage_config_x86_64.json └── src ├── common ├── headers.rs └── mod.rs ├── connection.rs ├── lib.rs ├── request.rs ├── response.rs ├── router.rs ├── server.rs └── vmm └── src └── vmm_config └── mmds.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | # This workaround is needed because the linker is unable to find __addtf3, 2 | # __multf3 and __subtf3. 3 | # Related issue: https://github.com/rust-lang/compiler-builtins/issues/201 4 | [target.aarch64-unknown-linux-musl] 5 | rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc"] 6 | 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Reason for This PR 2 | 3 | `[Author TODO: add issue # or explain reasoning.]` 4 | 5 | ## Description of Changes 6 | 7 | `[Author TODO: add description of changes.]` 8 | 9 | ## License Acceptance 10 | 11 | By submitting this pull request, I confirm that my contribution is made under 12 | the terms of the Apache 2.0 license. 13 | 14 | ## PR Checklist 15 | 16 | `[Author TODO: Meet these criteria.]` 17 | `[Reviewer TODO: Verify that these criteria are met. Request changes if not]` 18 | 19 | - [ ] All commits in this PR are signed (`git commit -s`). 20 | - [ ] The reason for this PR is clearly provided (issue no. or explanation). 21 | - [ ] The description of changes is clear and encompassing. 22 | - [ ] Any required documentation changes (code and docs) are included in this PR. 23 | - [ ] Any newly added `unsafe` code is properly documented. 24 | - [ ] Any user-facing changes are mentioned in `CHANGELOG.md`. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-vmm-ci"] 2 | path = rust-vmm-ci 3 | url = https://github.com/rust-vmm/rust-vmm-ci.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | ## Added 4 | 5 | - Implemented `Eq` for `common::headers::Encoding`, `common::headers::MediaType`, 6 | `common::headers::Headers`, `common::HttpHeaderError`, `common::Body`, `common::Version`, 7 | `common::RequestError`, `request::Uri`, `request::RequestLine`, `response::StatusCode`, 8 | `response::ResponseHeaders` 9 | 10 | ## Changed 11 | 12 | - Mark `HttpServer::new_from_fd` as `unsafe` as the correctness of the unsafe code 13 | in this method relies on an invariant the caller has to uphold 14 | - Always set 'Content-Length' in non-100/204 responses regardless of whether the 15 | body is empty. Otherwise, the client waits for the server to close the 16 | connection or for timeout to occur. 17 | 18 | # v0.1.0 19 | 20 | - micro-http v0.1.0 first release. 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to micro-http 2 | 3 | ## Contribution Workflow 4 | 5 | The micro-http repository uses the “fork-and-pull” development model. Follow 6 | these steps if you want to merge your changes: 7 | 8 | 1. Within your fork of 9 | [micro-http](https://github.com/firecracker-microvm/micro-http), create a 10 | branch for your contribution. Use a meaningful name. 11 | 1. Create your contribution, meeting all 12 | [contribution quality standards](#contribution-quality-standards) 13 | 1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) 14 | against the master branch of the micro-http repository. 15 | 1. Work with your reviewers to address any comments and obtain a 16 | minimum of 2 approvals, at least one of which must be provided by 17 | [a maintainer](MAINTAINERS.md). 18 | To update your pull request amend existing commits whenever applicable and 19 | then push the new changes to your pull request branch. 20 | 1. Once the pull request is approved, one of the maintainers will merge it. 21 | 22 | ## Request for Comments 23 | 24 | If you just want to receive feedback for a contribution proposal, open an “RFC” 25 | (“Request for Comments”) pull request: 26 | 27 | 1. On your fork of 28 | [micro-http](https://github.com/firecracker-microvm/micro-http), create a 29 | branch for the contribution you want feedback on. Use a meaningful name. 30 | 1. Create your proposal based on the existing codebase. 31 | 1. [Create a draft pull request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) 32 | against the master branch of the micro-http repository. 33 | 1. Discuss your proposal with the community on the pull request page (or on any 34 | other channel). Add the conclusion(s) of this discussion to the pull request 35 | page. 36 | 37 | ## Contribution Quality Standards 38 | 39 | Most quality and style standards are enforced automatically during integration 40 | testing. Your contribution needs to meet the following standards: 41 | 42 | - Separate each **logical change** into its own commit. 43 | - Each commit must pass all unit & code style tests, and the full pull request 44 | must pass all integration tests. 45 | - Unit test coverage must _increase_ the overall project code coverage. 46 | - Document all your public functions. 47 | - Add a descriptive message for each commit. Follow 48 | [commit message best practices](https://github.com/erlang/otp/wiki/writing-good-commit-messages). 49 | - Document your pull requests. Include the reasoning behind each change. 50 | - Acknowledge micro-http's [Apache 2.0 license](LICENSE) and certify that no 51 | part of your contribution contravenes this license by signing off on all your 52 | commits with `git -s`. Ensure that every file in your pull request has a 53 | header referring to the repository license file. 54 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.2.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 10 | 11 | [[package]] 12 | name = "libc" 13 | version = "0.2.66" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 16 | 17 | [[package]] 18 | name = "micro_http" 19 | version = "0.1.0" 20 | dependencies = [ 21 | "libc", 22 | "vmm-sys-util", 23 | ] 24 | 25 | [[package]] 26 | name = "vmm-sys-util" 27 | version = "0.12.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" 30 | dependencies = [ 31 | "bitflags", 32 | "libc", 33 | ] 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "micro_http" 3 | version = "0.1.0" 4 | license = "Apache-2.0" 5 | authors = ["Amazon Firecracker team "] 6 | edition = "2021" 7 | 8 | [dependencies] 9 | libc = "0.2.66" 10 | vmm-sys-util = "0.14.0" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micro-http 2 | 3 | This is a minimal implementation of the 4 | [HTTP/1.0](https://tools.ietf.org/html/rfc1945) and 5 | [HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) protocols. This HTTP 6 | implementation is stateless thus it does not support chunking or compression. 7 | 8 | The micro-http implementation is used in production by Firecracker. 9 | 10 | As micro-http uses [`std::os::unix`](https://doc.rust-lang.org/std/os/unix/index.html) this crates only supports Unix-like targets. 11 | 12 | ## Contributing 13 | 14 | To contribute to micro-http, checkout the 15 | [contribution guidelines](CONTRIBUTING.md). 16 | 17 | ## Releases 18 | 19 | New micro-http versions are released via the GitHub repository releases page. A 20 | history of changes is recorded in our [changelog](CHANGELOG.md). 21 | 22 | ## Policy for Security Disclosures 23 | 24 | If you suspect you have uncovered a vulnerability, contact us privately, as 25 | outlined in our [security policy document](); we will immediately prioritize 26 | your disclosure. -------------------------------------------------------------------------------- /SECURITY-POLICY.md: -------------------------------------------------------------------------------- 1 | # Security Issue Policy 2 | 3 | If you uncover a security issue with micro-http, please write to us on 4 | . 5 | 6 | Once the Firecracker [maintainers](MAINTAINERS.md) become aware (or are made 7 | aware) of a security issue, they will immediately assess it. Based on impact 8 | and complexity, they will determine an embargo period (if externally reported, 9 | the period will be agreed upon with the external party). 10 | 11 | During the embargo period, maintainers will prioritize developing a fix over 12 | other activities. Within this period, maintainers may also notify a limited 13 | number of trusted parties via a pre-disclosure list, providing them with 14 | technical information, a risk assessment, and early access to a fix. 15 | 16 | The external customers are included in this group based on the scale of their 17 | micro-http usage in production. The pre-disclosure list may also contain 18 | significant external security contributors that can join the effort to fix the 19 | issue during the embargo period. 20 | 21 | At the end of the embargo period, maintainers will publicly release information 22 | about the security issue together with the micro-http patches that mitigate it. 23 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(not(target_family = "unix"))] 3 | std::compile_error!("This crate only supports Unix-like targets"); 4 | } 5 | -------------------------------------------------------------------------------- /coverage_config_aarch64.json: -------------------------------------------------------------------------------- 1 | {"coverage_score": 91.9, "exclude_path": "", "crate_features": ""} 2 | -------------------------------------------------------------------------------- /coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | {"coverage_score": 97.63, "exclude_path": "", "crate_features": ""} 2 | -------------------------------------------------------------------------------- /src/common/headers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::collections::HashMap; 5 | use std::result::Result; 6 | 7 | use crate::HttpHeaderError; 8 | use crate::RequestError; 9 | 10 | /// Wrapper over an HTTP Header type. 11 | #[derive(Debug, Eq, Hash, PartialEq)] 12 | pub enum Header { 13 | /// Header `Content-Length`. 14 | ContentLength, 15 | /// Header `Content-Type`. 16 | ContentType, 17 | /// Header `Expect`. 18 | Expect, 19 | /// Header `Transfer-Encoding`. 20 | TransferEncoding, 21 | /// Header `Server`. 22 | Server, 23 | /// Header `Accept` 24 | Accept, 25 | /// Header `Accept-Encoding` 26 | AcceptEncoding, 27 | } 28 | 29 | impl Header { 30 | /// Returns a byte slice representation of the object. 31 | pub fn raw(&self) -> &'static [u8] { 32 | match self { 33 | Self::ContentLength => b"Content-Length", 34 | Self::ContentType => b"Content-Type", 35 | Self::Expect => b"Expect", 36 | Self::TransferEncoding => b"Transfer-Encoding", 37 | Self::Server => b"Server", 38 | Self::Accept => b"Accept", 39 | Self::AcceptEncoding => b"Accept-Encoding", 40 | } 41 | } 42 | 43 | /// Parses a byte slice into a Header structure. Header must be ASCII, so also 44 | /// UTF-8 valid. 45 | /// 46 | /// # Errors 47 | /// `InvalidRequest` is returned if slice contains invalid utf8 characters. 48 | /// `InvalidHeader` is returned if unsupported header found. 49 | fn try_from(string: &[u8]) -> Result { 50 | if let Ok(mut utf8_string) = String::from_utf8(string.to_vec()) { 51 | utf8_string.make_ascii_lowercase(); 52 | match utf8_string.trim() { 53 | "content-length" => Ok(Self::ContentLength), 54 | "content-type" => Ok(Self::ContentType), 55 | "expect" => Ok(Self::Expect), 56 | "transfer-encoding" => Ok(Self::TransferEncoding), 57 | "server" => Ok(Self::Server), 58 | "accept" => Ok(Self::Accept), 59 | "accept-encoding" => Ok(Self::AcceptEncoding), 60 | invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName( 61 | invalid_key.to_string(), 62 | ))), 63 | } 64 | } else { 65 | Err(RequestError::InvalidRequest) 66 | } 67 | } 68 | } 69 | 70 | /// Wrapper over the list of headers associated with a Request that we need 71 | /// in order to parse the request correctly and be able to respond to it. 72 | /// 73 | /// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both 74 | /// in plain text actually and don't influence our parsing process. 75 | /// 76 | /// All the other possible header fields are not necessary in order to serve this connection 77 | /// and, thus, are not of interest to us. However, we still look for header fields that might 78 | /// invalidate our request as we don't support the full set of HTTP/1.1 specification. 79 | /// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression 80 | /// algorithm is applied to the body of the request, or "Expect: 103-checkpoint". 81 | #[derive(Debug, PartialEq, Eq)] 82 | pub struct Headers { 83 | /// The `Content-Length` header field tells us how many bytes we need to receive 84 | /// from the source after the headers. 85 | content_length: u32, 86 | /// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue". 87 | /// This means that, per HTTP/1.1 specifications, we must send a response with the status code 88 | /// 100 after we have received the headers in order to receive the body of the request. This 89 | /// field should be known immediately after parsing the headers. 90 | expect: bool, 91 | /// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1 92 | /// server must support it. It is useful only when receiving the body of the request and should 93 | /// be known immediately after parsing the headers. 94 | chunked: bool, 95 | /// `Accept` header might be used by HTTP clients to enforce server responses with content 96 | /// formatted in a specific way. 97 | accept: MediaType, 98 | /// Hashmap reserved for storing custom headers. 99 | custom_entries: HashMap, 100 | } 101 | 102 | impl Default for Headers { 103 | /// By default Requests are created with no headers. 104 | fn default() -> Self { 105 | Self { 106 | content_length: Default::default(), 107 | expect: Default::default(), 108 | chunked: Default::default(), 109 | // The default `Accept` media type is plain text. This is inclusive enough 110 | // for structured and unstructured text. 111 | accept: MediaType::PlainText, 112 | custom_entries: HashMap::default(), 113 | } 114 | } 115 | } 116 | 117 | impl Headers { 118 | /// Expects one header line and parses it, updating the header structure or returning an 119 | /// error if the header is invalid. 120 | /// 121 | /// # Errors 122 | /// `UnsupportedHeader` is returned when the parsed header line is not of interest 123 | /// to us or when it is unrecognizable. 124 | /// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests 125 | /// that the client is using HTTP features that we do not support in this implementation, 126 | /// which invalidates the request. 127 | /// 128 | /// # Examples 129 | /// 130 | /// ``` 131 | /// use micro_http::Headers; 132 | /// 133 | /// let mut request_header = Headers::default(); 134 | /// assert!(request_header 135 | /// .parse_header_line(b"Content-Length: 24") 136 | /// .is_ok()); 137 | /// assert!(request_header 138 | /// .parse_header_line(b"Content-Length: 24: 2") 139 | /// .is_err()); 140 | /// ``` 141 | pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> { 142 | // Headers must be ASCII, so also UTF-8 valid. 143 | match std::str::from_utf8(header_line) { 144 | Ok(headers_str) => { 145 | let entry = headers_str.splitn(2, ':').collect::>(); 146 | if entry.len() != 2 { 147 | return Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat( 148 | entry[0].to_string(), 149 | ))); 150 | } 151 | if let Ok(head) = Header::try_from(entry[0].as_bytes()) { 152 | match head { 153 | Header::ContentLength => match entry[1].trim().parse::() { 154 | Ok(content_length) => { 155 | self.content_length = content_length; 156 | Ok(()) 157 | } 158 | Err(_) => { 159 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 160 | entry[0].to_string(), 161 | entry[1].to_string(), 162 | ))) 163 | } 164 | }, 165 | Header::ContentType => { 166 | match MediaType::try_from(entry[1].trim().as_bytes()) { 167 | Ok(_) => Ok(()), 168 | Err(_) => Err(RequestError::HeaderError( 169 | HttpHeaderError::UnsupportedValue( 170 | entry[0].to_string(), 171 | entry[1].to_string(), 172 | ), 173 | )), 174 | } 175 | } 176 | Header::Accept => match MediaType::try_from(entry[1].trim().as_bytes()) { 177 | Ok(accept_type) => { 178 | self.accept = accept_type; 179 | Ok(()) 180 | } 181 | Err(_) => Err(RequestError::HeaderError( 182 | HttpHeaderError::UnsupportedValue( 183 | entry[0].to_string(), 184 | entry[1].to_string(), 185 | ), 186 | )), 187 | }, 188 | Header::TransferEncoding => match entry[1].trim() { 189 | "chunked" => { 190 | self.chunked = true; 191 | Ok(()) 192 | } 193 | "identity" => Ok(()), 194 | _ => Err(RequestError::HeaderError( 195 | HttpHeaderError::UnsupportedValue( 196 | entry[0].to_string(), 197 | entry[1].to_string(), 198 | ), 199 | )), 200 | }, 201 | Header::Expect => match entry[1].trim() { 202 | "100-continue" => { 203 | self.expect = true; 204 | Ok(()) 205 | } 206 | _ => Err(RequestError::HeaderError( 207 | HttpHeaderError::UnsupportedValue( 208 | entry[0].to_string(), 209 | entry[1].to_string(), 210 | ), 211 | )), 212 | }, 213 | Header::Server => Ok(()), 214 | Header::AcceptEncoding => Encoding::try_from(entry[1].trim().as_bytes()), 215 | } 216 | } else { 217 | self.insert_custom_header( 218 | entry[0].trim().to_string(), 219 | entry[1].trim().to_string(), 220 | )?; 221 | Ok(()) 222 | } 223 | } 224 | Err(utf8_err) => Err(RequestError::HeaderError( 225 | HttpHeaderError::InvalidUtf8String(utf8_err), 226 | )), 227 | } 228 | } 229 | 230 | /// Returns the content length of the body. 231 | pub fn content_length(&self) -> u32 { 232 | self.content_length 233 | } 234 | 235 | /// Returns `true` if the transfer encoding is chunked. 236 | #[allow(unused)] 237 | pub fn chunked(&self) -> bool { 238 | self.chunked 239 | } 240 | 241 | /// Returns `true` if the client is expecting the code 100. 242 | #[allow(unused)] 243 | pub fn expect(&self) -> bool { 244 | self.expect 245 | } 246 | 247 | /// Returns the `Accept` header `MediaType`. 248 | pub fn accept(&self) -> MediaType { 249 | self.accept 250 | } 251 | 252 | /// Parses a byte slice into a Headers structure for a HTTP request. 253 | /// 254 | /// The byte slice is expected to have the following format:
255 | /// * Request Header Lines " CRLF"- Optional
256 | /// There can be any number of request headers, including none, followed by 257 | /// an extra sequence of Carriage Return and Line Feed. 258 | /// All header fields are parsed. However, only the ones present in the 259 | /// [`Headers`](struct.Headers.html) struct are relevant to us and stored 260 | /// for future use. 261 | /// 262 | /// # Errors 263 | /// The function returns `InvalidHeader` when parsing the byte stream fails. 264 | /// 265 | /// # Examples 266 | /// 267 | /// ``` 268 | /// use micro_http::Headers; 269 | /// 270 | /// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n"); 271 | /// ``` 272 | pub fn try_from(bytes: &[u8]) -> Result { 273 | // Headers must be ASCII, so also UTF-8 valid. 274 | if let Ok(text) = std::str::from_utf8(bytes) { 275 | let mut headers = Self::default(); 276 | 277 | let header_lines = text.split("\r\n"); 278 | for header_line in header_lines { 279 | if header_line.is_empty() { 280 | break; 281 | } 282 | match headers.parse_header_line(header_line.as_bytes()) { 283 | Ok(_) 284 | | Err(RequestError::HeaderError(HttpHeaderError::UnsupportedValue(_, _))) => { 285 | continue 286 | } 287 | Err(e) => return Err(e), 288 | }; 289 | } 290 | return Ok(headers); 291 | } 292 | Err(RequestError::InvalidRequest) 293 | } 294 | 295 | /// Accept header setter. 296 | pub fn set_accept(&mut self, media_type: MediaType) { 297 | self.accept = media_type; 298 | } 299 | 300 | /// Insert a new custom header and value pair into the `HashMap`. 301 | pub fn insert_custom_header(&mut self, key: String, value: String) -> Result<(), RequestError> { 302 | self.custom_entries.insert(key, value); 303 | Ok(()) 304 | } 305 | 306 | /// Returns the custom header `HashMap`. 307 | pub fn custom_entries(&self) -> &HashMap { 308 | &self.custom_entries 309 | } 310 | } 311 | 312 | /// Wrapper over supported AcceptEncoding. 313 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 314 | pub struct Encoding {} 315 | 316 | impl Encoding { 317 | /// Parses a byte slice and checks if identity encoding is invalidated. Encoding 318 | /// must be ASCII, so also UTF-8 valid. 319 | /// 320 | /// # Errors 321 | /// `InvalidRequest` is returned when the byte stream is empty. 322 | /// 323 | /// `InvalidValue` is returned when the identity encoding is invalidated. 324 | /// 325 | /// `InvalidUtf8String` is returned when the byte stream contains invalid characters. 326 | /// 327 | /// # Examples 328 | /// 329 | /// ``` 330 | /// use micro_http::Encoding; 331 | /// 332 | /// assert!(Encoding::try_from(b"deflate").is_ok()); 333 | /// assert!(Encoding::try_from(b"identity;q=0").is_err()); 334 | /// ``` 335 | pub fn try_from(bytes: &[u8]) -> Result<(), RequestError> { 336 | if bytes.is_empty() { 337 | return Err(RequestError::InvalidRequest); 338 | } 339 | match std::str::from_utf8(bytes) { 340 | Ok(headers_str) => { 341 | let entry = headers_str.split(',').collect::>(); 342 | 343 | for encoding in entry { 344 | match encoding.trim() { 345 | "identity;q=0" => { 346 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 347 | "Accept-Encoding".to_string(), 348 | encoding.to_string(), 349 | ))) 350 | } 351 | "*;q=0" if !headers_str.contains("identity") => { 352 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 353 | "Accept-Encoding".to_string(), 354 | encoding.to_string(), 355 | ))) 356 | } 357 | _ => Ok(()), 358 | }?; 359 | } 360 | Ok(()) 361 | } 362 | Err(utf8_err) => Err(RequestError::HeaderError( 363 | HttpHeaderError::InvalidUtf8String(utf8_err), 364 | )), 365 | } 366 | } 367 | } 368 | 369 | /// Wrapper over supported Media Types. 370 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 371 | pub enum MediaType { 372 | /// Media Type: "text/plain". 373 | PlainText, 374 | /// Media Type: "application/json". 375 | ApplicationJson, 376 | } 377 | 378 | impl Default for MediaType { 379 | /// Default value for MediaType is application/json 380 | fn default() -> Self { 381 | Self::ApplicationJson 382 | } 383 | } 384 | 385 | impl MediaType { 386 | /// Parses a byte slice into a MediaType structure for a HTTP request. MediaType 387 | /// must be ASCII, so also UTF-8 valid. 388 | /// 389 | /// # Errors 390 | /// The function returns `InvalidRequest` when parsing the byte stream fails or 391 | /// unsupported MediaType found. 392 | /// 393 | /// # Examples 394 | /// 395 | /// ``` 396 | /// use micro_http::MediaType; 397 | /// 398 | /// assert!(MediaType::try_from(b"application/json").is_ok()); 399 | /// assert!(MediaType::try_from(b"application/json2").is_err()); 400 | /// ``` 401 | pub fn try_from(bytes: &[u8]) -> Result { 402 | if bytes.is_empty() { 403 | return Err(RequestError::InvalidRequest); 404 | } 405 | let utf8_slice = 406 | String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?; 407 | match utf8_slice.as_str().trim() { 408 | "text/plain" => Ok(Self::PlainText), 409 | "application/json" => Ok(Self::ApplicationJson), 410 | _ => Err(RequestError::InvalidRequest), 411 | } 412 | } 413 | 414 | /// Returns a static string representation of the object. 415 | /// 416 | /// # Examples 417 | /// 418 | /// ``` 419 | /// use micro_http::MediaType; 420 | /// 421 | /// let media_type = MediaType::ApplicationJson; 422 | /// assert_eq!(media_type.as_str(), "application/json"); 423 | /// ``` 424 | pub fn as_str(self) -> &'static str { 425 | match self { 426 | Self::PlainText => "text/plain", 427 | Self::ApplicationJson => "application/json", 428 | } 429 | } 430 | } 431 | 432 | #[cfg(test)] 433 | mod tests { 434 | #![allow(missing_docs)] 435 | use super::*; 436 | use std::collections::HashMap; 437 | 438 | impl Headers { 439 | pub fn new(content_length: u32, expect: bool, chunked: bool) -> Self { 440 | Self { 441 | content_length, 442 | expect, 443 | chunked, 444 | accept: MediaType::PlainText, 445 | custom_entries: HashMap::default(), 446 | } 447 | } 448 | } 449 | 450 | #[test] 451 | fn test_default() { 452 | let headers = Headers::default(); 453 | assert_eq!(headers.content_length(), 0); 454 | assert!(!headers.chunked()); 455 | assert!(!headers.expect()); 456 | assert_eq!(headers.accept(), MediaType::PlainText); 457 | assert_eq!(headers.custom_entries(), &HashMap::default()); 458 | } 459 | 460 | #[test] 461 | fn test_try_from_media() { 462 | assert_eq!( 463 | MediaType::try_from(b"application/json").unwrap(), 464 | MediaType::ApplicationJson 465 | ); 466 | 467 | assert_eq!( 468 | MediaType::try_from(b"text/plain").unwrap(), 469 | MediaType::PlainText 470 | ); 471 | 472 | assert_eq!( 473 | MediaType::try_from(b"").unwrap_err(), 474 | RequestError::InvalidRequest 475 | ); 476 | 477 | assert_eq!( 478 | MediaType::try_from(b"application/json-patch").unwrap_err(), 479 | RequestError::InvalidRequest 480 | ); 481 | } 482 | 483 | #[test] 484 | fn test_media_as_str() { 485 | let media_type = MediaType::ApplicationJson; 486 | assert_eq!(media_type.as_str(), "application/json"); 487 | 488 | let media_type = MediaType::PlainText; 489 | assert_eq!(media_type.as_str(), "text/plain"); 490 | } 491 | 492 | #[test] 493 | fn test_try_from_encoding() { 494 | assert_eq!( 495 | Encoding::try_from(b"").unwrap_err(), 496 | RequestError::InvalidRequest 497 | ); 498 | 499 | assert_eq!( 500 | Encoding::try_from(b"identity;q=0").unwrap_err(), 501 | RequestError::HeaderError(HttpHeaderError::InvalidValue( 502 | "Accept-Encoding".to_string(), 503 | "identity;q=0".to_string() 504 | )) 505 | ); 506 | 507 | assert!(Encoding::try_from(b"identity;q").is_ok()); 508 | 509 | assert_eq!( 510 | Encoding::try_from(b"*;q=0").unwrap_err(), 511 | RequestError::HeaderError(HttpHeaderError::InvalidValue( 512 | "Accept-Encoding".to_string(), 513 | "*;q=0".to_string() 514 | )) 515 | ); 516 | 517 | let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; 518 | assert!(Encoding::try_from(&bytes[..]).is_err()); 519 | 520 | assert!(Encoding::try_from(b"identity;q=1").is_ok()); 521 | assert!(Encoding::try_from(b"identity;q=0.1").is_ok()); 522 | assert!(Encoding::try_from(b"deflate, identity, *;q=0").is_ok()); 523 | assert!(Encoding::try_from(b"br").is_ok()); 524 | assert!(Encoding::try_from(b"compress").is_ok()); 525 | assert!(Encoding::try_from(b"gzip").is_ok()); 526 | } 527 | 528 | #[test] 529 | fn test_try_from_headers() { 530 | // Valid headers. 531 | let headers = Headers::try_from( 532 | b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept: application/json\r\nContent-Length: 55\r\n\r\n" 533 | ) 534 | .unwrap(); 535 | assert_eq!(headers.content_length, 55); 536 | assert_eq!(headers.accept, MediaType::ApplicationJson); 537 | assert_eq!( 538 | headers.custom_entries().get("Last-Modified").unwrap(), 539 | "Tue, 15 Nov 1994 12:45:26 GMT" 540 | ); 541 | assert_eq!(headers.custom_entries().len(), 1); 542 | 543 | // Valid headers. (${HEADER_NAME} : WHITESPACE ${HEADER_VALUE}) 544 | // Any number of whitespace characters should be accepted including zero. 545 | let headers = Headers::try_from( 546 | b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept:text/plain\r\nContent-Length: 49\r\n\r\n" 547 | ) 548 | .unwrap(); 549 | assert_eq!(headers.content_length, 49); 550 | assert_eq!(headers.accept, MediaType::PlainText); 551 | 552 | // Valid headers. 553 | let headers = Headers::try_from( 554 | b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 29\r\n\r\n", 555 | ) 556 | .unwrap(); 557 | assert_eq!(headers.content_length, 29); 558 | 559 | // Custom headers only. 560 | let headers = Headers::try_from( 561 | b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nfoo: bar\r\nbar: 15\r\n\r\n", 562 | ) 563 | .unwrap(); 564 | let custom_entries = headers.custom_entries(); 565 | assert_eq!(custom_entries.get("foo").unwrap(), "bar"); 566 | assert_eq!(custom_entries.get("bar").unwrap(), "15"); 567 | assert_eq!(custom_entries.len(), 3); 568 | 569 | // Valid headers, invalid value. 570 | assert_eq!( 571 | Headers::try_from( 572 | b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: -55\r\n\r\n" 573 | ) 574 | .unwrap_err(), 575 | RequestError::HeaderError(HttpHeaderError::InvalidValue( 576 | "Content-Length".to_string(), 577 | " -55".to_string() 578 | )) 579 | ); 580 | 581 | let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; 582 | // Invalid headers. 583 | assert!(Headers::try_from(&bytes[..]).is_err()); 584 | } 585 | 586 | #[test] 587 | fn test_parse_header_line() { 588 | let mut header = Headers::default(); 589 | 590 | // Invalid header syntax. 591 | assert_eq!( 592 | header.parse_header_line(b"Expect"), 593 | Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat( 594 | "Expect".to_string() 595 | ))) 596 | ); 597 | 598 | // Invalid content length. 599 | assert_eq!( 600 | header.parse_header_line(b"Content-Length: five"), 601 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 602 | "Content-Length".to_string(), 603 | " five".to_string() 604 | ))) 605 | ); 606 | 607 | // Invalid transfer encoding. 608 | assert_eq!( 609 | header.parse_header_line(b"Transfer-Encoding: gzip"), 610 | Err(RequestError::HeaderError( 611 | HttpHeaderError::UnsupportedValue( 612 | "Transfer-Encoding".to_string(), 613 | " gzip".to_string() 614 | ) 615 | )) 616 | ); 617 | 618 | // Invalid expect. 619 | assert_eq!( 620 | header 621 | .parse_header_line(b"Expect: 102-processing") 622 | .unwrap_err(), 623 | RequestError::HeaderError(HttpHeaderError::UnsupportedValue( 624 | "Expect".to_string(), 625 | " 102-processing".to_string() 626 | )) 627 | ); 628 | 629 | // Unsupported media type. 630 | assert_eq!( 631 | header 632 | .parse_header_line(b"Content-Type: application/json-patch") 633 | .unwrap_err(), 634 | RequestError::HeaderError(HttpHeaderError::UnsupportedValue( 635 | "Content-Type".to_string(), 636 | " application/json-patch".to_string() 637 | )) 638 | ); 639 | 640 | // Invalid input format. 641 | let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; 642 | assert_eq!( 643 | header.parse_header_line(&input[..]).unwrap_err(), 644 | RequestError::HeaderError(HttpHeaderError::InvalidUtf8String( 645 | String::from_utf8(input.to_vec()).unwrap_err().utf8_error() 646 | )) 647 | ); 648 | 649 | // Test valid transfer encoding. 650 | assert!(header 651 | .parse_header_line(b"Transfer-Encoding: chunked") 652 | .is_ok()); 653 | assert!(header.chunked()); 654 | 655 | // Test valid expect. 656 | assert!(header.parse_header_line(b"Expect: 100-continue").is_ok()); 657 | assert!(header.expect()); 658 | 659 | // Test valid media type. 660 | assert!(header 661 | .parse_header_line(b"Content-Type: application/json") 662 | .is_ok()); 663 | 664 | // Test valid accept media type. 665 | assert!(header 666 | .parse_header_line(b"Accept: application/json") 667 | .is_ok()); 668 | assert_eq!(header.accept, MediaType::ApplicationJson); 669 | assert!(header.parse_header_line(b"Accept: text/plain").is_ok()); 670 | assert_eq!(header.accept, MediaType::PlainText); 671 | 672 | // Test invalid accept media type. 673 | assert!(header 674 | .parse_header_line(b"Accept: application/json-patch") 675 | .is_err()); 676 | 677 | // Invalid content length. 678 | assert_eq!( 679 | header.parse_header_line(b"Content-Length: -1"), 680 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 681 | "Content-Length".to_string(), 682 | " -1".to_string() 683 | ))) 684 | ); 685 | 686 | assert!(header 687 | .parse_header_line(b"Accept-Encoding: deflate") 688 | .is_ok()); 689 | assert_eq!( 690 | header.parse_header_line(b"Accept-Encoding: compress, identity;q=0"), 691 | Err(RequestError::HeaderError(HttpHeaderError::InvalidValue( 692 | "Accept-Encoding".to_string(), 693 | " identity;q=0".to_string() 694 | ))) 695 | ); 696 | 697 | // Test custom header. 698 | assert_eq!(header.custom_entries().len(), 0); 699 | assert!(header.parse_header_line(b"Custom-Header: foo").is_ok()); 700 | assert_eq!( 701 | header.custom_entries().get("Custom-Header").unwrap(), 702 | &"foo".to_string() 703 | ); 704 | assert_eq!(header.custom_entries().len(), 1); 705 | } 706 | 707 | #[test] 708 | fn test_parse_header_whitespace() { 709 | let mut header = Headers::default(); 710 | // Test that any number of whitespace characters are accepted before the header value. 711 | // For Content-Length 712 | assert!(header.parse_header_line(b"Content-Length:24").is_ok()); 713 | assert!(header.parse_header_line(b"Content-Length: 24").is_ok()); 714 | 715 | // For ContentType 716 | assert!(header 717 | .parse_header_line(b"Content-Type:application/json") 718 | .is_ok()); 719 | assert!(header 720 | .parse_header_line(b"Content-Type: application/json") 721 | .is_ok()); 722 | 723 | // For Accept 724 | assert!(header.parse_header_line(b"Accept:application/json").is_ok()); 725 | assert!(header 726 | .parse_header_line(b"Accept: application/json") 727 | .is_ok()); 728 | 729 | // For Transfer-Encoding 730 | assert!(header 731 | .parse_header_line(b"Transfer-Encoding:chunked") 732 | .is_ok()); 733 | assert!(header.chunked()); 734 | assert!(header 735 | .parse_header_line(b"Transfer-Encoding: chunked") 736 | .is_ok()); 737 | assert!(header.chunked()); 738 | 739 | // For Server 740 | assert!(header.parse_header_line(b"Server:xxx.yyy.zzz").is_ok()); 741 | assert!(header.parse_header_line(b"Server: xxx.yyy.zzz").is_ok()); 742 | 743 | // For Expect 744 | assert!(header.parse_header_line(b"Expect:100-continue").is_ok()); 745 | assert!(header.parse_header_line(b"Expect: 100-continue").is_ok()); 746 | 747 | // Test that custom headers' names and values are trimmed before being stored 748 | // inside the HashMap. 749 | assert!(header.parse_header_line(b"Foo:bar").is_ok()); 750 | assert_eq!(header.custom_entries().get("Foo").unwrap(), "bar"); 751 | assert!(header.parse_header_line(b" Bar : foo ").is_ok()); 752 | assert_eq!(header.custom_entries().get("Bar").unwrap(), "foo"); 753 | } 754 | 755 | #[test] 756 | fn test_header_try_from() { 757 | // Bad header. 758 | assert_eq!( 759 | Header::try_from(b"Encoding").unwrap_err(), 760 | RequestError::HeaderError(HttpHeaderError::UnsupportedName("encoding".to_string())) 761 | ); 762 | 763 | // Invalid encoding. 764 | let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; 765 | assert_eq!( 766 | Header::try_from(&input[..]).unwrap_err(), 767 | RequestError::InvalidRequest 768 | ); 769 | 770 | // Test valid headers. 771 | let header = Header::try_from(b"Expect").unwrap(); 772 | assert_eq!(header.raw(), b"Expect"); 773 | 774 | let header = Header::try_from(b"Transfer-Encoding").unwrap(); 775 | assert_eq!(header.raw(), b"Transfer-Encoding"); 776 | 777 | let header = Header::try_from(b"content-length").unwrap(); 778 | assert_eq!(header.raw(), b"Content-Length"); 779 | 780 | let header = Header::try_from(b"Accept").unwrap(); 781 | assert_eq!(header.raw(), b"Accept"); 782 | } 783 | 784 | #[test] 785 | fn test_set_accept() { 786 | let mut headers = Headers::default(); 787 | assert_eq!(headers.accept(), MediaType::PlainText); 788 | 789 | headers.set_accept(MediaType::ApplicationJson); 790 | assert_eq!(headers.accept(), MediaType::ApplicationJson); 791 | } 792 | } 793 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::fmt::{Display, Error, Formatter}; 5 | use std::str::Utf8Error; 6 | 7 | pub mod headers; 8 | 9 | pub mod ascii { 10 | pub const CR: u8 = b'\r'; 11 | pub const COLON: u8 = b':'; 12 | pub const LF: u8 = b'\n'; 13 | pub const SP: u8 = b' '; 14 | pub const CRLF_LEN: usize = 2; 15 | } 16 | 17 | ///Errors associated with a header that is invalid. 18 | #[derive(Debug, PartialEq, Eq)] 19 | pub enum HttpHeaderError { 20 | /// The header is misformatted. 21 | InvalidFormat(String), 22 | /// The specified header contains illegal characters. 23 | InvalidUtf8String(Utf8Error), 24 | ///The value specified is not valid. 25 | InvalidValue(String, String), 26 | /// The content length specified is longer than the limit imposed by Micro Http. 27 | SizeLimitExceeded(String), 28 | /// The requested feature is not currently supported. 29 | UnsupportedFeature(String, String), 30 | /// The header specified is not supported. 31 | UnsupportedName(String), 32 | /// The value for the specified header is not supported. 33 | UnsupportedValue(String, String), 34 | } 35 | 36 | impl Display for HttpHeaderError { 37 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 38 | match self { 39 | Self::InvalidFormat(header_key) => { 40 | write!(f, "Header is incorrectly formatted. Key: {}", header_key) 41 | } 42 | Self::InvalidUtf8String(header_key) => { 43 | write!(f, "Header contains invalid characters. Key: {}", header_key) 44 | } 45 | Self::InvalidValue(header_name, value) => { 46 | write!(f, "Invalid value. Key:{}; Value:{}", header_name, value) 47 | } 48 | Self::SizeLimitExceeded(inner) => { 49 | write!(f, "Invalid content length. Header: {}", inner) 50 | } 51 | Self::UnsupportedFeature(header_key, header_value) => write!( 52 | f, 53 | "Unsupported feature. Key: {}; Value: {}", 54 | header_key, header_value 55 | ), 56 | Self::UnsupportedName(inner) => write!(f, "Unsupported header name. Key: {}", inner), 57 | Self::UnsupportedValue(header_key, header_value) => write!( 58 | f, 59 | "Unsupported value. Key:{}; Value:{}", 60 | header_key, header_value 61 | ), 62 | } 63 | } 64 | } 65 | 66 | /// Errors associated with parsing the HTTP Request from a u8 slice. 67 | #[derive(Debug, PartialEq, Eq)] 68 | pub enum RequestError { 69 | /// No request was pending while the request body was being parsed. 70 | BodyWithoutPendingRequest, 71 | /// Header specified is either invalid or not supported by this HTTP implementation. 72 | HeaderError(HttpHeaderError), 73 | /// No request was pending while the request headers were being parsed. 74 | HeadersWithoutPendingRequest, 75 | /// The HTTP Method is not supported or it is invalid. 76 | InvalidHttpMethod(&'static str), 77 | /// The HTTP Version in the Request is not supported or it is invalid. 78 | InvalidHttpVersion(&'static str), 79 | /// The Request is invalid and cannot be served. 80 | InvalidRequest, 81 | /// Request URI is invalid. 82 | InvalidUri(&'static str), 83 | /// Overflow occurred when parsing a request. 84 | Overflow, 85 | /// Underflow occurred when parsing a request. 86 | Underflow, 87 | /// Payload too large. 88 | SizeLimitExceeded(usize, usize), 89 | } 90 | 91 | impl Display for RequestError { 92 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 93 | match self { 94 | Self::BodyWithoutPendingRequest => write!( 95 | f, 96 | "No request was pending while the request body was being parsed." 97 | ), 98 | Self::HeaderError(inner) => write!(f, "Invalid header. Reason: {}", inner), 99 | Self::HeadersWithoutPendingRequest => write!( 100 | f, 101 | "No request was pending while the request headers were being parsed." 102 | ), 103 | Self::InvalidHttpMethod(inner) => write!(f, "Invalid HTTP Method: {}", inner), 104 | Self::InvalidHttpVersion(inner) => write!(f, "Invalid HTTP Version: {}", inner), 105 | Self::InvalidRequest => write!(f, "Invalid request."), 106 | Self::InvalidUri(inner) => write!(f, "Invalid URI: {}", inner), 107 | Self::Overflow => write!(f, "Overflow occurred when parsing a request."), 108 | Self::Underflow => write!(f, "Underflow occurred when parsing a request."), 109 | Self::SizeLimitExceeded(limit, size) => write!( 110 | f, 111 | "Request payload with size {} is larger than the limit of {} \ 112 | allowed by server.", 113 | size, limit 114 | ), 115 | } 116 | } 117 | } 118 | 119 | /// Errors associated with a HTTP Connection. 120 | #[derive(Debug)] 121 | pub enum ConnectionError { 122 | /// Attempted to read or write on a closed connection. 123 | ConnectionClosed, 124 | /// Attempted to write on a stream when there was nothing to write. 125 | InvalidWrite, 126 | /// The request parsing has failed. 127 | ParseError(RequestError), 128 | /// Could not perform a read operation from stream successfully. 129 | StreamReadError(vmm_sys_util::errno::Error), 130 | /// Could not perform a write operation to stream successfully. 131 | StreamWriteError(std::io::Error), 132 | } 133 | 134 | impl Display for ConnectionError { 135 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 136 | match self { 137 | Self::ConnectionClosed => write!(f, "Connection closed."), 138 | Self::InvalidWrite => write!(f, "Invalid write attempt."), 139 | Self::ParseError(inner) => write!(f, "Parsing error: {}", inner), 140 | Self::StreamReadError(inner) => write!(f, "Reading stream error: {}", inner), 141 | Self::StreamWriteError(inner) => write!(f, "Writing stream error: {}", inner), 142 | } 143 | } 144 | } 145 | 146 | /// Errors pertaining to `HttpRoute`. 147 | #[derive(Debug)] 148 | #[allow(dead_code)] 149 | pub enum RouteError { 150 | /// Handler for http routing path already exists. 151 | HandlerExist(String), 152 | } 153 | 154 | impl Display for RouteError { 155 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 156 | match self { 157 | RouteError::HandlerExist(p) => write!(f, "handler for {} already exists", p), 158 | } 159 | } 160 | } 161 | 162 | /// Errors pertaining to `HttpServer`. 163 | #[derive(Debug)] 164 | pub enum ServerError { 165 | /// Error from one of the connections. 166 | ConnectionError(ConnectionError), 167 | /// Epoll operations failed. 168 | IOError(std::io::Error), 169 | /// Overflow occurred while processing messages. 170 | Overflow, 171 | /// Server maximum capacity has been reached. 172 | ServerFull, 173 | /// Shutdown requested. 174 | ShutdownEvent, 175 | /// Underflow occurred while processing messages. 176 | Underflow, 177 | } 178 | 179 | impl Display for ServerError { 180 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 181 | match self { 182 | Self::ConnectionError(inner) => write!(f, "Connection error: {}", inner), 183 | Self::IOError(inner) => write!(f, "IO error: {}", inner), 184 | Self::Overflow => write!(f, "Overflow occured while processing messages."), 185 | Self::ServerFull => write!(f, "Server is full."), 186 | Self::Underflow => write!(f, "Underflow occured while processing messages."), 187 | Self::ShutdownEvent => write!(f, "Shutdown requested."), 188 | } 189 | } 190 | } 191 | 192 | /// The Body associated with an HTTP Request or Response. 193 | /// 194 | /// ## Examples 195 | /// ``` 196 | /// use micro_http::Body; 197 | /// let body = Body::new("This is a test body.".to_string()); 198 | /// assert_eq!(body.raw(), b"This is a test body."); 199 | /// assert_eq!(body.len(), 20); 200 | /// ``` 201 | #[derive(Clone, Debug, PartialEq, Eq)] 202 | pub struct Body { 203 | /// Body of the HTTP message as bytes. 204 | pub body: Vec, 205 | } 206 | 207 | impl Body { 208 | /// Creates a new `Body` from a `String` input. 209 | pub fn new>>(body: T) -> Self { 210 | Self { body: body.into() } 211 | } 212 | 213 | /// Returns the body as an `u8 slice`. 214 | pub fn raw(&self) -> &[u8] { 215 | self.body.as_slice() 216 | } 217 | 218 | /// Returns the length of the `Body`. 219 | pub fn len(&self) -> usize { 220 | self.body.len() 221 | } 222 | 223 | /// Checks if the body is empty, ie with zero length 224 | pub fn is_empty(&self) -> bool { 225 | self.body.len() == 0 226 | } 227 | } 228 | 229 | /// Supported HTTP Methods. 230 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 231 | pub enum Method { 232 | /// GET Method. 233 | Get, 234 | /// PUT Method. 235 | Put, 236 | /// PATCH Method. 237 | Patch, 238 | } 239 | 240 | impl Method { 241 | /// Returns a `Method` object if the parsing of `bytes` is successful. 242 | /// 243 | /// The method is case sensitive. A call to try_from with the input b"get" will return 244 | /// an error, but when using the input b"GET", it returns Method::Get. 245 | /// 246 | /// # Errors 247 | /// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported. 248 | pub fn try_from(bytes: &[u8]) -> Result { 249 | match bytes { 250 | b"GET" => Ok(Self::Get), 251 | b"PUT" => Ok(Self::Put), 252 | b"PATCH" => Ok(Self::Patch), 253 | _ => Err(RequestError::InvalidHttpMethod("Unsupported HTTP method.")), 254 | } 255 | } 256 | 257 | /// Returns an `u8 slice` corresponding to the Method. 258 | pub fn raw(self) -> &'static [u8] { 259 | match self { 260 | Self::Get => b"GET", 261 | Self::Put => b"PUT", 262 | Self::Patch => b"PATCH", 263 | } 264 | } 265 | 266 | /// Returns an &str corresponding to the Method. 267 | pub fn to_str(self) -> &'static str { 268 | match self { 269 | Method::Get => "GET", 270 | Method::Put => "PUT", 271 | Method::Patch => "PATCH", 272 | } 273 | } 274 | } 275 | 276 | /// Supported HTTP Versions. 277 | /// 278 | /// # Examples 279 | /// ``` 280 | /// use micro_http::Version; 281 | /// let version = Version::try_from(b"HTTP/1.1"); 282 | /// assert!(version.is_ok()); 283 | /// 284 | /// let version = Version::try_from(b"http/1.1"); 285 | /// assert!(version.is_err()); 286 | /// ``` 287 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 288 | pub enum Version { 289 | /// HTTP/1.0 290 | Http10, 291 | /// HTTP/1.1 292 | Http11, 293 | } 294 | 295 | impl Default for Version { 296 | /// Returns the default HTTP version = HTTP/1.1. 297 | fn default() -> Self { 298 | Self::Http11 299 | } 300 | } 301 | 302 | impl Version { 303 | /// HTTP Version as an `u8 slice`. 304 | pub fn raw(self) -> &'static [u8] { 305 | match self { 306 | Self::Http10 => b"HTTP/1.0", 307 | Self::Http11 => b"HTTP/1.1", 308 | } 309 | } 310 | 311 | /// Creates a new HTTP Version from an `u8 slice`. 312 | /// 313 | /// The supported versions are HTTP/1.0 and HTTP/1.1. 314 | /// The version is case sensitive and the accepted input is upper case. 315 | /// 316 | /// # Errors 317 | /// Returns a `InvalidHttpVersion` when the HTTP version is not supported. 318 | pub fn try_from(bytes: &[u8]) -> Result { 319 | match bytes { 320 | b"HTTP/1.0" => Ok(Self::Http10), 321 | b"HTTP/1.1" => Ok(Self::Http11), 322 | _ => Err(RequestError::InvalidHttpVersion( 323 | "Unsupported HTTP version.", 324 | )), 325 | } 326 | } 327 | } 328 | 329 | #[cfg(test)] 330 | mod tests { 331 | use super::*; 332 | 333 | impl PartialEq for ConnectionError { 334 | fn eq(&self, other: &Self) -> bool { 335 | use self::ConnectionError::*; 336 | match (self, other) { 337 | (ParseError(ref e), ParseError(ref other_e)) => e.eq(other_e), 338 | (ConnectionClosed, ConnectionClosed) => true, 339 | (StreamReadError(ref e), StreamReadError(ref other_e)) => { 340 | format!("{}", e).eq(&format!("{}", other_e)) 341 | } 342 | (StreamWriteError(ref e), StreamWriteError(ref other_e)) => { 343 | format!("{}", e).eq(&format!("{}", other_e)) 344 | } 345 | (InvalidWrite, InvalidWrite) => true, 346 | _ => false, 347 | } 348 | } 349 | } 350 | 351 | impl PartialEq for ServerError { 352 | fn eq(&self, other: &Self) -> bool { 353 | use self::ServerError::*; 354 | match (self, other) { 355 | (ConnectionError(ref e), ConnectionError(ref other_e)) => e.eq(other_e), 356 | (IOError(ref e), IOError(ref other_e)) => { 357 | e.raw_os_error() == other_e.raw_os_error() 358 | } 359 | (Overflow, Overflow) => true, 360 | (ServerFull, ServerFull) => true, 361 | (ShutdownEvent, ShutdownEvent) => true, 362 | (Underflow, Underflow) => true, 363 | _ => false, 364 | } 365 | } 366 | } 367 | 368 | #[test] 369 | fn test_version() { 370 | // Tests for raw() 371 | assert_eq!(Version::Http10.raw(), b"HTTP/1.0"); 372 | assert_eq!(Version::Http11.raw(), b"HTTP/1.1"); 373 | 374 | // Tests for try_from() 375 | assert_eq!(Version::try_from(b"HTTP/1.0").unwrap(), Version::Http10); 376 | assert_eq!(Version::try_from(b"HTTP/1.1").unwrap(), Version::Http11); 377 | assert_eq!( 378 | Version::try_from(b"HTTP/2.0").unwrap_err(), 379 | RequestError::InvalidHttpVersion("Unsupported HTTP version.") 380 | ); 381 | 382 | // Test for default() 383 | assert_eq!(Version::default(), Version::Http11); 384 | } 385 | 386 | #[test] 387 | fn test_method() { 388 | // Test for raw 389 | assert_eq!(Method::Get.raw(), b"GET"); 390 | assert_eq!(Method::Put.raw(), b"PUT"); 391 | assert_eq!(Method::Patch.raw(), b"PATCH"); 392 | 393 | // Tests for try_from 394 | assert_eq!(Method::try_from(b"GET").unwrap(), Method::Get); 395 | assert_eq!(Method::try_from(b"PUT").unwrap(), Method::Put); 396 | assert_eq!(Method::try_from(b"PATCH").unwrap(), Method::Patch); 397 | assert_eq!( 398 | Method::try_from(b"POST").unwrap_err(), 399 | RequestError::InvalidHttpMethod("Unsupported HTTP method.") 400 | ); 401 | } 402 | 403 | #[test] 404 | fn test_body() { 405 | let body = Body::new("".to_string()); 406 | // Test for is_empty 407 | assert!(body.is_empty()); 408 | let body = Body::new("This is a body.".to_string()); 409 | // Test for len 410 | assert_eq!(body.len(), 15); 411 | // Test for raw 412 | assert_eq!(body.raw(), b"This is a body."); 413 | } 414 | 415 | #[test] 416 | fn test_display_request_error() { 417 | assert_eq!( 418 | format!("{}", RequestError::BodyWithoutPendingRequest), 419 | "No request was pending while the request body was being parsed." 420 | ); 421 | assert_eq!( 422 | format!("{}", RequestError::HeadersWithoutPendingRequest), 423 | "No request was pending while the request headers were being parsed." 424 | ); 425 | assert_eq!( 426 | format!("{}", RequestError::InvalidHttpMethod("test")), 427 | "Invalid HTTP Method: test" 428 | ); 429 | assert_eq!( 430 | format!("{}", RequestError::InvalidHttpVersion("test")), 431 | "Invalid HTTP Version: test" 432 | ); 433 | assert_eq!( 434 | format!("{}", RequestError::InvalidRequest), 435 | "Invalid request." 436 | ); 437 | assert_eq!( 438 | format!("{}", RequestError::InvalidUri("test")), 439 | "Invalid URI: test" 440 | ); 441 | assert_eq!( 442 | format!("{}", RequestError::Overflow), 443 | "Overflow occurred when parsing a request." 444 | ); 445 | assert_eq!( 446 | format!("{}", RequestError::Underflow), 447 | "Underflow occurred when parsing a request." 448 | ); 449 | assert_eq!( 450 | format!("{}", RequestError::SizeLimitExceeded(4, 10)), 451 | "Request payload with size 10 is larger than the limit of 4 allowed by server." 452 | ); 453 | } 454 | 455 | #[test] 456 | fn test_display_header_error() { 457 | assert_eq!( 458 | format!( 459 | "{}", 460 | RequestError::HeaderError(HttpHeaderError::InvalidFormat("test".to_string())) 461 | ), 462 | "Invalid header. Reason: Header is incorrectly formatted. Key: test" 463 | ); 464 | let value = String::from_utf8(vec![0, 159]); 465 | assert_eq!( 466 | format!( 467 | "{}", 468 | RequestError::HeaderError(HttpHeaderError::InvalidUtf8String( 469 | value.unwrap_err().utf8_error() 470 | )) 471 | ), 472 | "Invalid header. Reason: Header contains invalid characters. Key: invalid utf-8 sequence of 1 bytes from index 1" 473 | ); 474 | assert_eq!( 475 | format!( 476 | "{}", 477 | RequestError::HeaderError(HttpHeaderError::SizeLimitExceeded("test".to_string())) 478 | ), 479 | "Invalid header. Reason: Invalid content length. Header: test" 480 | ); 481 | assert_eq!( 482 | format!( 483 | "{}", 484 | RequestError::HeaderError(HttpHeaderError::UnsupportedFeature( 485 | "test".to_string(), 486 | "test".to_string() 487 | )) 488 | ), 489 | "Invalid header. Reason: Unsupported feature. Key: test; Value: test" 490 | ); 491 | assert_eq!( 492 | format!( 493 | "{}", 494 | RequestError::HeaderError(HttpHeaderError::UnsupportedName("test".to_string())) 495 | ), 496 | "Invalid header. Reason: Unsupported header name. Key: test" 497 | ); 498 | assert_eq!( 499 | format!( 500 | "{}", 501 | RequestError::HeaderError(HttpHeaderError::UnsupportedValue( 502 | "test".to_string(), 503 | "test".to_string() 504 | )) 505 | ), 506 | "Invalid header. Reason: Unsupported value. Key:test; Value:test" 507 | ); 508 | } 509 | 510 | #[test] 511 | fn test_display_connection_error() { 512 | assert_eq!( 513 | format!("{}", ConnectionError::ConnectionClosed), 514 | "Connection closed." 515 | ); 516 | assert_eq!( 517 | format!( 518 | "{}", 519 | ConnectionError::ParseError(RequestError::InvalidRequest) 520 | ), 521 | "Parsing error: Invalid request." 522 | ); 523 | assert_eq!( 524 | format!("{}", ConnectionError::InvalidWrite), 525 | "Invalid write attempt." 526 | ); 527 | assert_eq!( 528 | format!( 529 | "{}", 530 | ConnectionError::StreamWriteError(std::io::Error::from_raw_os_error(11)) 531 | ), 532 | "Writing stream error: Resource temporarily unavailable (os error 11)" 533 | ); 534 | } 535 | 536 | #[test] 537 | fn test_display_server_error() { 538 | assert_eq!( 539 | format!( 540 | "{}", 541 | ServerError::ConnectionError(ConnectionError::ConnectionClosed) 542 | ), 543 | "Connection error: Connection closed." 544 | ); 545 | assert_eq!( 546 | format!( 547 | "{}", 548 | ServerError::IOError(std::io::Error::from_raw_os_error(11)) 549 | ), 550 | "IO error: Resource temporarily unavailable (os error 11)" 551 | ); 552 | assert_eq!( 553 | format!("{}", ServerError::Overflow), 554 | "Overflow occured while processing messages." 555 | ); 556 | assert_eq!(format!("{}", ServerError::ServerFull), "Server is full."); 557 | assert_eq!( 558 | format!("{}", ServerError::Underflow), 559 | "Underflow occured while processing messages." 560 | ); 561 | } 562 | 563 | #[test] 564 | fn test_display_route_error() { 565 | assert_eq!( 566 | format!("{}", RouteError::HandlerExist("test".to_string())), 567 | "handler for test already exists" 568 | ); 569 | } 570 | 571 | #[test] 572 | fn test_method_to_str() { 573 | let val = Method::Get; 574 | assert_eq!(val.to_str(), "GET"); 575 | 576 | let val = Method::Put; 577 | assert_eq!(val.to_str(), "PUT"); 578 | 579 | let val = Method::Patch; 580 | assert_eq!(val.to_str(), "PATCH"); 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #![deny(missing_docs)] 4 | //! Minimal implementation of the [HTTP/1.0](https://tools.ietf.org/html/rfc1945) 5 | //! and [HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) protocols. 6 | //! 7 | //! HTTP/1.1 has a mandatory header **Host**, but as this crate is only used 8 | //! for parsing API requests, this header (if present) is ignored. 9 | //! 10 | //! This HTTP implementation is stateless thus it does not support chunking or 11 | //! compression. 12 | //! 13 | //! ## Supported Headers 14 | //! The **micro_http** crate has support for parsing the following **Request** 15 | //! headers: 16 | //! - Content-Length 17 | //! - Expect 18 | //! - Transfer-Encoding 19 | //! 20 | //! The **Response** does not have a public interface for adding headers, but whenever 21 | //! a write to the **Body** is made, the headers **ContentLength** and **MediaType** 22 | //! are automatically updated. 23 | //! 24 | //! ### Media Types 25 | //! The supported media types are: 26 | //! - text/plain 27 | //! - application/json 28 | //! 29 | //! ## Supported Methods 30 | //! The supported HTTP Methods are: 31 | //! - GET 32 | //! - PUT 33 | //! - PATCH 34 | //! 35 | //! ## Supported Status Codes 36 | //! The supported status codes are: 37 | //! 38 | //! - Continue - 100 39 | //! - OK - 200 40 | //! - No Content - 204 41 | //! - Bad Request - 400 42 | //! - Not Found - 404 43 | //! - Internal Server Error - 500 44 | //! - Not Implemented - 501 45 | //! 46 | //! ## Example for parsing an HTTP Request from a slice 47 | //! ``` 48 | //! use micro_http::{Request, Version}; 49 | //! 50 | //! let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\r\n"; 51 | //! let http_request = Request::try_from(request_bytes, None).unwrap(); 52 | //! assert_eq!(http_request.http_version(), Version::Http10); 53 | //! assert_eq!(http_request.uri().get_abs_path(), "/home"); 54 | //! ``` 55 | //! 56 | //! ## Example for creating an HTTP Response 57 | //! ``` 58 | //! use micro_http::{Body, MediaType, Response, StatusCode, Version}; 59 | //! 60 | //! let mut response = Response::new(Version::Http10, StatusCode::OK); 61 | //! let body = String::from("This is a test"); 62 | //! response.set_body(Body::new(body.clone())); 63 | //! response.set_content_type(MediaType::PlainText); 64 | //! 65 | //! assert!(response.status() == StatusCode::OK); 66 | //! assert_eq!(response.body().unwrap(), Body::new(body)); 67 | //! assert_eq!(response.http_version(), Version::Http10); 68 | //! 69 | //! let mut response_buf: [u8; 126] = [0; 126]; 70 | //! assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 71 | //! ``` 72 | //! 73 | //! `HttpConnection` can be used for automatic data exchange and parsing when 74 | //! handling a client, but it only supports one stream. 75 | //! 76 | //! For handling multiple clients use `HttpServer`, which multiplexes `HttpConnection`s 77 | //! and offers an easy to use interface. The server can run in either blocking or 78 | //! non-blocking mode. Non-blocking is achieved by using `epoll` to make sure 79 | //! `requests` will never block when called. 80 | //! 81 | //! ## Example for using the server 82 | //! 83 | //! ``` 84 | //! use micro_http::{HttpServer, Response, StatusCode}; 85 | //! 86 | //! let path_to_socket = "/tmp/example.sock"; 87 | //! std::fs::remove_file(path_to_socket).unwrap_or_default(); 88 | //! 89 | //! // Start the server. 90 | //! let mut server = HttpServer::new(path_to_socket).unwrap(); 91 | //! server.start_server().unwrap(); 92 | //! 93 | //! // Connect a client to the server so it doesn't block in our example. 94 | //! let mut socket = std::os::unix::net::UnixStream::connect(path_to_socket).unwrap(); 95 | //! 96 | //! // Server loop processing requests. 97 | //! loop { 98 | //! for request in server.requests().unwrap() { 99 | //! let response = request.process(|request| { 100 | //! // Your code here. 101 | //! Response::new(request.http_version(), StatusCode::NoContent) 102 | //! }); 103 | //! server.respond(response); 104 | //! } 105 | //! // Break this example loop. 106 | //! break; 107 | //! } 108 | //! ``` 109 | 110 | mod common; 111 | mod connection; 112 | mod request; 113 | mod response; 114 | mod router; 115 | mod server; 116 | use crate::common::ascii; 117 | use crate::common::headers; 118 | 119 | pub use self::router::{EndpointHandler, HttpRoutes, RouteError}; 120 | pub use crate::common::headers::{Encoding, Headers, MediaType}; 121 | pub use crate::common::{Body, HttpHeaderError, Method, Version}; 122 | pub use crate::connection::{ConnectionError, HttpConnection}; 123 | pub use crate::request::{Request, RequestError}; 124 | pub use crate::response::{Response, ResponseHeaders, StatusCode}; 125 | pub use crate::server::{HttpServer, ServerError, ServerRequest, ServerResponse}; 126 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::fs::File; 5 | use std::str::from_utf8; 6 | 7 | use crate::common::ascii::{CR, CRLF_LEN, LF, SP}; 8 | 9 | pub use crate::common::RequestError; 10 | use crate::common::{Body, Method, Version}; 11 | use crate::headers::Headers; 12 | 13 | // This type represents the RequestLine raw parts: method, uri and version. 14 | type RequestLineParts<'a> = (&'a [u8], &'a [u8], &'a [u8]); 15 | 16 | /// Finds the first occurrence of `sequence` in the `bytes` slice. 17 | /// 18 | /// Returns the starting position of the `sequence` in `bytes` or `None` if the 19 | /// `sequence` is not found. 20 | pub(crate) fn find(bytes: &[u8], sequence: &[u8]) -> Option { 21 | bytes 22 | .windows(sequence.len()) 23 | .position(|window| window == sequence) 24 | } 25 | 26 | /// Wrapper over HTTP URIs. 27 | /// 28 | /// The `Uri` can not be used directly and it is only accessible from an HTTP Request. 29 | #[derive(Clone, Debug, PartialEq, Eq)] 30 | pub struct Uri { 31 | string: String, 32 | } 33 | 34 | impl Uri { 35 | fn new(slice: &str) -> Self { 36 | Self { 37 | string: String::from(slice), 38 | } 39 | } 40 | 41 | fn try_from(bytes: &[u8]) -> Result { 42 | if bytes.is_empty() { 43 | return Err(RequestError::InvalidUri("Empty URI not allowed.")); 44 | } 45 | let utf8_slice = 46 | from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?; 47 | Ok(Self::new(utf8_slice)) 48 | } 49 | 50 | /// Returns the absolute path of the `Uri`. 51 | /// 52 | /// URIs can be represented in absolute form or relative form. The absolute form includes 53 | /// the HTTP scheme, followed by the absolute path as follows: 54 | /// "http:" "//" host [ ":" port ] [ abs_path ] 55 | /// The relative URIs can be one of net_path | abs_path | rel_path. 56 | /// This method only handles absolute URIs and relative URIs specified by abs_path. 57 | /// The abs_path is expected to start with '/'. 58 | /// 59 | /// # Errors 60 | /// Returns an empty byte array when the host or the path are empty/invalid. 61 | pub fn get_abs_path(&self) -> &str { 62 | const HTTP_SCHEME_PREFIX: &str = "http://"; 63 | 64 | if self.string.starts_with(HTTP_SCHEME_PREFIX) { 65 | // Slice access is safe because we checked above that `self.string` size <= `HTTP_SCHEME_PREFIX.len()`. 66 | let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..]; 67 | if without_scheme.is_empty() { 68 | return ""; 69 | } 70 | // The host in this case includes the port and contains the bytes after http:// up to 71 | // the next '/'. 72 | match without_scheme.bytes().position(|byte| byte == b'/') { 73 | // Slice access is safe because `position` validates that `len` is a valid index. 74 | Some(len) => &without_scheme[len..], 75 | None => "", 76 | } 77 | } else { 78 | if self.string.starts_with('/') { 79 | return self.string.as_str(); 80 | } 81 | 82 | "" 83 | } 84 | } 85 | } 86 | 87 | /// Wrapper over an HTTP Request Line. 88 | #[derive(Debug, PartialEq, Eq)] 89 | pub struct RequestLine { 90 | method: Method, 91 | uri: Uri, 92 | http_version: Version, 93 | } 94 | 95 | impl RequestLine { 96 | fn parse_request_line( 97 | request_line: &[u8], 98 | ) -> std::result::Result { 99 | if let Some(method_end) = find(request_line, &[SP]) { 100 | // The slice access is safe because `find` validates that `method_end` < `request_line` size. 101 | let method = &request_line[..method_end]; 102 | 103 | // `uri_start` <= `request_line` size. 104 | let uri_start = method_end.checked_add(1).ok_or(RequestError::Overflow)?; 105 | 106 | // Slice access is safe because `uri_start` <= `request_line` size. 107 | // If `uri_start` == `request_line` size, then `uri_and_version` will be an empty slice. 108 | let uri_and_version = &request_line[uri_start..]; 109 | 110 | if let Some(uri_end) = find(uri_and_version, &[SP]) { 111 | // Slice access is safe because `find` validates that `uri_end` < `uri_and_version` size. 112 | let uri = &uri_and_version[..uri_end]; 113 | 114 | // `version_start` <= `uri_and_version` size. 115 | let version_start = uri_end.checked_add(1).ok_or(RequestError::Overflow)?; 116 | 117 | // Slice access is safe because `version_start` <= `uri_and_version` size. 118 | let version = &uri_and_version[version_start..]; 119 | 120 | return Ok((method, uri, version)); 121 | } 122 | } 123 | 124 | // Request Line can be valid only if it contains the method, uri and version separated with SP. 125 | Err(RequestError::InvalidRequest) 126 | } 127 | 128 | /// Tries to parse a byte stream in a request line. Fails if the request line is malformed. 129 | /// 130 | /// # Errors 131 | /// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported. 132 | /// `InvalidHttpVersion` is returned if the specified HTTP version is unsupported. 133 | /// `InvalidUri` is returned if the specified Uri is not valid. 134 | pub fn try_from(request_line: &[u8]) -> Result { 135 | let (method, uri, version) = Self::parse_request_line(request_line)?; 136 | 137 | Ok(Self { 138 | method: Method::try_from(method)?, 139 | uri: Uri::try_from(uri)?, 140 | http_version: Version::try_from(version)?, 141 | }) 142 | } 143 | 144 | // Returns the minimum length of a valid request. The request must contain 145 | // the method (GET), the URI (minimum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and 146 | // 2 separators (SP). 147 | fn min_len() -> usize { 148 | // Addition is safe because these are small constants. 149 | Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2 150 | } 151 | } 152 | 153 | /// Wrapper over an HTTP Request. 154 | #[derive(Debug)] 155 | pub struct Request { 156 | /// The request line of the request. 157 | pub request_line: RequestLine, 158 | /// The headers of the request. 159 | pub headers: Headers, 160 | /// The body of the request. 161 | pub body: Option, 162 | /// The optional files associated with the request. 163 | pub files: Vec, 164 | } 165 | 166 | impl Request { 167 | /// Parses a byte slice into a HTTP Request. 168 | /// 169 | /// The byte slice is expected to have the following format:
170 | /// * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory
171 | /// * Request Headers " CRLF"- Optional
172 | /// * Empty Line "CRLF"
173 | /// * Entity Body - Optional
174 | /// The request headers and the entity body are not parsed and None is returned because 175 | /// these are not used by the MMDS server. 176 | /// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0 177 | /// or HTTP/1.1. 178 | /// 179 | /// # Errors 180 | /// The function returns InvalidRequest when parsing the byte stream fails. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ``` 185 | /// use micro_http::Request; 186 | /// 187 | /// let max_request_len = 2000; 188 | /// let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\r\n"; 189 | /// let http_request = Request::try_from(request_bytes, Some(max_request_len)).unwrap(); 190 | /// ``` 191 | pub fn try_from(byte_stream: &[u8], max_len: Option) -> Result { 192 | // If a size limit is provided, verify the request length does not exceed it. 193 | if let Some(limit) = max_len { 194 | if byte_stream.len() >= limit { 195 | return Err(RequestError::InvalidRequest); 196 | } 197 | } 198 | 199 | // The first line of the request is the Request Line. The line ending is CR LF. 200 | let request_line_end = match find(byte_stream, &[CR, LF]) { 201 | Some(len) => len, 202 | // If no CR LF is found in the stream, the request format is invalid. 203 | None => return Err(RequestError::InvalidRequest), 204 | }; 205 | 206 | // Slice access is safe because `find` validates that `request_line_end` < `byte_stream` size. 207 | let request_line_bytes = &byte_stream[..request_line_end]; 208 | if request_line_bytes.len() < RequestLine::min_len() { 209 | return Err(RequestError::InvalidRequest); 210 | } 211 | 212 | let request_line = RequestLine::try_from(request_line_bytes)?; 213 | 214 | // Find the next CR LF CR LF sequence in our buffer starting at the end on the Request 215 | // Line, including the trailing CR LF previously found. 216 | match find(&byte_stream[request_line_end..], &[CR, LF, CR, LF]) { 217 | // If we have found a CR LF CR LF at the end of the Request Line, the request 218 | // is complete. 219 | Some(0) => Ok(Self { 220 | request_line, 221 | headers: Headers::default(), 222 | body: None, 223 | files: Vec::new(), 224 | }), 225 | Some(headers_end) => { 226 | // Parse the request headers. 227 | // Start by removing the leading CR LF from them. 228 | // The addition is safe because `find()` guarantees that `request_line_end` 229 | // precedes 2 `CRLF` sequences. 230 | let headers_start = request_line_end + CRLF_LEN; 231 | // Slice access is safe because starting from `request_line_end` there are at least two CRLF 232 | // (enforced by `find` at the start of this method). 233 | let headers_and_body = &byte_stream[headers_start..]; 234 | // Because we advanced the start with CRLF_LEN, we now have to subtract CRLF_LEN 235 | // from the end in order to keep the same window. 236 | // Underflow is not possible here because `byte_stream[request_line_end..]` starts with CR LF, 237 | // so `headers_end` can be either zero (this case is treated separately in the first match arm) 238 | // or >= 3 (current case). 239 | let headers_end = headers_end - CRLF_LEN; 240 | // Slice access is safe because `headers_end` is checked above 241 | // (`find` gives a valid position, and subtracting 2 can't underflow). 242 | let headers = Headers::try_from(&headers_and_body[..headers_end])?; 243 | 244 | // Parse the body of the request. 245 | // Firstly check if we have a body. 246 | let body = match headers.content_length() { 247 | 0 => { 248 | // No request body. 249 | None 250 | } 251 | content_length => { 252 | if request_line.method == Method::Get { 253 | return Err(RequestError::InvalidRequest); 254 | } 255 | // Multiplication is safe because `CRLF_LEN` is a small constant. 256 | // Addition is also safe because `headers_end` started out as the result 257 | // of `find(, CRLFCRLF)`, then `CRLF_LEN` was subtracted from it. 258 | let crlf_end = headers_end + 2 * CRLF_LEN; 259 | // This can't underflow because `headers_and_body.len()` >= `crlf_end`. 260 | let body_len = headers_and_body.len() - crlf_end; 261 | // Headers suggest we have a body, but the buffer is shorter than the specified 262 | // content length. 263 | if body_len < content_length as usize { 264 | return Err(RequestError::InvalidRequest); 265 | } 266 | // Slice access is safe because `crlf_end` is the index after two CRLF 267 | // (it is <= `headers_and_body` size). 268 | let body_as_bytes = &headers_and_body[crlf_end..]; 269 | // If the actual length of the body is different than the `Content-Length` value 270 | // in the headers, then this request is invalid. 271 | if body_as_bytes.len() == content_length as usize { 272 | Some(Body::new(body_as_bytes)) 273 | } else { 274 | return Err(RequestError::InvalidRequest); 275 | } 276 | } 277 | }; 278 | 279 | Ok(Self { 280 | request_line, 281 | headers, 282 | body, 283 | files: Vec::new(), 284 | }) 285 | } 286 | // If we can't find a CR LF CR LF even though the request should have headers 287 | // the request format is invalid. 288 | None => Err(RequestError::InvalidRequest), 289 | } 290 | } 291 | 292 | /// Returns the `Uri` from the parsed `Request`. 293 | /// 294 | /// The return value can be used to get the absolute path of the URI. 295 | pub fn uri(&self) -> &Uri { 296 | &self.request_line.uri 297 | } 298 | 299 | /// Returns the HTTP `Version` of the `Request`. 300 | pub fn http_version(&self) -> Version { 301 | self.request_line.http_version 302 | } 303 | 304 | /// Returns the HTTP `Method` of the `Request`. 305 | pub fn method(&self) -> Method { 306 | self.request_line.method 307 | } 308 | } 309 | 310 | #[cfg(test)] 311 | mod tests { 312 | use super::*; 313 | 314 | impl PartialEq for Request { 315 | fn eq(&self, other: &Self) -> bool { 316 | // Ignore the other fields of Request for now because they are not used. 317 | self.request_line == other.request_line 318 | && self.headers.content_length() == other.headers.content_length() 319 | && self.headers.expect() == other.headers.expect() 320 | && self.headers.chunked() == other.headers.chunked() 321 | } 322 | } 323 | 324 | impl RequestLine { 325 | pub fn new(method: Method, uri: &str, http_version: Version) -> Self { 326 | Self { 327 | method, 328 | uri: Uri::new(uri), 329 | http_version, 330 | } 331 | } 332 | } 333 | 334 | #[test] 335 | fn test_uri() { 336 | for tc in &vec![ 337 | ("http://localhost/home", "/home"), 338 | ("http://localhost:8080/home", "/home"), 339 | ("http://localhost/home/sub", "/home/sub"), 340 | ("/home", "/home"), 341 | ("home", ""), 342 | ("http://", ""), 343 | ("http://192.168.0.0", ""), 344 | ] { 345 | assert_eq!(Uri::new(tc.0).get_abs_path(), tc.1); 346 | } 347 | } 348 | 349 | #[test] 350 | fn test_find() { 351 | let bytes: &[u8; 13] = b"abcacrgbabsjl"; 352 | 353 | for tc in &vec![ 354 | ("ac", Some(3)), 355 | ("rgb", Some(5)), 356 | ("ab", Some(0)), 357 | ("l", Some(12)), 358 | ("abcacrgbabsjl", Some(0)), 359 | ("jle", None), 360 | ("asdkjhasjhdjhgsadg", None), 361 | ] { 362 | assert_eq!(find(&bytes[..], tc.0.as_bytes()), tc.1); 363 | } 364 | } 365 | 366 | #[test] 367 | fn test_into_request_line() { 368 | let expected_request_line = RequestLine { 369 | http_version: Version::Http10, 370 | method: Method::Get, 371 | uri: Uri::new("http://localhost/home"), 372 | }; 373 | 374 | let request_line = b"GET http://localhost/home HTTP/1.0"; 375 | assert_eq!( 376 | RequestLine::try_from(request_line).unwrap(), 377 | expected_request_line 378 | ); 379 | 380 | let expected_request_line = RequestLine { 381 | http_version: Version::Http11, 382 | method: Method::Get, 383 | uri: Uri::new("http://localhost/home"), 384 | }; 385 | 386 | // Happy case with request line ending in CRLF. 387 | let request_line = b"GET http://localhost/home HTTP/1.1"; 388 | assert_eq!( 389 | RequestLine::try_from(request_line).unwrap(), 390 | expected_request_line 391 | ); 392 | 393 | // Happy case with request line ending in LF instead of CRLF. 394 | let request_line = b"GET http://localhost/home HTTP/1.1"; 395 | assert_eq!( 396 | RequestLine::try_from(request_line).unwrap(), 397 | expected_request_line 398 | ); 399 | 400 | // Test for invalid request missing the separator. 401 | let request_line = b"GET"; 402 | assert_eq!( 403 | RequestLine::try_from(request_line).unwrap_err(), 404 | RequestError::InvalidRequest 405 | ); 406 | 407 | // Test for invalid method. 408 | let request_line = b"POST http://localhost/home HTTP/1.0"; 409 | assert_eq!( 410 | RequestLine::try_from(request_line).unwrap_err(), 411 | RequestError::InvalidHttpMethod("Unsupported HTTP method.") 412 | ); 413 | 414 | // Test for invalid uri. 415 | let request_line = b"GET HTTP/1.0"; 416 | assert_eq!( 417 | RequestLine::try_from(request_line).unwrap_err(), 418 | RequestError::InvalidUri("Empty URI not allowed.") 419 | ); 420 | 421 | // Test for invalid HTTP version. 422 | let request_line = b"GET http://localhost/home HTTP/2.0"; 423 | assert_eq!( 424 | RequestLine::try_from(request_line).unwrap_err(), 425 | RequestError::InvalidHttpVersion("Unsupported HTTP version.") 426 | ); 427 | 428 | // Test for invalid format with no method, uri or version. 429 | let request_line = b"nothing"; 430 | assert_eq!( 431 | RequestLine::try_from(request_line).unwrap_err(), 432 | RequestError::InvalidRequest 433 | ); 434 | 435 | // Test for invalid format with no version. 436 | let request_line = b"GET /"; 437 | assert_eq!( 438 | RequestLine::try_from(request_line).unwrap_err(), 439 | RequestError::InvalidRequest 440 | ); 441 | } 442 | 443 | #[test] 444 | fn test_into_request() { 445 | let expected_request = Request { 446 | request_line: RequestLine { 447 | http_version: Version::Http10, 448 | method: Method::Get, 449 | uri: Uri::new("http://localhost/home"), 450 | }, 451 | body: None, 452 | files: Vec::new(), 453 | headers: Headers::default(), 454 | }; 455 | let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\ 456 | Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n"; 457 | let request = Request::try_from(request_bytes, None).unwrap(); 458 | assert_eq!(request, expected_request); 459 | assert_eq!(request.uri(), &Uri::new("http://localhost/home")); 460 | assert_eq!(request.http_version(), Version::Http10); 461 | assert!(request.body.is_none()); 462 | 463 | // Test for invalid Request (missing CR LF). 464 | let request_bytes = b"GET / HTTP/1.1"; 465 | assert_eq!( 466 | Request::try_from(request_bytes, None).unwrap_err(), 467 | RequestError::InvalidRequest 468 | ); 469 | 470 | // Test for invalid Request (length is less than minimum). 471 | let request_bytes = b"GET"; 472 | assert_eq!( 473 | Request::try_from(request_bytes, None).unwrap_err(), 474 | RequestError::InvalidRequest 475 | ); 476 | 477 | // Test for invalid Request (`GET` requests should have no body). 478 | let request_bytes = b"GET /machine-config HTTP/1.1\r\n\ 479 | Content-Length: 13\r\n\ 480 | Content-Type: application/json\r\n\r\nwhatever body"; 481 | assert_eq!( 482 | Request::try_from(request_bytes, None).unwrap_err(), 483 | RequestError::InvalidRequest 484 | ); 485 | 486 | // Test for request larger than maximum len provided. 487 | let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\ 488 | Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n"; 489 | assert_eq!( 490 | Request::try_from(request_bytes, Some(20)).unwrap_err(), 491 | RequestError::InvalidRequest 492 | ); 493 | 494 | // Test request smaller than maximum len provided is ok. 495 | let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\ 496 | Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n"; 497 | assert!(Request::try_from(request_bytes, Some(500)).is_ok()); 498 | 499 | // Test for a request with the headers we are looking for. 500 | let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\ 501 | Expect: 100-continue\r\n\ 502 | Transfer-Encoding: chunked\r\n\ 503 | Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody"; 504 | let request = Request::try_from(request_bytes, None).unwrap(); 505 | assert_eq!(request.uri(), &Uri::new("http://localhost/home")); 506 | assert_eq!(request.http_version(), Version::Http11); 507 | assert_eq!(request.method(), Method::Patch); 508 | assert!(request.headers.chunked()); 509 | assert!(request.headers.expect()); 510 | assert_eq!(request.headers.content_length(), 26); 511 | assert_eq!( 512 | request.body.unwrap().body, 513 | String::from("this is not\n\r\na json \nbody") 514 | .as_bytes() 515 | .to_vec() 516 | ); 517 | 518 | // Test for an invalid request format. 519 | Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n", None).unwrap_err(); 520 | 521 | // Test for an invalid encoding. 522 | let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\ 523 | Expect: 100-continue\r\n\ 524 | Transfer-Encoding: identity; q=0\r\n\ 525 | Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody"; 526 | 527 | assert!(Request::try_from(request_bytes, None).is_ok()); 528 | 529 | // Test for an invalid content length. 530 | let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\ 531 | Content-Length: 5000\r\n\r\nthis is a short body"; 532 | let request = Request::try_from(request_bytes, None).unwrap_err(); 533 | assert_eq!(request, RequestError::InvalidRequest); 534 | 535 | // Test for a request without a body and an optional header. 536 | let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\ 537 | Accept-Encoding: gzip\r\n\r\n"; 538 | let request = Request::try_from(request_bytes, None).unwrap(); 539 | assert_eq!(request.uri(), &Uri::new("http://localhost/")); 540 | assert_eq!(request.http_version(), Version::Http10); 541 | assert_eq!(request.method(), Method::Get); 542 | assert!(!request.headers.chunked()); 543 | assert!(!request.headers.expect()); 544 | assert_eq!(request.headers.content_length(), 0); 545 | assert!(request.body.is_none()); 546 | 547 | let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\ 548 | Accept-Encoding: identity;q=0\r\n\r\n"; 549 | let request = Request::try_from(request_bytes, None); 550 | assert_eq!( 551 | request.unwrap_err(), 552 | RequestError::HeaderError(crate::HttpHeaderError::InvalidValue( 553 | "Accept-Encoding".to_string(), 554 | "identity;q=0".to_string() 555 | )) 556 | ); 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::io::{Error as WriteError, Write}; 5 | 6 | use crate::ascii::{COLON, CR, LF, SP}; 7 | use crate::common::{Body, Version}; 8 | use crate::headers::{Header, MediaType}; 9 | use crate::Method; 10 | 11 | /// Wrapper over a response status code. 12 | /// 13 | /// The status code is defined as specified in the 14 | /// [RFC](https://tools.ietf.org/html/rfc7231#section-6). 15 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 16 | pub enum StatusCode { 17 | /// 100, Continue 18 | Continue, 19 | /// 200, OK 20 | OK, 21 | /// 204, No Content 22 | NoContent, 23 | /// 400, Bad Request 24 | BadRequest, 25 | /// 401, Unauthorized 26 | Unauthorized, 27 | /// 404, Not Found 28 | NotFound, 29 | /// 405, Method Not Allowed 30 | MethodNotAllowed, 31 | /// 413, Payload Too Large 32 | PayloadTooLarge, 33 | /// 429, Too Many Requests 34 | TooManyRequests, 35 | /// 500, Internal Server Error 36 | InternalServerError, 37 | /// 501, Not Implemented 38 | NotImplemented, 39 | /// 503, Service Unavailable 40 | ServiceUnavailable, 41 | } 42 | 43 | impl StatusCode { 44 | /// Returns the status code as bytes. 45 | pub fn raw(self) -> &'static [u8; 3] { 46 | match self { 47 | Self::Continue => b"100", 48 | Self::OK => b"200", 49 | Self::NoContent => b"204", 50 | Self::BadRequest => b"400", 51 | Self::Unauthorized => b"401", 52 | Self::NotFound => b"404", 53 | Self::MethodNotAllowed => b"405", 54 | Self::PayloadTooLarge => b"413", 55 | Self::TooManyRequests => b"429", 56 | Self::InternalServerError => b"500", 57 | Self::NotImplemented => b"501", 58 | Self::ServiceUnavailable => b"503", 59 | } 60 | } 61 | } 62 | 63 | #[derive(Debug, PartialEq)] 64 | struct StatusLine { 65 | http_version: Version, 66 | status_code: StatusCode, 67 | } 68 | 69 | impl StatusLine { 70 | fn new(http_version: Version, status_code: StatusCode) -> Self { 71 | Self { 72 | http_version, 73 | status_code, 74 | } 75 | } 76 | 77 | fn write_all(&self, mut buf: T) -> Result<(), WriteError> { 78 | buf.write_all(self.http_version.raw())?; 79 | buf.write_all(&[SP])?; 80 | buf.write_all(self.status_code.raw())?; 81 | buf.write_all(&[SP, CR, LF])?; 82 | 83 | Ok(()) 84 | } 85 | } 86 | 87 | /// Wrapper over the list of headers associated with a HTTP Response. 88 | /// When creating a ResponseHeaders object, the content type is initialized to `text/plain`. 89 | /// The content type can be updated with a call to `set_content_type`. 90 | #[derive(Debug, PartialEq, Eq)] 91 | pub struct ResponseHeaders { 92 | content_length: Option, 93 | content_type: MediaType, 94 | deprecation: bool, 95 | server: String, 96 | allow: Vec, 97 | accept_encoding: bool, 98 | } 99 | 100 | impl Default for ResponseHeaders { 101 | fn default() -> Self { 102 | Self { 103 | content_length: Default::default(), 104 | content_type: Default::default(), 105 | deprecation: false, 106 | server: String::from("Firecracker API"), 107 | allow: Vec::new(), 108 | accept_encoding: false, 109 | } 110 | } 111 | } 112 | 113 | impl ResponseHeaders { 114 | // The logic pertaining to `Allow` header writing. 115 | fn write_allow_header(&self, buf: &mut T) -> Result<(), WriteError> { 116 | if self.allow.is_empty() { 117 | return Ok(()); 118 | } 119 | 120 | buf.write_all(b"Allow: ")?; 121 | 122 | let delimitator = b", "; 123 | for (idx, method) in self.allow.iter().enumerate() { 124 | buf.write_all(method.raw())?; 125 | // We check above that `self.allow` is not empty. 126 | if idx < self.allow.len() - 1 { 127 | buf.write_all(delimitator)?; 128 | } 129 | } 130 | 131 | buf.write_all(&[CR, LF]) 132 | } 133 | 134 | // The logic pertaining to `Deprecation` header writing. 135 | fn write_deprecation_header(&self, buf: &mut T) -> Result<(), WriteError> { 136 | if !self.deprecation { 137 | return Ok(()); 138 | } 139 | 140 | buf.write_all(b"Deprecation: true")?; 141 | buf.write_all(&[CR, LF]) 142 | } 143 | 144 | /// Writes the headers to `buf` using the HTTP specification. 145 | pub fn write_all(&self, buf: &mut T) -> Result<(), WriteError> { 146 | buf.write_all(Header::Server.raw())?; 147 | buf.write_all(&[COLON, SP])?; 148 | buf.write_all(self.server.as_bytes())?; 149 | buf.write_all(&[CR, LF])?; 150 | 151 | buf.write_all(b"Connection: keep-alive")?; 152 | buf.write_all(&[CR, LF])?; 153 | 154 | self.write_allow_header(buf)?; 155 | self.write_deprecation_header(buf)?; 156 | 157 | if let Some(content_length) = self.content_length { 158 | buf.write_all(Header::ContentType.raw())?; 159 | buf.write_all(&[COLON, SP])?; 160 | buf.write_all(self.content_type.as_str().as_bytes())?; 161 | buf.write_all(&[CR, LF])?; 162 | 163 | buf.write_all(Header::ContentLength.raw())?; 164 | buf.write_all(&[COLON, SP])?; 165 | buf.write_all(content_length.to_string().as_bytes())?; 166 | buf.write_all(&[CR, LF])?; 167 | 168 | if self.accept_encoding { 169 | buf.write_all(Header::AcceptEncoding.raw())?; 170 | buf.write_all(&[COLON, SP])?; 171 | buf.write_all(b"identity")?; 172 | buf.write_all(&[CR, LF])?; 173 | } 174 | } 175 | 176 | buf.write_all(&[CR, LF]) 177 | } 178 | 179 | /// Sets the content length to be written in the HTTP response. 180 | pub fn set_content_length(&mut self, content_length: Option) { 181 | self.content_length = content_length; 182 | } 183 | 184 | /// Sets the HTTP response header server. 185 | pub fn set_server(&mut self, server: &str) { 186 | self.server = String::from(server); 187 | } 188 | 189 | /// Sets the content type to be written in the HTTP response. 190 | pub fn set_content_type(&mut self, content_type: MediaType) { 191 | self.content_type = content_type; 192 | } 193 | 194 | /// Sets the `Deprecation` header to be written in the HTTP response. 195 | /// https://tools.ietf.org/id/draft-dalal-deprecation-header-03.html 196 | #[allow(unused)] 197 | pub fn set_deprecation(&mut self) { 198 | self.deprecation = true; 199 | } 200 | 201 | /// Sets the encoding type to be written in the HTTP response. 202 | #[allow(unused)] 203 | pub fn set_encoding(&mut self) { 204 | self.accept_encoding = true; 205 | } 206 | } 207 | 208 | /// Wrapper over an HTTP Response. 209 | /// 210 | /// The Response is created using a `Version` and a `StatusCode`. When creating a Response object, 211 | /// the body is initialized to `None` and the header is initialized with the `default` value. The body 212 | /// can be updated with a call to `set_body`. The header can be updated with `set_content_type` and 213 | /// `set_server`. 214 | #[derive(Debug, PartialEq)] 215 | pub struct Response { 216 | status_line: StatusLine, 217 | headers: ResponseHeaders, 218 | body: Option, 219 | } 220 | 221 | impl Response { 222 | /// Creates a new HTTP `Response` with an empty body. 223 | /// 224 | /// Although there are several cases where Content-Length field must not 225 | /// be sent, micro-http omits Content-Length field when the response 226 | /// status code is 1XX or 204. If needed, users can remove it by calling 227 | /// `set_content_length(None)`. 228 | /// 229 | /// https://datatracker.ietf.org/doc/html/rfc9110#name-content-length 230 | /// > A server MAY send a Content-Length header field in a response to a 231 | /// > HEAD request (Section 9.3.2); a server MUST NOT send Content-Length 232 | /// > in such a response unless its field value equals the decimal number 233 | /// > of octets that would have been sent in the content of a response if 234 | /// > the same request had used the GET method. 235 | /// > 236 | /// > A server MAY send a Content-Length header field in a 304 (Not 237 | /// > Modified) response to a conditional GET request (Section 15.4.5); a 238 | /// > server MUST NOT send Content-Length in such a response unless its 239 | /// > field value equals the decimal number of octets that would have been 240 | /// > sent in the content of a 200 (OK) response to the same request. 241 | /// > 242 | /// > A server MUST NOT send a Content-Length header field in any response 243 | /// > with a status code of 1xx (Informational) or 204 (No Content). A 244 | /// > server MUST NOT send a Content-Length header field in any 2xx 245 | /// > (Successful) response to a CONNECT request (Section 9.3.6). 246 | pub fn new(http_version: Version, status_code: StatusCode) -> Self { 247 | Self { 248 | status_line: StatusLine::new(http_version, status_code), 249 | headers: ResponseHeaders { 250 | content_length: match status_code { 251 | StatusCode::Continue | StatusCode::NoContent => None, 252 | _ => Some(0), 253 | }, 254 | ..Default::default() 255 | }, 256 | body: Default::default(), 257 | } 258 | } 259 | 260 | /// Updates the body of the `Response`. 261 | /// 262 | /// This function has side effects because it also updates the headers: 263 | /// - `ContentLength`: this is set to the length of the specified body. 264 | pub fn set_body(&mut self, body: Body) { 265 | self.headers.set_content_length(Some(body.len() as i32)); 266 | self.body = Some(body); 267 | } 268 | 269 | /// Updates the content length of the `Response`. 270 | /// 271 | /// It is recommended to use this method only when removing Content-Length 272 | /// field if the response status is not 1XX or 204. 273 | pub fn set_content_length(&mut self, content_length: Option) { 274 | self.headers.set_content_length(content_length); 275 | } 276 | 277 | /// Updates the content type of the `Response`. 278 | pub fn set_content_type(&mut self, content_type: MediaType) { 279 | self.headers.set_content_type(content_type); 280 | } 281 | 282 | /// Marks the `Response` as deprecated. 283 | pub fn set_deprecation(&mut self) { 284 | self.headers.set_deprecation(); 285 | } 286 | 287 | /// Updates the encoding type of `Response`. 288 | pub fn set_encoding(&mut self) { 289 | self.headers.set_encoding(); 290 | } 291 | 292 | /// Sets the HTTP response server. 293 | pub fn set_server(&mut self, server: &str) { 294 | self.headers.set_server(server); 295 | } 296 | 297 | /// Sets the HTTP allowed methods. 298 | pub fn set_allow(&mut self, methods: Vec) { 299 | self.headers.allow = methods; 300 | } 301 | 302 | /// Allows a specific HTTP method. 303 | pub fn allow_method(&mut self, method: Method) { 304 | self.headers.allow.push(method); 305 | } 306 | 307 | fn write_body(&self, mut buf: T) -> Result<(), WriteError> { 308 | if let Some(ref body) = self.body { 309 | buf.write_all(body.raw())?; 310 | } 311 | Ok(()) 312 | } 313 | 314 | /// Writes the content of the `Response` to the specified `buf`. 315 | /// 316 | /// # Errors 317 | /// Returns an error when the buffer is not large enough. 318 | pub fn write_all(&self, mut buf: &mut T) -> Result<(), WriteError> { 319 | self.status_line.write_all(&mut buf)?; 320 | self.headers.write_all(&mut buf)?; 321 | self.write_body(&mut buf)?; 322 | 323 | Ok(()) 324 | } 325 | 326 | /// Returns the Status Code of the Response. 327 | pub fn status(&self) -> StatusCode { 328 | self.status_line.status_code 329 | } 330 | 331 | /// Returns the Body of the response. If the response does not have a body, 332 | /// it returns None. 333 | pub fn body(&self) -> Option { 334 | self.body.clone() 335 | } 336 | 337 | /// Returns the Content Length of the response. 338 | pub fn content_length(&self) -> i32 { 339 | self.headers.content_length.unwrap_or(0) 340 | } 341 | 342 | /// Returns the Content Type of the response. 343 | pub fn content_type(&self) -> MediaType { 344 | self.headers.content_type 345 | } 346 | 347 | /// Returns the deprecation status of the response. 348 | pub fn deprecation(&self) -> bool { 349 | self.headers.deprecation 350 | } 351 | 352 | /// Returns the HTTP Version of the response. 353 | pub fn http_version(&self) -> Version { 354 | self.status_line.http_version 355 | } 356 | 357 | /// Returns the allowed HTTP methods. 358 | pub fn allow(&self) -> Vec { 359 | self.headers.allow.clone() 360 | } 361 | } 362 | 363 | #[cfg(test)] 364 | mod tests { 365 | use super::*; 366 | 367 | #[test] 368 | fn test_write_response() { 369 | let mut response = Response::new(Version::Http10, StatusCode::OK); 370 | let body = "This is a test"; 371 | response.set_body(Body::new(body)); 372 | response.set_content_type(MediaType::PlainText); 373 | response.set_encoding(); 374 | 375 | assert_eq!(response.status(), StatusCode::OK); 376 | assert_eq!(response.body().unwrap(), Body::new(body)); 377 | assert_eq!(response.http_version(), Version::Http10); 378 | assert_eq!(response.content_length(), 14); 379 | assert_eq!(response.content_type(), MediaType::PlainText); 380 | 381 | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ 382 | Server: Firecracker API\r\n\ 383 | Connection: keep-alive\r\n\ 384 | Content-Type: text/plain\r\n\ 385 | Content-Length: 14\r\n\ 386 | Accept-Encoding: identity\r\n\r\n\ 387 | This is a test"; 388 | 389 | let mut response_buf: [u8; 153] = [0; 153]; 390 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 391 | assert_eq!(response_buf.as_ref(), expected_response); 392 | 393 | // Test response `Allow` header. 394 | let mut response = Response::new(Version::Http10, StatusCode::OK); 395 | let allowed_methods = vec![Method::Get, Method::Patch, Method::Put]; 396 | response.set_allow(allowed_methods.clone()); 397 | assert_eq!(response.allow(), allowed_methods); 398 | 399 | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ 400 | Server: Firecracker API\r\n\ 401 | Connection: keep-alive\r\n\ 402 | Allow: GET, PATCH, PUT\r\n\ 403 | Content-Type: application/json\r\n\ 404 | Content-Length: 0\r\n\r\n"; 405 | let mut response_buf: [u8; 141] = [0; 141]; 406 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 407 | assert_eq!(response_buf.as_ref(), expected_response); 408 | 409 | // Test write failed. 410 | let mut response_buf: [u8; 1] = [0; 1]; 411 | assert!(response.write_all(&mut response_buf.as_mut()).is_err()); 412 | } 413 | 414 | #[test] 415 | fn test_set_server() { 416 | let mut response = Response::new(Version::Http10, StatusCode::OK); 417 | let body = "This is a test"; 418 | let server = "rust-vmm API"; 419 | response.set_body(Body::new(body)); 420 | response.set_content_type(MediaType::PlainText); 421 | response.set_server(server); 422 | 423 | assert_eq!(response.status(), StatusCode::OK); 424 | assert_eq!(response.body().unwrap(), Body::new(body)); 425 | assert_eq!(response.http_version(), Version::Http10); 426 | assert_eq!(response.content_length(), 14); 427 | assert_eq!(response.content_type(), MediaType::PlainText); 428 | 429 | let expected_response = format!( 430 | "HTTP/1.0 200 \r\n\ 431 | Server: {}\r\n\ 432 | Connection: keep-alive\r\n\ 433 | Content-Type: text/plain\r\n\ 434 | Content-Length: 14\r\n\r\n\ 435 | This is a test", 436 | server 437 | ); 438 | 439 | let mut response_buf: [u8; 123] = [0; 123]; 440 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 441 | assert!(response_buf.as_ref() == expected_response.as_bytes()); 442 | } 443 | 444 | #[test] 445 | fn test_status_code() { 446 | assert_eq!(StatusCode::Continue.raw(), b"100"); 447 | assert_eq!(StatusCode::OK.raw(), b"200"); 448 | assert_eq!(StatusCode::NoContent.raw(), b"204"); 449 | assert_eq!(StatusCode::BadRequest.raw(), b"400"); 450 | assert_eq!(StatusCode::Unauthorized.raw(), b"401"); 451 | assert_eq!(StatusCode::NotFound.raw(), b"404"); 452 | assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405"); 453 | assert_eq!(StatusCode::PayloadTooLarge.raw(), b"413"); 454 | assert_eq!(StatusCode::TooManyRequests.raw(), b"429"); 455 | assert_eq!(StatusCode::InternalServerError.raw(), b"500"); 456 | assert_eq!(StatusCode::NotImplemented.raw(), b"501"); 457 | assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503"); 458 | } 459 | 460 | #[test] 461 | fn test_allow_method() { 462 | let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); 463 | response.allow_method(Method::Get); 464 | response.allow_method(Method::Put); 465 | assert_eq!(response.allow(), vec![Method::Get, Method::Put]); 466 | } 467 | 468 | #[test] 469 | fn test_deprecation() { 470 | // Test a deprecated response with body. 471 | let mut response = Response::new(Version::Http10, StatusCode::OK); 472 | let body = "This is a test"; 473 | response.set_body(Body::new(body)); 474 | response.set_content_type(MediaType::PlainText); 475 | response.set_encoding(); 476 | response.set_deprecation(); 477 | 478 | assert_eq!(response.status(), StatusCode::OK); 479 | assert_eq!(response.body().unwrap(), Body::new(body)); 480 | assert_eq!(response.http_version(), Version::Http10); 481 | assert_eq!(response.content_length(), 14); 482 | assert_eq!(response.content_type(), MediaType::PlainText); 483 | assert!(response.deprecation()); 484 | 485 | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ 486 | Server: Firecracker API\r\n\ 487 | Connection: keep-alive\r\n\ 488 | Deprecation: true\r\n\ 489 | Content-Type: text/plain\r\n\ 490 | Content-Length: 14\r\n\ 491 | Accept-Encoding: identity\r\n\r\n\ 492 | This is a test"; 493 | 494 | let mut response_buf: [u8; 172] = [0; 172]; 495 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 496 | assert_eq!(response_buf.as_ref(), expected_response); 497 | 498 | // Test a deprecated response without a body. 499 | let mut response = Response::new(Version::Http10, StatusCode::NoContent); 500 | response.set_deprecation(); 501 | 502 | assert_eq!(response.status(), StatusCode::NoContent); 503 | assert_eq!(response.http_version(), Version::Http10); 504 | assert!(response.deprecation()); 505 | 506 | let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\ 507 | Server: Firecracker API\r\n\ 508 | Connection: keep-alive\r\n\ 509 | Deprecation: true\r\n\r\n"; 510 | 511 | let mut response_buf: [u8; 85] = [0; 85]; 512 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 513 | assert_eq!(response_buf.as_ref(), expected_response); 514 | } 515 | 516 | #[test] 517 | fn test_equal() { 518 | let response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); 519 | let another_response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); 520 | assert_eq!(response, another_response); 521 | 522 | let response = Response::new(Version::Http10, StatusCode::OK); 523 | let another_response = Response::new(Version::Http10, StatusCode::BadRequest); 524 | assert_ne!(response, another_response); 525 | } 526 | 527 | #[test] 528 | fn test_content_length() { 529 | // If the status code is 1XX or 204, Content-Length field must not exist. 530 | let response = Response::new(Version::Http10, StatusCode::Continue); 531 | let expected_response: &'static [u8] = b"HTTP/1.0 100 \r\n\ 532 | Server: Firecracker API\r\n\ 533 | Connection: keep-alive\r\n\r\n"; 534 | let mut response_buf: [u8; 66] = [0; 66]; 535 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 536 | assert_eq!(response_buf.as_ref(), expected_response); 537 | 538 | let response = Response::new(Version::Http10, StatusCode::NoContent); 539 | let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\ 540 | Server: Firecracker API\r\n\ 541 | Connection: keep-alive\r\n\r\n"; 542 | let mut response_buf: [u8; 66] = [0; 66]; 543 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 544 | assert_eq!(response_buf.as_ref(), expected_response); 545 | 546 | // If not 1XX or 204, Content-Length field must exist even if the body isn't set. 547 | let response = Response::new(Version::Http10, StatusCode::OK); 548 | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ 549 | Server: Firecracker API\r\n\ 550 | Connection: keep-alive\r\n\ 551 | Content-Type: application/json\r\n\ 552 | Content-Length: 0\r\n\r\n"; 553 | let mut response_buf: [u8; 117] = [0; 117]; 554 | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); 555 | assert_eq!(response_buf.as_ref(), expected_response); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Alibaba Cloud. All rights reserved. 2 | // Copyright © 2019 Intel Corporation 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | use std::collections::hash_map::{Entry, HashMap}; 7 | 8 | use crate::{MediaType, Method, Request, Response, StatusCode, Version}; 9 | 10 | pub use crate::common::RouteError; 11 | 12 | /// An HTTP endpoint handler interface 13 | pub trait EndpointHandler: Sync + Send { 14 | /// Handles an HTTP request. 15 | fn handle_request(&self, req: &Request, arg: &T) -> Response; 16 | } 17 | 18 | /// An HTTP routes structure. 19 | pub struct HttpRoutes { 20 | server_id: String, 21 | prefix: String, 22 | media_type: MediaType, 23 | /// routes is a hash table mapping endpoint URIs to their endpoint handlers. 24 | routes: HashMap + Sync + Send>>, 25 | } 26 | 27 | impl HttpRoutes { 28 | /// Create a http request router. 29 | pub fn new(server_id: String, prefix: String) -> Self { 30 | HttpRoutes { 31 | server_id, 32 | prefix, 33 | media_type: MediaType::ApplicationJson, 34 | routes: HashMap::new(), 35 | } 36 | } 37 | 38 | /// Register a request handler for a unique (HTTP_METHOD, HTTP_PATH) tuple. 39 | /// 40 | /// # Arguments 41 | /// * `method`: HTTP method to assoicate with the handler. 42 | /// * `path`: HTTP path to associate with the handler. 43 | /// * `handler`: HTTP request handler for the (method, path) tuple. 44 | pub fn add_route( 45 | &mut self, 46 | method: Method, 47 | path: String, 48 | handler: Box + Sync + Send>, 49 | ) -> Result<(), RouteError> { 50 | let full_path = format!("{}:{}{}", method.to_str(), self.prefix, path); 51 | match self.routes.entry(full_path.clone()) { 52 | Entry::Occupied(_) => Err(RouteError::HandlerExist(full_path)), 53 | Entry::Vacant(entry) => { 54 | entry.insert(handler); 55 | Ok(()) 56 | } 57 | } 58 | } 59 | 60 | /// Handle an incoming http request and generate corresponding response. 61 | /// 62 | /// # Examples 63 | /// 64 | /// ``` 65 | /// extern crate micro_http; 66 | /// use micro_http::{EndpointHandler, HttpRoutes, Method, Request, Response, StatusCode, Version}; 67 | /// 68 | /// struct HandlerArg(bool); 69 | /// struct MockHandler {} 70 | /// impl EndpointHandler for MockHandler { 71 | /// fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response { 72 | /// Response::new(Version::Http11, StatusCode::OK) 73 | /// } 74 | /// } 75 | /// 76 | /// let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); 77 | /// let handler = MockHandler {}; 78 | /// router 79 | /// .add_route(Method::Get, "/func1".to_string(), Box::new(handler)) 80 | /// .unwrap(); 81 | /// 82 | /// let request_bytes = b"GET http://localhost/api/v1/func1 HTTP/1.1\r\n\r\n"; 83 | /// let request = Request::try_from(request_bytes, None).unwrap(); 84 | /// let arg = HandlerArg(true); 85 | /// let reply = router.handle_http_request(&request, &arg); 86 | /// assert_eq!(reply.status(), StatusCode::OK); 87 | /// ``` 88 | pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response { 89 | let path = format!( 90 | "{}:{}", 91 | request.method().to_str(), 92 | request.uri().get_abs_path() 93 | ); 94 | let mut response = match self.routes.get(&path) { 95 | Some(route) => route.handle_request(request, argument), 96 | None => Response::new(Version::Http11, StatusCode::NotFound), 97 | }; 98 | 99 | response.set_server(&self.server_id); 100 | response.set_content_type(self.media_type); 101 | response 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::*; 108 | 109 | struct HandlerArg; 110 | 111 | struct MockHandler {} 112 | 113 | impl EndpointHandler for MockHandler { 114 | fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response { 115 | Response::new(Version::Http11, StatusCode::OK) 116 | } 117 | } 118 | 119 | #[test] 120 | fn test_create_router() { 121 | let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); 122 | let handler = MockHandler {}; 123 | let res = router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)); 124 | assert!(res.is_ok()); 125 | assert!(router.routes.contains_key("GET:/api/v1/func1")); 126 | 127 | let handler = MockHandler {}; 128 | match router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)) { 129 | Err(RouteError::HandlerExist(_)) => {} 130 | _ => panic!("add_route() should return error for path with existing handler"), 131 | } 132 | 133 | let handler = MockHandler {}; 134 | let res = router.add_route(Method::Put, "/func1".to_string(), Box::new(handler)); 135 | assert!(res.is_ok()); 136 | 137 | let handler = MockHandler {}; 138 | let res = router.add_route(Method::Get, "/func2".to_string(), Box::new(handler)); 139 | assert!(res.is_ok()); 140 | } 141 | 142 | #[test] 143 | fn test_handle_http_request() { 144 | let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); 145 | let handler = MockHandler {}; 146 | router 147 | .add_route(Method::Get, "/func1".to_string(), Box::new(handler)) 148 | .unwrap(); 149 | 150 | let request = 151 | Request::try_from(b"GET http://localhost/api/v1/func2 HTTP/1.1\r\n\r\n", None).unwrap(); 152 | let arg = HandlerArg; 153 | let reply = router.handle_http_request(&request, &arg); 154 | assert_eq!(reply.status(), StatusCode::NotFound); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::collections::HashMap; 5 | use std::io::{Read, Write}; 6 | use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; 7 | use std::os::unix::net::{UnixListener, UnixStream}; 8 | use std::path::Path; 9 | 10 | use crate::common::{Body, Version}; 11 | pub use crate::common::{ConnectionError, ServerError}; 12 | use crate::connection::HttpConnection; 13 | use crate::request::Request; 14 | use crate::response::{Response, StatusCode}; 15 | use vmm_sys_util::{epoll, eventfd::EventFd, sock_ctrl_msg::ScmSocket}; 16 | 17 | static SERVER_FULL_ERROR_MESSAGE: &[u8] = b"HTTP/1.1 503\r\n\ 18 | Server: Firecracker API\r\n\ 19 | Connection: close\r\n\ 20 | Content-Length: 40\r\n\r\n{ \"error\": \"Too many open connections\" }"; 21 | const MAX_CONNECTIONS: usize = 10; 22 | /// Payload max size 23 | pub(crate) const MAX_PAYLOAD_SIZE: usize = 51200; 24 | 25 | type Result = std::result::Result; 26 | 27 | /// Wrapper over `Request` which adds an identification token. 28 | #[derive(Debug)] 29 | pub struct ServerRequest { 30 | /// Inner request. 31 | pub request: Request, 32 | /// Identification token. 33 | id: u64, 34 | } 35 | 36 | impl ServerRequest { 37 | /// Creates a new `ServerRequest` object from an existing `Request`, 38 | /// adding an identification token. 39 | pub fn new(request: Request, id: u64) -> Self { 40 | Self { request, id } 41 | } 42 | 43 | /// Returns a reference to the inner request. 44 | pub fn inner(&self) -> &Request { 45 | &self.request 46 | } 47 | 48 | /// Calls the function provided on the inner request to obtain the response. 49 | /// The response is then wrapped in a `ServerResponse`. 50 | /// 51 | /// Returns a `ServerResponse` ready for yielding to the server 52 | pub fn process(&self, mut callable: F) -> ServerResponse 53 | where 54 | F: FnMut(&Request) -> Response, 55 | { 56 | let http_response = callable(self.inner()); 57 | ServerResponse::new(http_response, self.id) 58 | } 59 | } 60 | 61 | /// Wrapper over `Response` which adds an identification token. 62 | pub struct ServerResponse { 63 | /// Inner response. 64 | response: Response, 65 | /// Identification token. 66 | id: u64, 67 | } 68 | 69 | impl ServerResponse { 70 | fn new(response: Response, id: u64) -> Self { 71 | Self { response, id } 72 | } 73 | } 74 | 75 | /// Describes the state of the connection as far as data exchange 76 | /// on the stream is concerned. 77 | #[derive(PartialOrd, PartialEq)] 78 | enum ClientConnectionState { 79 | AwaitingIncoming, 80 | AwaitingOutgoing, 81 | Closed, 82 | } 83 | 84 | /// Wrapper over `HttpConnection` which keeps track of yielded 85 | /// requests and absorbed responses. 86 | struct ClientConnection { 87 | /// The `HttpConnection` object which handles data exchange. 88 | connection: HttpConnection, 89 | /// The state of the connection in the `epoll` structure. 90 | state: ClientConnectionState, 91 | /// Represents the difference between yielded requests and 92 | /// absorbed responses. 93 | /// This has to be `0` if we want to drop the connection. 94 | in_flight_response_count: u32, 95 | } 96 | 97 | impl ClientConnection { 98 | fn new(connection: HttpConnection) -> Self { 99 | Self { 100 | connection, 101 | state: ClientConnectionState::AwaitingIncoming, 102 | in_flight_response_count: 0, 103 | } 104 | } 105 | 106 | fn read(&mut self) -> Result> { 107 | // Data came into the connection. 108 | let mut parsed_requests = vec![]; 109 | match self.connection.try_read() { 110 | Err(ConnectionError::ConnectionClosed) => { 111 | // Connection timeout. 112 | self.state = ClientConnectionState::Closed; 113 | // We don't want to propagate this to the server and we will 114 | // return no requests and wait for the connection to become 115 | // safe to drop. 116 | return Ok(vec![]); 117 | } 118 | Err(ConnectionError::StreamReadError(inner)) => { 119 | // Reading from the connection failed. 120 | // We should try to write an error message regardless. 121 | let mut internal_error_response = 122 | Response::new(Version::Http11, StatusCode::InternalServerError); 123 | internal_error_response.set_body(Body::new(inner.to_string())); 124 | self.connection.enqueue_response(internal_error_response); 125 | } 126 | Err(ConnectionError::ParseError(inner)) => { 127 | // An error occurred while parsing the read bytes. 128 | // Check if there are any valid parsed requests in the queue. 129 | while let Some(_discarded_request) = self.connection.pop_parsed_request() {} 130 | 131 | // Send an error response for the request that gave us the error. 132 | let mut error_response = Response::new(Version::Http11, StatusCode::BadRequest); 133 | error_response.set_body(Body::new(format!( 134 | "{{ \"error\": \"{}\nAll previous unanswered requests will be dropped.\" }}", 135 | inner 136 | ))); 137 | self.connection.enqueue_response(error_response); 138 | } 139 | Err(ConnectionError::InvalidWrite) | Err(ConnectionError::StreamWriteError(_)) => { 140 | // This is unreachable because `HttpConnection::try_read()` cannot return this error variant. 141 | unreachable!(); 142 | } 143 | Ok(()) => { 144 | while let Some(request) = self.connection.pop_parsed_request() { 145 | // Add all valid requests to `parsed_requests`. 146 | parsed_requests.push(request); 147 | } 148 | } 149 | } 150 | self.in_flight_response_count = self 151 | .in_flight_response_count 152 | .checked_add(parsed_requests.len() as u32) 153 | .ok_or(ServerError::Overflow)?; 154 | // If the state of the connection has changed, we need to update 155 | // the event set in the `epoll` structure. 156 | if self.connection.pending_write() { 157 | self.state = ClientConnectionState::AwaitingOutgoing; 158 | } 159 | 160 | Ok(parsed_requests) 161 | } 162 | 163 | fn write(&mut self) -> Result<()> { 164 | // The stream is available for writing. 165 | match self.connection.try_write() { 166 | Err(ConnectionError::ConnectionClosed) | Err(ConnectionError::StreamWriteError(_)) => { 167 | // Writing to the stream failed so it will be removed. 168 | self.state = ClientConnectionState::Closed; 169 | } 170 | Err(ConnectionError::InvalidWrite) => { 171 | // A `try_write` call was performed on a connection that has nothing 172 | // to write. 173 | return Err(ServerError::ConnectionError(ConnectionError::InvalidWrite)); 174 | } 175 | _ => { 176 | // Check if we still have bytes to write for this connection. 177 | if !self.connection.pending_write() { 178 | self.state = ClientConnectionState::AwaitingIncoming; 179 | } 180 | } 181 | } 182 | Ok(()) 183 | } 184 | 185 | fn enqueue_response(&mut self, response: Response) -> Result<()> { 186 | if self.state != ClientConnectionState::Closed { 187 | self.connection.enqueue_response(response); 188 | } 189 | self.in_flight_response_count = self 190 | .in_flight_response_count 191 | .checked_sub(1) 192 | .ok_or(ServerError::Underflow)?; 193 | Ok(()) 194 | } 195 | 196 | /// Discards all pending writes from the inner connection. 197 | fn clear_write_buffer(&mut self) { 198 | self.connection.clear_write_buffer(); 199 | } 200 | 201 | // Returns `true` if the connection is closed and safe to drop. 202 | fn is_done(&self) -> bool { 203 | self.state == ClientConnectionState::Closed 204 | && !self.connection.pending_write() 205 | && self.in_flight_response_count == 0 206 | } 207 | } 208 | 209 | /// HTTP Server implementation using Unix Domain Sockets and `EPOLL` to 210 | /// handle multiple connections on the same thread. 211 | /// 212 | /// The function that handles incoming connections, parses incoming 213 | /// requests and sends responses for awaiting requests is `requests`. 214 | /// It can be called in a loop, which will render the thread that the 215 | /// server runs on incapable of performing other operations, or it can 216 | /// be used in another `EPOLL` structure, as it provides its `epoll`, 217 | /// which is a wrapper over the file descriptor of the epoll structure 218 | /// used within the server, and it can be added to another one using 219 | /// the `EPOLLIN` flag. Whenever there is a notification on that fd, 220 | /// `requests` should be called once. 221 | /// 222 | /// # Example 223 | /// 224 | /// ## Starting and running the server 225 | /// 226 | /// ``` 227 | /// use micro_http::{HttpServer, Response, StatusCode}; 228 | /// 229 | /// let path_to_socket = "/tmp/example.sock"; 230 | /// std::fs::remove_file(path_to_socket).unwrap_or_default(); 231 | /// 232 | /// // Start the server. 233 | /// let mut server = HttpServer::new(path_to_socket).unwrap(); 234 | /// server.start_server().unwrap(); 235 | /// 236 | /// // Connect a client to the server so it doesn't block in our example. 237 | /// let mut socket = std::os::unix::net::UnixStream::connect(path_to_socket).unwrap(); 238 | /// 239 | /// // Server loop processing requests. 240 | /// loop { 241 | /// for request in server.requests().unwrap() { 242 | /// let response = request.process(|request| { 243 | /// // Your code here. 244 | /// Response::new(request.http_version(), StatusCode::NoContent) 245 | /// }); 246 | /// server.respond(response); 247 | /// } 248 | /// // Break this example loop. 249 | /// break; 250 | /// } 251 | /// ``` 252 | pub struct HttpServer { 253 | /// Socket on which we listen for new connections. 254 | socket: UnixListener, 255 | /// Server's epoll instance. 256 | epoll: epoll::Epoll, 257 | /// Event requesting micro-http shutdown. 258 | /// Used to break out of inner `epoll_wait` and reports shutdown event. 259 | kill_switch: Option, 260 | /// Holds the token-connection pairs of the server. 261 | /// Each connection has an associated identification token, which is 262 | /// the file descriptor of the underlying stream. 263 | /// We use the file descriptor of the stream as the key for mapping 264 | /// connections because the 1-to-1 relation is guaranteed by the OS. 265 | connections: HashMap>, 266 | /// Payload max size 267 | payload_max_size: usize, 268 | } 269 | 270 | impl HttpServer { 271 | /// Constructor for `HttpServer`. 272 | /// 273 | /// Returns the newly formed `HttpServer`. 274 | /// 275 | /// # Errors 276 | /// Returns an `IOError` when binding or `epoll::create` fails. 277 | pub fn new>(path_to_socket: P) -> Result { 278 | let socket = UnixListener::bind(path_to_socket).map_err(ServerError::IOError)?; 279 | let epoll = epoll::Epoll::new().map_err(ServerError::IOError)?; 280 | Ok(Self { 281 | socket, 282 | epoll, 283 | kill_switch: None, 284 | connections: HashMap::new(), 285 | payload_max_size: MAX_PAYLOAD_SIZE, 286 | }) 287 | } 288 | 289 | /// Constructor for `HttpServer`. 290 | /// 291 | /// Returns the newly formed `HttpServer`. 292 | /// 293 | /// # Safety 294 | /// This function requires the socket_fd to be solely owned 295 | /// and not be associated with another File in the caller as it uses 296 | /// the unsafe `UnixListener::from_raw_fd method`. 297 | /// 298 | /// # Errors 299 | /// Returns an `IOError` when `epoll::create` fails. 300 | pub unsafe fn new_from_fd(socket_fd: RawFd) -> Result { 301 | let socket = UnixListener::from_raw_fd(socket_fd); 302 | let epoll = epoll::Epoll::new().map_err(ServerError::IOError)?; 303 | Ok(HttpServer { 304 | socket, 305 | epoll, 306 | kill_switch: None, 307 | connections: HashMap::new(), 308 | payload_max_size: MAX_PAYLOAD_SIZE, 309 | }) 310 | } 311 | 312 | /// Adds a `kill_switch` event fd used to break out of inner `epoll_wait` 313 | /// and report a shutdown event. 314 | pub fn add_kill_switch(&mut self, kill_switch: EventFd) -> Result<()> { 315 | // Add the kill switch to the `epoll` structure. 316 | let ret = Self::epoll_add(&self.epoll, kill_switch.as_raw_fd()); 317 | self.kill_switch = Some(kill_switch); 318 | ret 319 | } 320 | 321 | /// This function sets the limit for PUT/PATCH requests. It overwrites the 322 | /// default limit of 0.05MiB with the one allowed by server. 323 | pub fn set_payload_max_size(&mut self, request_payload_max_size: usize) { 324 | self.payload_max_size = request_payload_max_size; 325 | } 326 | 327 | /// Starts the HTTP Server. 328 | pub fn start_server(&mut self) -> Result<()> { 329 | // Add the socket on which we listen for new connections to the 330 | // `epoll` structure. 331 | Self::epoll_add(&self.epoll, self.socket.as_raw_fd()) 332 | } 333 | 334 | /// This function is responsible for the data exchange with the clients and should 335 | /// be called when we are either notified through `epoll` that we need to exchange 336 | /// data with at least a client or when we don't need to perform any other operations 337 | /// on this thread and we can afford to call it in a loop. 338 | /// 339 | /// Note that this function will block the current thread if there are no notifications 340 | /// to be handled by the server. 341 | /// 342 | /// Returns a collection of complete and valid requests to be processed by the user 343 | /// of the server. Once processed, responses should be sent using `enqueue_responses()`. 344 | /// 345 | /// # Errors 346 | /// `IOError` is returned when `read`, `write` or `epoll::ctl` operations fail. 347 | /// `ServerFull` is returned when a client is trying to connect to the server, but 348 | /// full capacity has already been reached. 349 | /// `InvalidWrite` is returned when the server attempted to perform a write operation 350 | /// on a connection on which it is not possible. 351 | pub fn requests(&mut self) -> Result> { 352 | let mut parsed_requests: Vec = vec![]; 353 | // Possible events coming from FDs: 1 + 1 + MAX_CONNECTIONS: 354 | // exit-eventfd, sock-listen-fd, active-connections-fds. 355 | let mut events = [epoll::EpollEvent::default(); MAX_CONNECTIONS + 2]; 356 | // This is a wrapper over the syscall `epoll_wait` and it will block the 357 | // current thread until at least one event is received. 358 | // The received notifications will then populate the `events` array with 359 | // `event_count` elements, where 1 <= event_count <= MAX_CONNECTIONS + 2. 360 | let event_count = match self.epoll.wait(-1, &mut events[..]) { 361 | Ok(event_count) => event_count, 362 | Err(e) if e.raw_os_error() == Some(libc::EINTR) => 0, 363 | Err(e) => return Err(ServerError::IOError(e)), 364 | }; 365 | 366 | // Getting the file descriptor for kill switch. 367 | // If there is no kill switch fd, we use value -1 as an invalid fd. 368 | let kill_fd = self.kill_switch.as_ref().map_or(-1, |ks| ks.as_raw_fd()); 369 | 370 | // We only iterate over first `event_count` events and discard empty elements 371 | // at the end of the array. 372 | for e in events[..event_count].iter() { 373 | // Check the file descriptor which produced the notification `e`. 374 | // It could be that we need to shutdown, or have a new connection, or 375 | // one of our open connections is ready to exchange data with a client. 376 | if e.fd() == kill_fd { 377 | // Report that the kill switch was triggered. 378 | return Err(ServerError::ShutdownEvent); 379 | } else if e.fd() == self.socket.as_raw_fd() { 380 | // We have received a notification on the listener socket, which 381 | // means we have a new connection to accept. 382 | match self.handle_new_connection() { 383 | // If the server is full, we send a message to the client 384 | // notifying them that we will close the connection, then 385 | // we discard it. 386 | Err(ServerError::ServerFull) => { 387 | self.socket 388 | .accept() 389 | .map_err(ServerError::IOError) 390 | .and_then(move |(mut stream, _)| { 391 | stream 392 | .write(SERVER_FULL_ERROR_MESSAGE) 393 | .map_err(ServerError::IOError) 394 | })?; 395 | } 396 | // An internal error will compromise any in-flight requests. 397 | Err(error) => return Err(error), 398 | Ok(()) => {} 399 | }; 400 | } else { 401 | // We have a notification on one of our open connections. 402 | let fd = e.fd(); 403 | let client_connection = self.connections.get_mut(&fd).unwrap(); 404 | 405 | // If we receive a hang up on a connection, we clear the write buffer and set 406 | // the connection state to closed to mark it ready for removal from the 407 | // connections map, which will gracefully close the socket. 408 | // The connection is also marked for removal when encountering `EPOLLERR`, 409 | // since this is an "error condition happened on the associated file 410 | // descriptor", according to the `epoll_ctl` man page. 411 | if e.event_set().contains(epoll::EventSet::ERROR) 412 | || e.event_set().contains(epoll::EventSet::HANG_UP) 413 | || e.event_set().contains(epoll::EventSet::READ_HANG_UP) 414 | { 415 | client_connection.clear_write_buffer(); 416 | client_connection.state = ClientConnectionState::Closed; 417 | continue; 418 | } 419 | 420 | if e.event_set().contains(epoll::EventSet::IN) { 421 | // We have bytes to read from this connection. 422 | // If our `read` yields `Request` objects, we wrap them with an ID before 423 | // handing them to the user. 424 | parsed_requests.append( 425 | &mut client_connection 426 | .read()? 427 | .into_iter() 428 | .map(|request| ServerRequest::new(request, e.data())) 429 | .collect(), 430 | ); 431 | // If the connection was incoming before we read and we now have to write 432 | // either an error message or an `expect` response, we change its `epoll` 433 | // event set to notify us when the stream is ready for writing. 434 | if client_connection.state == ClientConnectionState::AwaitingOutgoing { 435 | Self::epoll_mod( 436 | &self.epoll, 437 | fd, 438 | epoll::EventSet::OUT | epoll::EventSet::READ_HANG_UP, 439 | )?; 440 | } 441 | } else if e.event_set().contains(epoll::EventSet::OUT) { 442 | // We have bytes to write on this connection. 443 | client_connection.write()?; 444 | // If the connection was outgoing before we tried to write the responses 445 | // and we don't have any more responses to write, we change the `epoll` 446 | // event set to notify us when we have bytes to read from the stream. 447 | if client_connection.state == ClientConnectionState::AwaitingIncoming { 448 | Self::epoll_mod( 449 | &self.epoll, 450 | fd, 451 | epoll::EventSet::IN | epoll::EventSet::READ_HANG_UP, 452 | )?; 453 | } 454 | } 455 | } 456 | } 457 | 458 | // Remove dead connections. 459 | let epoll = &self.epoll; 460 | self.connections.retain(|rawfd, client_connection| { 461 | if client_connection.is_done() { 462 | // The rawfd should have been registered to the epoll fd. 463 | Self::epoll_del(epoll, *rawfd).unwrap(); 464 | false 465 | } else { 466 | true 467 | } 468 | }); 469 | 470 | Ok(parsed_requests) 471 | } 472 | 473 | /// This function is responsible with flushing any remaining outgoing 474 | /// requests on the server. 475 | /// 476 | /// Note that this function can block the thread on write, since the 477 | /// operation is blocking. 478 | pub fn flush_outgoing_writes(&mut self) { 479 | for (_, connection) in self.connections.iter_mut() { 480 | while connection.state == ClientConnectionState::AwaitingOutgoing { 481 | if let Err(e) = connection.write() { 482 | if let ServerError::ConnectionError(ConnectionError::InvalidWrite) = e { 483 | // Nothing is logged since an InvalidWrite means we have successfully 484 | // flushed the connection 485 | } 486 | break; 487 | } 488 | } 489 | } 490 | } 491 | 492 | /// The file descriptor of the `epoll` structure can enable the server to become 493 | /// a non-blocking structure in an application. 494 | /// 495 | /// Returns a reference to the instance of the server's internal `epoll` structure. 496 | /// 497 | /// # Example 498 | /// 499 | /// ## Non-blocking server 500 | /// ``` 501 | /// use std::os::unix::io::AsRawFd; 502 | /// 503 | /// use micro_http::{HttpServer, Response, StatusCode}; 504 | /// use vmm_sys_util::epoll; 505 | /// 506 | /// // Create our epoll manager. 507 | /// let epoll = epoll::Epoll::new().unwrap(); 508 | /// 509 | /// let path_to_socket = "/tmp/epoll_example.sock"; 510 | /// std::fs::remove_file(path_to_socket).unwrap_or_default(); 511 | /// 512 | /// // Start the server. 513 | /// let mut server = HttpServer::new(path_to_socket).unwrap(); 514 | /// server.start_server().unwrap(); 515 | /// 516 | /// // Add our server to the `epoll` manager. 517 | /// epoll 518 | /// .ctl( 519 | /// epoll::ControlOperation::Add, 520 | /// server.epoll().as_raw_fd(), 521 | /// epoll::EpollEvent::new(epoll::EventSet::IN, 1234u64), 522 | /// ) 523 | /// .unwrap(); 524 | /// 525 | /// // Connect a client to the server so it doesn't block in our example. 526 | /// let mut socket = std::os::unix::net::UnixStream::connect(path_to_socket).unwrap(); 527 | /// 528 | /// // Control loop of the application. 529 | /// let mut events = Vec::with_capacity(10); 530 | /// loop { 531 | /// let num_ev = epoll.wait(-1, events.as_mut_slice()); 532 | /// for event in events { 533 | /// match event.data() { 534 | /// // The server notification. 535 | /// 1234 => { 536 | /// let request = server.requests(); 537 | /// // Process... 538 | /// } 539 | /// // Other `epoll` notifications. 540 | /// _ => { 541 | /// // Do other computation. 542 | /// } 543 | /// } 544 | /// } 545 | /// // Break this example loop. 546 | /// break; 547 | /// } 548 | /// ``` 549 | pub fn epoll(&self) -> &epoll::Epoll { 550 | &self.epoll 551 | } 552 | 553 | /// Enqueues the provided responses in the outgoing connection. 554 | /// 555 | /// # Errors 556 | /// `IOError` is returned when an `epoll::ctl` operation fails. 557 | pub fn enqueue_responses(&mut self, responses: Vec) -> Result<()> { 558 | for response in responses { 559 | self.respond(response)?; 560 | } 561 | 562 | Ok(()) 563 | } 564 | 565 | /// Adds the provided response to the outgoing buffer in the corresponding connection. 566 | /// 567 | /// # Errors 568 | /// `IOError` is returned when an `epoll::ctl` operation fails. 569 | /// `Underflow` is returned when `enqueue_response` fails. 570 | pub fn respond(&mut self, response: ServerResponse) -> Result<()> { 571 | if let Some(client_connection) = self.connections.get_mut(&(response.id as i32)) { 572 | // If the connection was incoming before we enqueue the response, we change its 573 | // `epoll` event set to notify us when the stream is ready for writing. 574 | if let ClientConnectionState::AwaitingIncoming = client_connection.state { 575 | client_connection.state = ClientConnectionState::AwaitingOutgoing; 576 | Self::epoll_mod( 577 | &self.epoll, 578 | response.id as RawFd, 579 | epoll::EventSet::OUT | epoll::EventSet::READ_HANG_UP, 580 | )?; 581 | } 582 | client_connection.enqueue_response(response.response)?; 583 | } 584 | Ok(()) 585 | } 586 | 587 | /// Accepts a new incoming connection and adds it to the `epoll` notification structure. 588 | /// 589 | /// # Errors 590 | /// `IOError` is returned when socket or epoll operations fail. 591 | /// `ServerFull` is returned if server full capacity has been reached. 592 | fn handle_new_connection(&mut self) -> Result<()> { 593 | if self.connections.len() == MAX_CONNECTIONS { 594 | // If we want a replacement policy for connections 595 | // this is where we will have it. 596 | return Err(ServerError::ServerFull); 597 | } 598 | 599 | self.socket 600 | .accept() 601 | .map_err(ServerError::IOError) 602 | .and_then(|(stream, _)| { 603 | // `HttpConnection` is supposed to work with non-blocking streams. 604 | stream 605 | .set_nonblocking(true) 606 | .map(|_| stream) 607 | .map_err(ServerError::IOError) 608 | }) 609 | .and_then(|stream| { 610 | // Add the stream to the `epoll` structure and listen for bytes to be read. 611 | let raw_fd = stream.as_raw_fd(); 612 | Self::epoll_add(&self.epoll, raw_fd)?; 613 | let mut conn = HttpConnection::new(stream); 614 | conn.set_payload_max_size(self.payload_max_size); 615 | // Then add it to our open connections. 616 | self.connections.insert(raw_fd, ClientConnection::new(conn)); 617 | Ok(()) 618 | }) 619 | } 620 | 621 | /// Changes the event type for a connection to either listen for incoming bytes 622 | /// or for when the stream is ready for writing. 623 | /// 624 | /// # Errors 625 | /// `IOError` is returned when an `EPOLL_CTL_MOD` control operation fails. 626 | fn epoll_mod(epoll: &epoll::Epoll, stream_fd: RawFd, evset: epoll::EventSet) -> Result<()> { 627 | let event = epoll::EpollEvent::new(evset, stream_fd as u64); 628 | epoll 629 | .ctl(epoll::ControlOperation::Modify, stream_fd, event) 630 | .map_err(ServerError::IOError) 631 | } 632 | 633 | /// Adds a stream to the `epoll` notification structure with the `EPOLLIN` event set. 634 | /// 635 | /// # Errors 636 | /// `IOError` is returned when an `EPOLL_CTL_ADD` control operation fails. 637 | fn epoll_add(epoll: &epoll::Epoll, stream_fd: RawFd) -> Result<()> { 638 | epoll 639 | .ctl( 640 | epoll::ControlOperation::Add, 641 | stream_fd, 642 | epoll::EpollEvent::new( 643 | epoll::EventSet::IN | epoll::EventSet::READ_HANG_UP, 644 | stream_fd as u64, 645 | ), 646 | ) 647 | .map_err(ServerError::IOError) 648 | } 649 | 650 | /// Removes a stream to the `epoll` notification structure. 651 | fn epoll_del(epoll: &epoll::Epoll, stream_fd: RawFd) -> Result<()> { 652 | epoll 653 | .ctl( 654 | epoll::ControlOperation::Delete, 655 | stream_fd, 656 | epoll::EpollEvent::new(epoll::EventSet::IN, stream_fd as u64), 657 | ) 658 | .map_err(ServerError::IOError) 659 | } 660 | } 661 | 662 | #[cfg(test)] 663 | mod tests { 664 | #![allow(clippy::undocumented_unsafe_blocks)] 665 | 666 | use super::*; 667 | use std::io::{Read, Write}; 668 | use std::net::Shutdown; 669 | use std::os::unix::net::UnixStream; 670 | use std::sync::{Arc, Mutex}; 671 | 672 | use crate::common::Body; 673 | use vmm_sys_util::{eventfd::EFD_NONBLOCK, tempfile::TempFile}; 674 | 675 | fn get_temp_socket_file() -> TempFile { 676 | let mut path_to_socket = TempFile::new().unwrap(); 677 | path_to_socket.remove().unwrap(); 678 | path_to_socket 679 | } 680 | 681 | #[test] 682 | fn test_wait_one_connection() { 683 | let path_to_socket = get_temp_socket_file(); 684 | 685 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 686 | server.start_server().unwrap(); 687 | 688 | // Test one incoming connection. 689 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 690 | assert!(server.requests().unwrap().is_empty()); 691 | 692 | socket 693 | .write_all( 694 | b"PATCH /machine-config HTTP/1.1\r\n\ 695 | Content-Length: 13\r\n\ 696 | Content-Type: application/json\r\n\r\nwhatever body", 697 | ) 698 | .unwrap(); 699 | 700 | let mut req_vec = server.requests().unwrap(); 701 | let server_request = req_vec.remove(0); 702 | 703 | server 704 | .respond(server_request.process(|_request| { 705 | let mut response = Response::new(Version::Http11, StatusCode::OK); 706 | let response_body = b"response body"; 707 | response.set_body(Body::new(response_body.to_vec())); 708 | response 709 | })) 710 | .unwrap(); 711 | assert!(server.requests().unwrap().is_empty()); 712 | 713 | let mut buf: [u8; 1024] = [0; 1024]; 714 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 715 | } 716 | 717 | #[test] 718 | fn test_connection_size_limit_exceeded() { 719 | let path_to_socket = get_temp_socket_file(); 720 | 721 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 722 | server.start_server().unwrap(); 723 | 724 | // Test one incoming connection. 725 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 726 | assert!(server.requests().unwrap().is_empty()); 727 | 728 | socket 729 | .write_all( 730 | b"PATCH /machine-config HTTP/1.1\r\n\ 731 | Content-Length: 51201\r\n\ 732 | Content-Type: application/json\r\n\r\naaaaa", 733 | ) 734 | .unwrap(); 735 | assert!(server.requests().unwrap().is_empty()); 736 | assert!(server.requests().unwrap().is_empty()); 737 | let mut buf: [u8; 265] = [0; 265]; 738 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 739 | let error_message = b"HTTP/1.1 400 \r\n\ 740 | Server: Firecracker API\r\n\ 741 | Connection: keep-alive\r\n\ 742 | Content-Type: application/json\r\n\ 743 | Content-Length: 149\r\n\r\n{ \"error\": \"\ 744 | Request payload with size 51201 is larger than \ 745 | the limit of 51200 allowed by server.\nAll \ 746 | previous unanswered requests will be dropped."; 747 | assert_eq!(&buf[..], &error_message[..]); 748 | } 749 | 750 | #[test] 751 | fn test_set_payload_size() { 752 | let path_to_socket = get_temp_socket_file(); 753 | 754 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 755 | server.start_server().unwrap(); 756 | server.set_payload_max_size(4); 757 | 758 | // Test one incoming connection. 759 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 760 | assert!(server.requests().unwrap().is_empty()); 761 | 762 | socket 763 | .write_all( 764 | b"PATCH /machine-config HTTP/1.1\r\n\ 765 | Content-Length: 5\r\n\ 766 | Content-Type: application/json\r\n\r\naaaaa", 767 | ) 768 | .unwrap(); 769 | assert!(server.requests().unwrap().is_empty()); 770 | assert!(server.requests().unwrap().is_empty()); 771 | let mut buf: [u8; 260] = [0; 260]; 772 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 773 | let error_message = b"HTTP/1.1 400 \r\n\ 774 | Server: Firecracker API\r\n\ 775 | Connection: keep-alive\r\n\ 776 | Content-Type: application/json\r\n\ 777 | Content-Length: 141\r\n\r\n{ \"error\": \"\ 778 | Request payload with size 5 is larger than the \ 779 | limit of 4 allowed by server.\nAll previous \ 780 | unanswered requests will be dropped.\" }"; 781 | assert_eq!(&buf[..], &error_message[..]); 782 | } 783 | 784 | #[test] 785 | fn test_wait_one_fd_connection() { 786 | use std::os::unix::io::IntoRawFd; 787 | let path_to_socket = get_temp_socket_file(); 788 | 789 | let socket_listener = UnixListener::bind(path_to_socket.as_path()).unwrap(); 790 | let socket_fd = socket_listener.into_raw_fd(); 791 | 792 | let mut server = unsafe { HttpServer::new_from_fd(socket_fd).unwrap() }; 793 | server.start_server().unwrap(); 794 | 795 | // Test one incoming connection. 796 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 797 | assert!(server.requests().unwrap().is_empty()); 798 | 799 | socket 800 | .write_all( 801 | b"PATCH /machine-config HTTP/1.1\r\n\ 802 | Content-Length: 13\r\n\ 803 | Content-Type: application/json\r\n\r\nwhatever body", 804 | ) 805 | .unwrap(); 806 | 807 | let mut req_vec = server.requests().unwrap(); 808 | let server_request = req_vec.remove(0); 809 | 810 | server 811 | .respond(server_request.process(|request| { 812 | assert_eq!( 813 | std::str::from_utf8(&request.body.as_ref().unwrap().body).unwrap(), 814 | "whatever body" 815 | ); 816 | let mut response = Response::new(Version::Http11, StatusCode::OK); 817 | let response_body = b"response body"; 818 | response.set_body(Body::new(response_body.to_vec())); 819 | response 820 | })) 821 | .unwrap(); 822 | assert!(server.requests().unwrap().is_empty()); 823 | 824 | let mut buf: [u8; 1024] = [0; 1024]; 825 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 826 | assert!(String::from_utf8_lossy(&buf).contains("response body")); 827 | } 828 | 829 | #[test] 830 | fn test_wait_concurrent_connections() { 831 | let path_to_socket = get_temp_socket_file(); 832 | 833 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 834 | server.start_server().unwrap(); 835 | 836 | // Test two concurrent connections. 837 | let mut first_socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 838 | assert!(server.requests().unwrap().is_empty()); 839 | 840 | first_socket 841 | .write_all( 842 | b"PATCH /machine-config HTTP/1.1\r\n\ 843 | Content-Length: 13\r\n\ 844 | Content-Type: application/json\r\n\r\nwhatever body", 845 | ) 846 | .unwrap(); 847 | let mut second_socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 848 | 849 | let mut req_vec = server.requests().unwrap(); 850 | let server_request = req_vec.remove(0); 851 | 852 | server 853 | .respond(server_request.process(|_request| { 854 | let mut response = Response::new(Version::Http11, StatusCode::OK); 855 | let response_body = b"response body"; 856 | response.set_body(Body::new(response_body.to_vec())); 857 | response 858 | })) 859 | .unwrap(); 860 | second_socket 861 | .write_all( 862 | b"GET /machine-config HTTP/1.1\r\n\ 863 | Content-Type: application/json\r\n\r\n", 864 | ) 865 | .unwrap(); 866 | 867 | let mut req_vec = server.requests().unwrap(); 868 | let second_server_request = req_vec.remove(0); 869 | 870 | assert_eq!( 871 | second_server_request.request, 872 | Request::try_from( 873 | b"GET /machine-config HTTP/1.1\r\n\ 874 | Content-Type: application/json\r\n\r\n", 875 | None 876 | ) 877 | .unwrap() 878 | ); 879 | 880 | let mut buf: [u8; 1024] = [0; 1024]; 881 | assert!(first_socket.read(&mut buf[..]).unwrap() > 0); 882 | first_socket.shutdown(std::net::Shutdown::Both).unwrap(); 883 | 884 | server 885 | .respond(second_server_request.process(|_request| { 886 | let mut response = Response::new(Version::Http11, StatusCode::OK); 887 | let response_body = b"response second body"; 888 | response.set_body(Body::new(response_body.to_vec())); 889 | response 890 | })) 891 | .unwrap(); 892 | 893 | assert!(server.requests().unwrap().is_empty()); 894 | let mut buf: [u8; 1024] = [0; 1024]; 895 | assert!(second_socket.read(&mut buf[..]).unwrap() > 0); 896 | second_socket.shutdown(std::net::Shutdown::Both).unwrap(); 897 | assert!(server.requests().unwrap().is_empty()); 898 | } 899 | 900 | #[test] 901 | fn test_wait_expect_connection() { 902 | let path_to_socket = get_temp_socket_file(); 903 | 904 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 905 | server.start_server().unwrap(); 906 | 907 | // Test one incoming connection with `Expect: 100-continue`. 908 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 909 | assert!(server.requests().unwrap().is_empty()); 910 | 911 | socket 912 | .write_all( 913 | b"PATCH /machine-config HTTP/1.1\r\n\ 914 | Content-Length: 13\r\n\ 915 | Expect: 100-continue\r\n\r\n", 916 | ) 917 | .unwrap(); 918 | // `wait` on server to receive what the client set on the socket. 919 | // This will set the stream direction to `Outgoing`, as we need to send a `100 CONTINUE` response. 920 | let req_vec = server.requests().unwrap(); 921 | assert!(req_vec.is_empty()); 922 | // Another `wait`, this time to send the response. 923 | // Will be called because of an `EPOLLOUT` notification. 924 | let req_vec = server.requests().unwrap(); 925 | assert!(req_vec.is_empty()); 926 | let mut buf: [u8; 1024] = [0; 1024]; 927 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 928 | 929 | socket.write_all(b"whatever body").unwrap(); 930 | let mut req_vec = server.requests().unwrap(); 931 | let server_request = req_vec.remove(0); 932 | 933 | server 934 | .respond(server_request.process(|_request| { 935 | let mut response = Response::new(Version::Http11, StatusCode::OK); 936 | let response_body = b"response body"; 937 | response.set_body(Body::new(response_body.to_vec())); 938 | response 939 | })) 940 | .unwrap(); 941 | 942 | let req_vec = server.requests().unwrap(); 943 | assert!(req_vec.is_empty()); 944 | 945 | let mut buf: [u8; 1024] = [0; 1024]; 946 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 947 | } 948 | 949 | #[test] 950 | fn test_wait_many_connections() { 951 | let path_to_socket = get_temp_socket_file(); 952 | 953 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 954 | server.start_server().unwrap(); 955 | 956 | let mut sockets: Vec = Vec::with_capacity(MAX_CONNECTIONS + 1); 957 | for _ in 0..MAX_CONNECTIONS { 958 | sockets.push(UnixStream::connect(path_to_socket.as_path()).unwrap()); 959 | assert!(server.requests().unwrap().is_empty()); 960 | } 961 | 962 | sockets.push(UnixStream::connect(path_to_socket.as_path()).unwrap()); 963 | assert!(server.requests().unwrap().is_empty()); 964 | let mut buf: [u8; 120] = [0; 120]; 965 | sockets[MAX_CONNECTIONS].read_exact(&mut buf).unwrap(); 966 | assert_eq!(&buf[..], SERVER_FULL_ERROR_MESSAGE); 967 | assert_eq!(server.connections.len(), 10); 968 | { 969 | // Drop this stream. 970 | let _refused_stream = sockets.pop().unwrap(); 971 | } 972 | assert_eq!(server.connections.len(), 10); 973 | 974 | // Check that the server detects a connection shutdown. 975 | let sock: &UnixStream = sockets.first().unwrap(); 976 | sock.shutdown(Shutdown::Both).unwrap(); 977 | assert!(server.requests().unwrap().is_empty()); 978 | // Server should drop a closed connection. 979 | assert_eq!(server.connections.len(), 9); 980 | 981 | // Close the backing FD of this connection by dropping 982 | // it out of scope. 983 | { 984 | // Enforce the drop call on the stream 985 | let _sock = sockets.pop().unwrap(); 986 | } 987 | assert!(server.requests().unwrap().is_empty()); 988 | // Server should drop a closed connection. 989 | assert_eq!(server.connections.len(), 8); 990 | 991 | let sock: &UnixStream = sockets.get(1).unwrap(); 992 | // Close both the read and write sides of the socket 993 | // separately and check that the server detects it. 994 | sock.shutdown(Shutdown::Read).unwrap(); 995 | sock.shutdown(Shutdown::Write).unwrap(); 996 | assert!(server.requests().unwrap().is_empty()); 997 | // Server should drop a closed connection. 998 | assert_eq!(server.connections.len(), 7); 999 | } 1000 | 1001 | #[test] 1002 | fn test_wait_parse_error() { 1003 | let path_to_socket = get_temp_socket_file(); 1004 | 1005 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 1006 | server.start_server().unwrap(); 1007 | 1008 | // Test one incoming connection. 1009 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 1010 | socket.set_nonblocking(true).unwrap(); 1011 | assert!(server.requests().unwrap().is_empty()); 1012 | 1013 | socket 1014 | .write_all( 1015 | b"PATCH /machine-config HTTP/1.1\r\n\ 1016 | Content-Length: alpha\r\n\ 1017 | Content-Type: application/json\r\n\r\nwhatever body", 1018 | ) 1019 | .unwrap(); 1020 | 1021 | assert!(server.requests().unwrap().is_empty()); 1022 | assert!(server.requests().unwrap().is_empty()); 1023 | let mut buf: [u8; 255] = [0; 255]; 1024 | assert!(socket.read(&mut buf[..]).unwrap() > 0); 1025 | let error_message = b"HTTP/1.1 400 \r\n\ 1026 | Server: Firecracker API\r\n\ 1027 | Connection: keep-alive\r\n\ 1028 | Content-Type: application/json\r\n\ 1029 | Content-Length: 136\r\n\r\n{ \"error\": \"Invalid header. \ 1030 | Reason: Invalid value. Key:Content-Length; Value: alpha\nAll previous unanswered requests will be dropped.\" }"; 1031 | assert_eq!(&buf[..], &error_message[..]); 1032 | 1033 | socket 1034 | .write_all( 1035 | b"PATCH /machine-config HTTP/1.1\r\n\ 1036 | Content-Length: alpha\r\n\ 1037 | Content-Type: application/json\r\n\r\nwhatever body", 1038 | ) 1039 | .unwrap(); 1040 | } 1041 | 1042 | #[test] 1043 | fn test_wait_in_flight_responses() { 1044 | let path_to_socket = get_temp_socket_file(); 1045 | 1046 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 1047 | server.start_server().unwrap(); 1048 | 1049 | // Test a connection dropped and then a new one appearing 1050 | // before the user had a chance to send the response to the 1051 | // first one. 1052 | let mut first_socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 1053 | assert!(server.requests().unwrap().is_empty()); 1054 | 1055 | first_socket 1056 | .write_all( 1057 | b"PATCH /machine-config HTTP/1.1\r\n\ 1058 | Content-Length: 13\r\n\ 1059 | Content-Type: application/json\r\n\r\nwhatever body", 1060 | ) 1061 | .unwrap(); 1062 | 1063 | let mut req_vec = server.requests().unwrap(); 1064 | let server_request = req_vec.remove(0); 1065 | 1066 | first_socket.shutdown(std::net::Shutdown::Both).unwrap(); 1067 | assert!(server.requests().unwrap().is_empty()); 1068 | let mut second_socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 1069 | second_socket.set_nonblocking(true).unwrap(); 1070 | assert!(server.requests().unwrap().is_empty()); 1071 | 1072 | server 1073 | .enqueue_responses(vec![server_request.process(|_request| { 1074 | let mut response = Response::new(Version::Http11, StatusCode::OK); 1075 | let response_body = b"response body"; 1076 | response.set_body(Body::new(response_body.to_vec())); 1077 | response 1078 | })]) 1079 | .unwrap(); 1080 | assert!(server.requests().unwrap().is_empty()); 1081 | assert_eq!(server.connections.len(), 1); 1082 | let mut buf: [u8; 1024] = [0; 1024]; 1083 | assert!(second_socket.read(&mut buf[..]).is_err()); 1084 | 1085 | second_socket 1086 | .write_all( 1087 | b"GET /machine-config HTTP/1.1\r\n\ 1088 | Content-Type: application/json\r\n\r\n", 1089 | ) 1090 | .unwrap(); 1091 | 1092 | let mut req_vec = server.requests().unwrap(); 1093 | let second_server_request = req_vec.remove(0); 1094 | 1095 | assert_eq!( 1096 | second_server_request.request, 1097 | Request::try_from( 1098 | b"GET /machine-config HTTP/1.1\r\n\ 1099 | Content-Type: application/json\r\n\r\n", 1100 | None 1101 | ) 1102 | .unwrap() 1103 | ); 1104 | 1105 | server 1106 | .respond(second_server_request.process(|_request| { 1107 | let mut response = Response::new(Version::Http11, StatusCode::OK); 1108 | let response_body = b"response second body"; 1109 | response.set_body(Body::new(response_body.to_vec())); 1110 | response 1111 | })) 1112 | .unwrap(); 1113 | 1114 | assert!(server.requests().unwrap().is_empty()); 1115 | let mut buf: [u8; 1024] = [0; 1024]; 1116 | assert!(second_socket.read(&mut buf[..]).unwrap() > 0); 1117 | second_socket.shutdown(std::net::Shutdown::Both).unwrap(); 1118 | assert!(server.requests().is_ok()); 1119 | } 1120 | 1121 | #[test] 1122 | fn test_kill_switch() { 1123 | let path_to_socket = get_temp_socket_file(); 1124 | 1125 | let mut server = HttpServer::new(path_to_socket.as_path()).unwrap(); 1126 | let kill_switch = EventFd::new(EFD_NONBLOCK).unwrap(); 1127 | server 1128 | .add_kill_switch(kill_switch.try_clone().unwrap()) 1129 | .unwrap(); 1130 | server.start_server().unwrap(); 1131 | 1132 | let request_result = Arc::new(Mutex::new(Ok(vec![]))); 1133 | let res_clone = request_result.clone(); 1134 | // Start a thread running the server, expect it to report shutdown event. 1135 | let handler = std::thread::spawn(move || { 1136 | *res_clone.lock().unwrap() = server.requests(); 1137 | }); 1138 | 1139 | // Trigger kill switch. 1140 | kill_switch.write(1).unwrap(); 1141 | // Then send request. 1142 | let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap(); 1143 | socket 1144 | .write_all( 1145 | b"PATCH /machine-config HTTP/1.1\r\n\ 1146 | Content-Length: 13\r\n\ 1147 | Content-Type: application/json\r\n\r\nwhatever body", 1148 | ) 1149 | .unwrap(); 1150 | // Wait for server thread to handle events. 1151 | handler.join().unwrap(); 1152 | 1153 | // Expect shutdown event instead of http request event. 1154 | let res = request_result.lock().unwrap(); 1155 | assert_eq!( 1156 | res.as_ref().unwrap_err(), 1157 | &ServerError::ShutdownEvent, 1158 | "Expected shutdown event, instead got {res:?}" 1159 | ); 1160 | } 1161 | } 1162 | -------------------------------------------------------------------------------- /src/vmm/src/vmm_config/mmds.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use serde::{export::Formatter, Deserialize}; 5 | use std::fmt::{Display, Result}; 6 | use std::net::Ipv4Addr; 7 | 8 | /// Keeps the MMDS configuration. 9 | #[derive(Debug, Deserialize, PartialEq)] 10 | #[serde(deny_unknown_fields)] 11 | pub struct MmdsConfig { 12 | /// MMDS IPv4 configured address. 13 | ipv4_address: Option, 14 | } 15 | 16 | impl MmdsConfig { 17 | /// Returns the MMDS IPv4 address if one was configured. 18 | /// Otherwise returns None. 19 | pub fn ipv4_addr(&self) -> Option { 20 | self.ipv4_address 21 | } 22 | } 23 | --------------------------------------------------------------------------------