├── .gitignore ├── .hgignore ├── .travis.yml ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.rst ├── codegen ├── branchify.rs ├── main.rs ├── read_method.rs └── status.rs ├── comparisons ├── README.txt ├── apache_fake.go ├── apache_fake.js └── run.py ├── documentation └── client-plan.rst ├── examples ├── apache_fake.rs ├── client.rs ├── hello_world.rs ├── info.rs ├── one_time_server.rs └── request_uri.rs └── src └── http ├── buffer.rs ├── client ├── mod.rs ├── request.rs ├── response.rs ├── sslclients │ ├── mod.rs │ ├── none.rs │ └── openssl.rs └── tests.rs ├── common.rs ├── connecter.rs ├── headers ├── accept_ranges.rs ├── connection.rs ├── content_type.rs ├── etag.rs ├── host.rs ├── mod.rs ├── serialization_utils.rs ├── test_utils.rs └── transfer_encoding.rs ├── lib.rs ├── memstream.rs ├── method.rs ├── rfc2616.rs └── server ├── mod.rs ├── request.rs └── response.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.dSYM/ 3 | *.swp 4 | *.lock 5 | src/http/generated/ 6 | target/ 7 | doc/ 8 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | \.dSYM/ 3 | \.swp$ 4 | ^src/http/generated/ 5 | ^bin/ 6 | ^build/ 7 | ^TAGS$ 8 | ^doc/http/ 9 | ^doc/src/http/ 10 | ^doc/\.lock$ 11 | ^doc/.*\.js$ 12 | ^doc/.*\.css$ 13 | ^lib/ 14 | ^.rust/ 15 | ^Makefile$ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | env: 3 | global: 4 | - secure: cyOddoEksDnFv8mpEByRqyk5SNw1d0Wgx0HLgSg7+CEYDwZNrxODbK1k3KTkALD9nLb5Sr8Rol2vhJkjr6EY8IAXnX1QBVjHSeRe8wLkDg/TVpxKcTIF/Mw96N27MtvT+3c17AtbR6zG4yrsfgHTWc8HQOul0lJGnsxIj1N+DWM= 5 | script: 6 | - cargo build -v 7 | - cargo test -v 8 | - cargo doc --no-deps -v 9 | - mv target/doc doc 10 | after_script: 11 | - curl http://www.rust-ci.org/artifacts/put?t=$RUSTCI_TOKEN | sh 12 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | This project is dual-licensed under the terms of the MIT and Apache (version 2.0) licenses. 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http" 3 | version = "0.1.0-pre" 4 | authors = ["Chris Morgan "] 5 | description = "Obsolete HTTP library" 6 | #documentation = 7 | #homepage = 8 | repository = "https://github.com/chris-morgan/rust-http" 9 | readme = "README.rst" 10 | keywords = ["web", "http", "library"] 11 | license = "MIT/Apache-2.0" 12 | build = "codegen/main.rs" 13 | 14 | [features] 15 | default = ["ssl"] 16 | ssl = ["openssl"] 17 | 18 | [lib] 19 | name = "http" 20 | path = "src/http/lib.rs" 21 | 22 | [dependencies] 23 | url = "*" 24 | log = "*" 25 | time = "*" 26 | 27 | [dependencies.openssl] 28 | version = "*" 29 | optional = true 30 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2009 Graydon Hoare 2 | Copyright (c) 2009-2013 Mozilla Foundation 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | OBSOLETION NOTICE 2 | ================= 3 | 4 | This library is DEAD. It was a useful experiment and is now being 5 | replaced under the scope of the Teepee_ (experimentation 6 | grounds at the present) and Hyper_ (actual, usable HTTP stuff) projects. 7 | 8 | For some more information on what’s happened/happening with it all, see 9 | http://chrismorgan.info/blog/introducing-teepee.html. 10 | 11 | **YOU SHOULD NOT USE THIS LIBRARY AT ALL.** 12 | If you want to use HTTP, I recommend you use Hyper_ for the moment. 13 | 14 | .. _Teepee: http://teepee.rs/ 15 | .. _Hyper: https://github.com/hyperium/hyper 16 | -------------------------------------------------------------------------------- /codegen/branchify.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use std::str::Chars; 4 | use std::vec::Vec; 5 | use std::io::IoResult; 6 | use std::iter::repeat; 7 | use std::ascii::AsciiExt; 8 | 9 | #[derive(Clone)] 10 | pub struct ParseBranch { 11 | matches: Vec, 12 | result: Option, 13 | children: Vec, 14 | } 15 | 16 | impl ParseBranch { 17 | fn new() -> ParseBranch { 18 | ParseBranch { 19 | matches: Vec::new(), 20 | result: None, 21 | children: Vec::new() 22 | } 23 | } 24 | } 25 | 26 | pub fn branchify(options: &[(&str, &str)], case_sensitive: bool) -> Vec { 27 | let mut root = ParseBranch::new(); 28 | 29 | fn go_down_moses(branch: &mut ParseBranch, mut chariter: Chars, result: &str, case_sensitive: bool) { 30 | match chariter.next() { 31 | Some(c) => { 32 | let first_case = if case_sensitive { c as u8 } else { c.to_ascii_uppercase() as u8 }; 33 | for next_branch in branch.children.iter_mut() { 34 | if next_branch.matches[0] == first_case { 35 | go_down_moses(next_branch, chariter, result, case_sensitive); 36 | return; 37 | } 38 | } 39 | let mut subbranch = ParseBranch::new(); 40 | subbranch.matches.push(first_case); 41 | if !case_sensitive { 42 | let second_case = c.to_ascii_lowercase() as u8; 43 | if first_case != second_case { 44 | subbranch.matches.push(second_case); 45 | } 46 | } 47 | branch.children.push(subbranch); 48 | let i = branch.children.len() - 1; 49 | go_down_moses(&mut branch.children[i], chariter, result, case_sensitive); 50 | }, 51 | None => { 52 | assert!(branch.result.is_none()); 53 | branch.result = Some(String::from_str(result)); 54 | }, 55 | } 56 | }; 57 | 58 | for &(key, result) in options.iter() { 59 | go_down_moses(&mut root, key.chars(), result, case_sensitive); 60 | } 61 | 62 | root.children 63 | } 64 | 65 | macro_rules! branchify( 66 | (case sensitive, $($key:expr => $value:ident),*) => ( 67 | ::branchify::branchify(&[$(($key, stringify!($value))),*], true) 68 | ); 69 | (case insensitive, $($key:expr => $value:ident),*) => ( 70 | ::branchify::branchify(&[$(($key, stringify!($value))),*], false) 71 | ); 72 | ); 73 | 74 | /// Prints the contents to stdout. 75 | /// 76 | /// :param branches: the branches to search through 77 | /// :param indent: the level of indentation (each level representing four leading spaces) 78 | /// :param read_call: the function call to read a byte 79 | /// :param end: the byte which marks the end of the sequence 80 | /// :param max_len: the maximum length a value may be before giving up and returning ``None`` 81 | /// :param valid: the function call to if a byte ``b`` is valid 82 | /// :param unknown: the expression to call for an unknown value; in this string, ``{}`` will be 83 | /// replaced with an expression (literal or non-literal) evaluating to a ``String`` (it is 84 | /// ``{}`` only, not arbitrary format strings) 85 | pub fn generate_branchified_method( 86 | writer: &mut Writer, 87 | branches: Vec, 88 | indent: usize, 89 | read_call: &str, 90 | end: &str, 91 | max_len: &str, 92 | valid: &str, 93 | unknown: &str) -> IoResult<()> { 94 | 95 | fn r(writer: &mut Writer, branch: &ParseBranch, prefix: &str, indent: usize, read_call: &str, 96 | end: &str, max_len: &str, valid: &str, unknown: &str) -> IoResult<()> { 97 | let indentstr = repeat(' ').take(indent * 4).collect::(); 98 | macro_rules! w ( 99 | ($s:expr) => { 100 | try!(write!(writer, "{}{}\n", indentstr, $s)) 101 | } 102 | ); 103 | for &c in branch.matches.iter() { 104 | let next_prefix = format!("{}{}", prefix, c as char); 105 | w!(format!("Ok(b'{}') => match {} {{", c as char, read_call)); 106 | for b in branch.children.iter() { 107 | try!(r(writer, b, &next_prefix[], indent + 1, read_call, end, max_len, valid, unknown)); 108 | } 109 | match branch.result { 110 | Some(ref result) => 111 | w!(format!(" Ok(b' ') => return Ok({}),", *result)), 112 | None => w!(format!(" Ok(b' ') => return Ok({}),", 113 | unknown.replace("{}", &format!("String::from_str(\"{}\")", next_prefix)[]))), 114 | } 115 | w!(format!(" Ok(b) if {} => (\"{}\", b),", valid, next_prefix)); 116 | w!(" Ok(_) => return Err(::std::io::IoError { kind: ::std::io::OtherIoError, desc: \"bad value\", detail: None }),"); 117 | w!(" Err(err) => return Err(err),"); 118 | w!("},"); 119 | } 120 | Ok(()) 121 | } 122 | let indentstr = repeat(' ').take(indent * 4).collect::(); 123 | macro_rules! w ( 124 | ($s:expr) => { 125 | try!(write!(writer, "{}{}\n", indentstr, $s)) 126 | } 127 | ); 128 | 129 | w!(format!("let (s, next_byte) = match {} {{", read_call)); 130 | for b in branches.iter() { 131 | try!(r(writer, b, "", indent + 1, read_call, end, max_len, valid, unknown)); 132 | } 133 | w!(format!(" Ok(b) if {} => (\"\", b),", valid)); 134 | w!( (" Ok(_) => return Err(::std::io::IoError { kind: ::std::io::OtherIoError, desc: \"bad value\", detail: None }),")); 135 | w!( (" Err(err) => return Err(err),")); 136 | w!( ("};")); 137 | w!( ("// OK, that didn't pan out. Let's read the rest and see what we get.")); 138 | w!( ("let mut s = String::from_str(s);")); 139 | w!( ("s.push(next_byte as char);")); 140 | w!( ("loop {")); 141 | w!(format!(" match {} {{", read_call)); 142 | w!(format!(" Ok(b) if b == {} => return Ok({}),", end, unknown.replace("{}", "s"))); 143 | w!(format!(" Ok(b) if {} => {{", valid)); 144 | w!(format!(" if s.len() == {} {{", max_len)); 145 | w!( (" // Too long; bad request")); 146 | w!( (" return Err(::std::io::IoError { kind: ::std::io::OtherIoError, desc: \"too long, bad request\", detail: None });")); 147 | w!( (" }")); 148 | w!( (" s.push(b as char);")); 149 | w!( (" },")); 150 | w!( (" Ok(_) => return Err(::std::io::IoError { kind: ::std::io::OtherIoError, desc: \"bad value\", detail: None }),")); 151 | w!( (" Err(err) => return Err(err),")); 152 | w!( (" }")); 153 | w!( ("}")); 154 | Ok(()) 155 | } 156 | -------------------------------------------------------------------------------- /codegen/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unstable)] 2 | use std::io::{File, Truncate, Write}; 3 | use std::os; 4 | use std::thread::Thread; 5 | 6 | pub mod branchify; 7 | pub mod status; 8 | pub mod read_method; 9 | 10 | fn main() { 11 | Thread::spawn(move || { 12 | let output_dir = Path::new(os::getenv("OUT_DIR").unwrap()); 13 | read_method::generate(output_dir).unwrap(); 14 | }); 15 | 16 | let output_dir = Path::new(os::getenv("OUT_DIR").unwrap()); 17 | status::generate(output_dir).unwrap(); 18 | } 19 | 20 | pub fn get_writer(mut output_dir: Path, filename: &str) -> Box { 21 | output_dir.push(filename); 22 | match File::open_mode(&output_dir, Truncate, Write) { 23 | Ok(writer) => Box::new(writer), 24 | Err(e) => panic!("Unable to write file: {}", e.desc), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /codegen/read_method.rs: -------------------------------------------------------------------------------- 1 | use super::branchify::generate_branchified_method; 2 | use super::get_writer; 3 | use std::io::IoResult; 4 | 5 | pub fn generate(output_dir: Path) -> IoResult<()> { 6 | let mut writer = get_writer(output_dir, "read_method.rs"); 7 | try!(writer.write(b"\ 8 | // This automatically generated file is included in request.rs. 9 | pub mod dummy { 10 | use std::io::{Stream, IoResult}; 11 | use method::Method; 12 | use method::Method::{Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace, ExtensionMethod}; 13 | use server::request::MAX_METHOD_LEN; 14 | use rfc2616::{SP, is_token_item}; 15 | use buffer::BufferedStream; 16 | 17 | #[inline] 18 | pub fn read_method(stream: &mut BufferedStream) -> IoResult { 19 | ")); 20 | 21 | try!(generate_branchified_method( 22 | &mut *writer, 23 | branchify!(case sensitive, 24 | "CONNECT" => Connect, 25 | "DELETE" => Delete, 26 | "GET" => Get, 27 | "HEAD" => Head, 28 | "OPTIONS" => Options, 29 | "PATCH" => Patch, 30 | "POST" => Post, 31 | "PUT" => Put, 32 | "TRACE" => Trace 33 | ), 34 | 1, 35 | "stream.read_byte()", 36 | "SP", 37 | "MAX_METHOD_LEN", 38 | "is_token_item(b)", 39 | "ExtensionMethod({})")); 40 | writer.write(b"}\n}\n") 41 | } 42 | -------------------------------------------------------------------------------- /codegen/status.rs: -------------------------------------------------------------------------------- 1 | // These are taken from http://en.wikipedia.org/wiki/List_of_HTTP_Status_Codes. 2 | // Last updated on 2013-07-04 3 | // Entries from third-party vendors not standardised upon are not included. 4 | // If not specified otherwise, they are defined in RFC 2616. 5 | 6 | // Yes, this is ugly code. 7 | // No, I don't mind. 8 | // That was easy. :-) 9 | 10 | use std::collections::HashSet; 11 | use std::ascii::AsciiExt; 12 | use std::io::IoResult; 13 | use std::iter::repeat; 14 | use super::get_writer; 15 | 16 | use self::HeadingOrStatus::{Heading, Status}; 17 | 18 | #[derive(Copy)] 19 | enum HeadingOrStatus { 20 | Heading(&'static str), 21 | Status(HttpStatus), 22 | } 23 | 24 | #[derive(Copy)] 25 | struct HttpStatus { 26 | code: usize, 27 | reason: &'static str, 28 | comment: Option<&'static str>, 29 | } 30 | 31 | /// Status with comment 32 | fn status_c(code: usize, reason: &'static str, comment: &'static str) -> HeadingOrStatus { 33 | Status(HttpStatus { code: code, reason: reason, comment: Some(comment) }) 34 | } 35 | 36 | /// Status without comment 37 | fn status_n(code: usize, reason: &'static str) -> HeadingOrStatus { 38 | Status(HttpStatus { code: code, reason: reason, comment: None }) 39 | } 40 | 41 | impl HttpStatus { 42 | fn ident(&self) -> String { 43 | camel_case(self.reason) 44 | } 45 | 46 | fn padded_ident(&self) -> String { 47 | let mut s = self.ident(); 48 | s.push_str(&self.reason_padding_spaces()[]); 49 | s 50 | } 51 | 52 | fn reason_padding_spaces(&self) -> String { 53 | repeat(' ').take(unsafe { longest_reason } - self.reason.len()).collect() 54 | } 55 | } 56 | 57 | /// >>> camel_case("I'm a Tea-pot") 58 | /// "ImATeaPot" 59 | fn camel_case(msg: &str) -> String { 60 | let msg = msg.replace("-", " ").replace("'", ""); 61 | let mut result = String::with_capacity(msg.len()); 62 | let mut capitalise = true; 63 | for c in msg.chars() { 64 | let c = match capitalise { 65 | true => c.to_ascii_uppercase(), 66 | false => c.to_ascii_lowercase(), 67 | }; 68 | // For a space, capitalise the next char 69 | capitalise = c == ' '; 70 | if !capitalise { // And also, for a space, don't keep it 71 | result.push(c); 72 | } 73 | } 74 | result 75 | } 76 | 77 | static mut longest_ident: usize = 0; 78 | static mut longest_reason: usize = 0; 79 | 80 | pub fn generate(output_dir: Path) -> IoResult<()> { 81 | let mut out = get_writer(output_dir, "status.rs"); 82 | let entries = [ 83 | Heading("1xx Informational"), 84 | status_n(100, "Continue"), 85 | status_n(101, "Switching Protocols"), 86 | status_c(102, "Processing", "WebDAV; RFC 2518"), 87 | 88 | Heading("2xx Success"), 89 | status_n(200, "OK"), 90 | status_n(201, "Created"), 91 | status_n(202, "Accepted"), 92 | status_c(203, "Non-Authoritative Information", "since HTTP/1.1"), 93 | status_n(204, "No Content"), 94 | status_n(205, "Reset Content"), 95 | status_n(206, "Partial Content"), 96 | status_c(207, "Multi-Status", "WebDAV; RFC 4918"), 97 | status_c(208, "Already Reported", "WebDAV; RFC 5842"), 98 | status_c(226, "IM Used", "RFC 3229"), 99 | 100 | Heading("3xx Redirection"), 101 | status_n(300, "Multiple Choices"), 102 | status_n(301, "Moved Permanently"), 103 | status_n(302, "Found"), 104 | status_c(303, "See Other", "since HTTP/1.1"), 105 | status_n(304, "Not Modified"), 106 | status_c(305, "Use Proxy", "since HTTP/1.1"), 107 | status_n(306, "Switch Proxy"), 108 | status_c(307, "Temporary Redirect", "since HTTP/1.1"), 109 | status_c(308, "Permanent Redirect", "approved as experimental RFC: http://tools.ietf.org/html/draft-reschke-http-status-308"), 110 | 111 | Heading("4xx Client Error"), 112 | status_n(400, "Bad Request"), 113 | status_n(401, "Unauthorized"), 114 | status_n(402, "Payment Required"), 115 | status_n(403, "Forbidden"), 116 | status_n(404, "Not Found"), 117 | status_n(405, "Method Not Allowed"), 118 | status_n(406, "Not Acceptable"), 119 | status_n(407, "Proxy Authentication Required"), 120 | status_n(408, "Request Timeout"), 121 | status_n(409, "Conflict"), 122 | status_n(410, "Gone"), 123 | status_n(411, "Length Required"), 124 | status_n(412, "Precondition Failed"), 125 | status_n(413, "Request Entity Too Large"), 126 | status_n(414, "Request-URI Too Long"), 127 | status_n(415, "Unsupported Media Type"), 128 | status_n(416, "Requested Range Not Satisfiable"), 129 | status_n(417, "Expectation Failed"), 130 | status_c(418, "I'm a teapot", "RFC 2324"), 131 | status_n(419, "Authentication Timeout"), 132 | status_c(422, "Unprocessable Entity", "WebDAV; RFC 4918"), 133 | status_c(423, "Locked", "WebDAV; RFC 4918"), 134 | status_c(424, "Failed Dependency", "WebDAV; RFC 4918"), 135 | status_c(424, "Method Failure", "WebDAV"), 136 | status_c(425, "Unordered Collection", "Internet draft"), 137 | status_c(426, "Upgrade Required", "RFC 2817"), 138 | status_c(428, "Precondition Required", "RFC 6585"), 139 | status_c(429, "Too Many Requests", "RFC 6585"), 140 | status_c(431, "Request Header Fields Too Large", "RFC 6585"), 141 | status_c(451, "Unavailable For Legal Reasons", "Internet draft"), 142 | 143 | Heading("5xx Server Error"), 144 | status_n(500, "Internal Server Error"), 145 | status_n(501, "Not Implemented"), 146 | status_n(502, "Bad Gateway"), 147 | status_n(503, "Service Unavailable"), 148 | status_n(504, "Gateway Timeout"), 149 | status_n(505, "HTTP Version Not Supported"), 150 | status_c(506, "Variant Also Negotiates", "RFC 2295"), 151 | status_c(507, "Insufficient Storage", "WebDAV; RFC 4918"), 152 | status_c(508, "Loop Detected", "WebDAV; RFC 5842"), 153 | status_c(510, "Not Extended", "RFC 2774"), 154 | status_c(511, "Network Authentication Required", "RFC 6585"), 155 | ]; 156 | unsafe { 157 | longest_ident = entries.iter().map(|&e| match e { 158 | Heading(_heading) => 0, 159 | Status(status) => status.ident().len(), 160 | }).max_by(|&i| i).unwrap(); 161 | longest_reason = entries.iter().map(|&e| match e { 162 | Heading(_heading) => 0, 163 | Status(status) => status.reason.len(), 164 | }).max_by(|&i| i).unwrap(); 165 | } 166 | try!(out.write("// This file is automatically generated file is used as http::status. 167 | 168 | pub mod status { 169 | use std::fmt; 170 | use std::ascii::AsciiExt; 171 | use std::num::{ToPrimitive, FromPrimitive}; 172 | 173 | pub use self::Status::*; 174 | 175 | /// HTTP status code 176 | #[derive(Eq, PartialEq, Clone)] 177 | pub enum Status { 178 | ".as_bytes())); 179 | for &entry in entries.iter() { 180 | match entry { 181 | Heading(heading) => try!(write!(out, "\n // {}\n", heading)), 182 | Status(status) => match status.comment { 183 | None => try!(write!(out, " {},\n", status.ident())), 184 | Some(comment) => try!(write!(out, " {}, // {}\n", status.ident(), comment)), 185 | }, 186 | } 187 | } 188 | 189 | try!(out.write(" 190 | UnregisteredStatus(u16, String), 191 | } 192 | 193 | impl Status { 194 | 195 | /// Get the status code 196 | pub fn code(&self) -> u16 { 197 | match *self { 198 | ".as_bytes())); 199 | for &entry in entries.iter() { 200 | match entry { 201 | Heading(heading) => try!(write!(out, "\n // {}\n", heading)), 202 | Status(status) => try!(write!(out, " {} => {},\n", 203 | status.padded_ident(), status.code)), 204 | } 205 | } 206 | try!(out.write(" 207 | UnregisteredStatus(code, _) => code, 208 | } 209 | } 210 | 211 | /// Get the reason phrase 212 | pub fn reason(&self) -> String { 213 | match *self { 214 | ".as_bytes())); 215 | for &entry in entries.iter() { 216 | match entry { 217 | Heading(heading) => try!(write!(out, "\n // {}\n", heading)), 218 | Status(status) => try!(write!(out, " {} => String::from_str(\"{}\"),\n", 219 | status.padded_ident(), status.reason)) 220 | } 221 | } 222 | try!(out.write(" 223 | UnregisteredStatus(_, ref reason) => (*reason).clone(), 224 | } 225 | } 226 | 227 | /// Get a status from the code and reason 228 | pub fn from_code_and_reason(status: u16, reason: String) -> Status { 229 | let reason_lower = reason.to_ascii_lowercase(); 230 | match (status, &reason_lower[]) { 231 | ".as_bytes())); 232 | for &entry in entries.iter() { 233 | match entry { 234 | Heading(heading) => try!(write!(out, "\n // {}\n", heading)), 235 | Status(status) => try!(write!(out, " ({}, \"{}\"){} => {},\n", 236 | status.code, 237 | status.reason.to_ascii_lowercase(), 238 | status.reason_padding_spaces(), 239 | status.ident())), 240 | } 241 | } 242 | try!(out.write(" 243 | (_, _) => UnregisteredStatus(status, reason), 244 | } 245 | } 246 | } 247 | 248 | impl fmt::Show for Status { 249 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 250 | write!(f, \"{} {}\", self.code(), self.reason()) 251 | } 252 | } 253 | 254 | impl ToPrimitive for Status { 255 | 256 | /// Equivalent to `Some(self.code() as i64)` 257 | fn to_i64(&self) -> Option { 258 | Some(self.code() as i64) 259 | } 260 | 261 | /// Equivalent to `Some(self.code() as u64)` 262 | fn to_u64(&self) -> Option { 263 | Some(self.code() as u64) 264 | } 265 | } 266 | 267 | impl FromPrimitive for Status { 268 | /// Get a *registered* status code from the number of its status code. 269 | /// 270 | /// This will return None if an unknown (or negative, which are invalid) code is passed. 271 | /// 272 | /// For example, `from_i64(200)` will return `OK`. 273 | fn from_i64(n: i64) -> Option { 274 | if n < 0 { 275 | None 276 | } else { 277 | FromPrimitive::from_u64(n as u64) 278 | } 279 | } 280 | 281 | /// Get a *registered* status code from the number of its status code. 282 | /// 283 | /// This will return None if an unknown code is passed. 284 | /// 285 | /// For example, `from_u64(200)` will return `OK`. 286 | fn from_u64(n: u64) -> Option { 287 | Some(match n { 288 | ".as_bytes())); 289 | let mut matched_numbers = HashSet::new(); 290 | for &entry in entries.iter() { 291 | match entry { 292 | Heading(heading) => try!(write!(out, "\n // {}\n", heading)), 293 | Status(status) => { 294 | if !matched_numbers.contains(&status.code) { 295 | // Purpose: FailedDependency and MethodFailure both use 424, 296 | // but clearly they mustn't both go in here 297 | try!(write!(out, " {} => {},\n", status.code, status.ident())); 298 | matched_numbers.insert(status.code); 299 | } 300 | }, 301 | } 302 | } 303 | out.write(" 304 | _ => { return None } 305 | }) 306 | } 307 | } 308 | }".as_bytes()) 309 | } 310 | -------------------------------------------------------------------------------- /comparisons/README.txt: -------------------------------------------------------------------------------- 1 | Comparisons 2 | =========== 3 | 4 | Performance is important. Here are some comparisons with other languages and 5 | environments. 6 | 7 | Note that these examples may not be *precisely* equivalent to their Rust 8 | counterparts as their server frameworks may insert extra headers. They should 9 | not be using such significant differences as chunked encodings, though, which 10 | would really render the comparison unfair. 11 | 12 | The examples shown below are not in any way scientific tests; I have not run 13 | them in any controlled environment whatsoever—just on my own personal machine. 14 | 15 | How to run the examples 16 | ----------------------- 17 | 18 | Any Node examples:: 19 | 20 | node ___.js 21 | 22 | Any Go examples:: 23 | 24 | go run ___.go 25 | 26 | Results 27 | ======= 28 | 29 | Apache fake 30 | ----------- 31 | 32 | This test is designed to produce much the same request as the default page 33 | Apache HTTP server serves. (Or did, before I updated from Ubuntu 13.04 to 13.10 34 | and Apache config got broken.) 35 | 36 | :Hardware: Over-six-year-old Core 2 Duo laptop with 4GB ("plenty") of RAM 37 | :OS: Ubuntu 13.10 (alpha) 64-bit 38 | :Rust version: 0.9-pre (11b0784 2013-11-12 02:31:15 -0800) 39 | :Node version: 0.10.20 40 | :Go version: 1.1.2 41 | 42 | ``ab`` (new connections) 43 | ```````````````````````` 44 | 45 | :: 46 | 47 | ab -n 10000 -c 1 http://127.0.0.1:8001/ 48 | 49 | (For higher concurrency, alter the value of ``-c``.) 50 | 51 | =========== ==== ===== ==== 52 | Concurrency Node Go Rust 53 | =========== ==== ===== ==== 54 | 1 4300 4100 4825 55 | 2 5300 9500 5640 56 | 3 5450 10000 6575 57 | 4 5650 10500 7100 58 | 8 5750 10750 7900 59 | =========== ==== ===== ==== 60 | 61 | ``ab -k`` (keep-alive) also works, but gets 5-10% *worse* performance, rather 62 | than very significantly better, for some as-yet-unassessed reason. 63 | 64 | ``wrk`` (same connection) 65 | ````````````````````````` 66 | 67 | *This benchmark is not currently automated and IS OUT OF DATE (the expected 68 | change is that Rust should now be faring much better for higher concurrency).* 69 | 70 | Ten seconds of benchmarking, with one connection kept per thread. 71 | 72 | :: 73 | 74 | wrk --connections 1 --duration 10s --threads 1 http://127.0.0.1:8001/ 75 | 76 | (For higher concurrency, alter the value of both ``-c`` and ``-t``.) 77 | 78 | =========== ===== ===== ===== 79 | Concurrency Node Go Rust 80 | =========== ===== ===== ===== 81 | 1 10400 8600 10100 82 | 2 11400 21000 12300 83 | 3 11900 22000 13300 84 | 8 12400 21000 15000 85 | =========== ===== ===== ===== 86 | 87 | Conclusions 88 | =========== 89 | 90 | Single request performance is now distinctly better than Node and Go. This is a 91 | Good Thing™. However, with higher concurrency, Rust is not faring as well as it 92 | might, demonstrating a speedup of around 1.2× for concurrency of 2, rather than 93 | the theoretical optimum of 2× (I don't understand how or why, but Go is getting 94 | 2.1× consistently!). Still, by the time it gets to concurrency of 8, speedup is 95 | to about 1.65×, which is not bad; certainly it is better than Node. 96 | 97 | Further tests need to be done, of course, benchmarking other servers for 98 | comparison and other tech stacks, to ensure that it's all fair. 99 | 100 | There is scope for further performance increases at all levels, among which I 101 | include the Rust scheduler, the TCP library and writer interface (that is, 102 | mostly, the libuv interfacing, things like requiring fewer allocations) and my 103 | server library. There are a number of things known to be suboptimal which will 104 | be improved. (Request reading is still not great, for example, and accounts for 105 | about a third of the time taken with both ``wrk`` and ``ab``.) 106 | 107 | Although my server library claims to be HTTP/1.1, it is by no means a compliant 108 | HTTP/1.1 server; many things are not yet implemented (e.g. full request 109 | reading, transfer codings). However, the general framework is in place and 110 | general performance characteristics (as far as rust-http is concerned, 111 | excluding improvements in the Rust scheduler) are not expected to change 112 | radically. (By that I mean that I don’t believe any new things I add should 113 | slow it down enormously.) 114 | -------------------------------------------------------------------------------- /comparisons/apache_fake.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "runtime" 6 | ) 7 | 8 | var helloWorld = []byte("

It works!

\n

This is the default web page for this server.

\n

The web server software is running but no content has been added, yet.

\n\n") 9 | 10 | func main() { 11 | runtime.GOMAXPROCS(runtime.NumCPU()) 12 | 13 | http.HandleFunc("/", apacheFakeHandler) 14 | http.ListenAndServe(":8001", nil) 15 | } 16 | 17 | func apacheFakeHandler(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "text/html") 19 | w.Header().Set("Server", "Apache/2.2.22 (Ubuntu)") 20 | w.Header().Set("Last-Modified", "Thu, 05 May 2011 11:46:42 GMT") 21 | w.Header().Set("ETag", "\"501b29-b1-4a285ed47404a\"") 22 | w.Header().Set("Accept-Ranges", "bytes") 23 | w.Header().Set("Content-Length", "177") 24 | w.Header().Set("Vary", "Accept-Encoding") 25 | w.Header().Set("X-Pad", "avoid browser bug") 26 | 27 | w.Write(helloWorld) 28 | } 29 | -------------------------------------------------------------------------------- /comparisons/apache_fake.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | 3 | http.createServer(function(request, response) { 4 | response.writeHead(200, { 5 | "Content-Type": "text/html", 6 | "Server": "Apache/2.2.22 (Ubuntu)", 7 | "Last-Modified": "Thu, 05 May 2011 11:46:42 GMT", 8 | "ETag": "\"501b29-b1-4a285ed47404a\"", 9 | "Accept-Ranges": "bytes", 10 | "Content-Length": "177", 11 | "Vary": "Accept-Encoding", 12 | "X-Pad": "avoid browser bug", 13 | }); 14 | 15 | response.end("

It works!

\n

This is the default web page for this server.

\n

The web server software is running but no content has been added, yet.

\n\n"); 16 | }).listen(8001); 17 | -------------------------------------------------------------------------------- /comparisons/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (python2 or python3, don’t mind which) 3 | '''Comparison benchmarker.''' 4 | 5 | from __future__ import print_function 6 | from __future__ import unicode_literals 7 | 8 | import os 9 | import subprocess 10 | import sys 11 | import time 12 | try: 13 | from urllib.request import urlopen 14 | from urllib.error import URLError 15 | except ImportError: # py2k 16 | from urllib2 import urlopen 17 | from urllib2 import URLError 18 | from contextlib import contextmanager 19 | 20 | 21 | @contextmanager 22 | def tempmsg(msg): 23 | ''' 24 | Write a message to the current line of the terminal and then erase the 25 | entire line when exiting the block. 26 | ''' 27 | try: 28 | sys.stdout.write(msg) 29 | sys.stdout.flush() 30 | yield 31 | finally: 32 | sys.stdout.write('\r') 33 | sys.stdout.flush() 34 | 35 | 36 | class ServerRunner(object): 37 | '''Abstract base class for server benchmark runners.''' 38 | 39 | PLATFORM = NotImplemented 40 | 41 | # When starting, check that the server is serving on HTTP every N seconds 42 | START_CHECK_FREQUENCY = 0.2 # seconds 43 | 44 | START_CHECK_TIMEOUT = 2 # seconds 45 | 46 | # How long should we wait after killing before going on to the next thing? 47 | KILL_DELAY = 0.5 # seconds 48 | 49 | def __init__(self, name, source, build_dir, hostname, port): 50 | self.name = name 51 | self.source = source 52 | self.build_dir = build_dir 53 | self.hostname = hostname 54 | self.port = port 55 | 56 | @property 57 | def root_url(self): 58 | '''Get the root URL that the server is serving to.''' 59 | if self.port == 80: 60 | return 'http://{}/'.format(self.hostname) 61 | else: 62 | return 'http://{}:{}/'.format(self.hostname, self.port) 63 | 64 | def get_server_process_details(self): 65 | '''Get the (image_name, args) of the subprocess to spawn.''' 66 | raise NotImplementedError() 67 | 68 | def compile_server(self): 69 | '''Compile the server, if such a step is necessary.''' 70 | 71 | # This method left intentionally blank. 72 | 73 | def spawn_server(self): 74 | ''' 75 | Start running the server. 76 | 77 | :returns: the :class:`subprocess.Popen` object pertaining to it. 78 | ''' 79 | args = self.get_server_process_details() 80 | process = subprocess.Popen(args, 81 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 82 | for _ in range(int(self.START_CHECK_TIMEOUT / 83 | self.START_CHECK_FREQUENCY)): 84 | time.sleep(self.START_CHECK_FREQUENCY) 85 | try: 86 | urlopen(self.root_url) 87 | except URLError: 88 | pass 89 | else: 90 | break 91 | return process 92 | 93 | @contextmanager 94 | def activate(self): 95 | ''' 96 | A context manager during which the server is running. 97 | 98 | Compilation with :meth:`compile_server` must already have been done. 99 | ''' 100 | process = self.spawn_server() 101 | try: 102 | yield 103 | finally: 104 | process.kill() 105 | time.sleep(self.KILL_DELAY) 106 | 107 | 108 | class GoServerRunner(ServerRunner): 109 | '''A runner for Go servers.''' 110 | 111 | PLATFORM = 'go' 112 | 113 | def __init__(self, *args, **kwargs): 114 | super(GoServerRunner, self).__init__(*args, **kwargs) 115 | assert self.source.endswith('.go'), 'source must end in ".go"' 116 | self.bin_name = 'go-' + os.path.basename(self.source[:-3]) 117 | 118 | def compile_server(self): 119 | subprocess.Popen(('go', 'build', 120 | '-o', os.path.join(self.build_dir, self.bin_name), 121 | self.source)).communicate() 122 | 123 | def get_server_process_details(self): 124 | return os.path.join(self.build_dir, self.bin_name), 125 | 126 | 127 | class NodeServerRunner(ServerRunner): 128 | '''A runner for Node servers.''' 129 | 130 | PLATFORM = 'node' 131 | 132 | def get_server_process_details(self): 133 | return 'node', self.source 134 | 135 | 136 | class RustServerRunner(ServerRunner): 137 | '''A runner for Rust servers.''' 138 | 139 | PLATFORM = 'rust' 140 | 141 | # e.g. x86_64-unknown-linux-gnu 142 | rustc_version = subprocess.Popen(('rustc', '--version'), 143 | stdout=subprocess.PIPE).communicate()[0] 144 | HOST = rustc_version.split(b'host: ')[1].rstrip() 145 | 146 | def __init__(self, *args, **kwargs): 147 | super(RustServerRunner, self).__init__(*args, **kwargs) 148 | assert self.source.endswith('.rs'), 'source must end in ".rs"' 149 | # Designed for the .../x/main.rs pattern (from rustpkg), to get x. 150 | self.bin_name = os.path.basename(os.path.dirname(self.source)) 151 | 152 | def compile_server(self): 153 | subprocess.Popen(('rustc', 154 | '--opt-level=3', self.source, 155 | '-Z', 'lto', 156 | #'-Z', 'no-landing-pads', 157 | #'--out-dir', self.build_dir, 158 | '-L', '../target', 159 | # Just in case it was built with openssl support. This should 160 | # really be done better, based on the Makefile contents. 161 | '-L', '../../rust-openssl/target', 162 | # Sorry, this main.rs business needs me to do this, or use rustpkg: 163 | '-o', os.path.join(self.build_dir, self.bin_name))).communicate() 164 | 165 | def get_server_process_details(self): 166 | return os.path.join(self.build_dir, self.bin_name), 167 | 168 | 169 | class ServerRunnerCollection(object): 170 | r''' 171 | A collection of :class:`ServerRunner`\ s, all with the same configuration. 172 | 173 | This is making the assumption that all of the examples are applicable to 174 | all of the server classes. 175 | ''' 176 | 177 | # Eek! Plenty of code duplication! Metaclasses could fix this if I felt 178 | # like it, but it makes it much more convoluted. I'll just wait until I do 179 | # it in Rust, then I can use macros for it, and *much* more conveniently. 180 | def __init__(self, name, skip=(), *args, **kwargs): 181 | if 'go' not in skip: 182 | self.go = GoServerRunner( 183 | source=name + '.go', 184 | name=name, *args, **kwargs) 185 | if 'node' not in skip: 186 | self.node = NodeServerRunner( 187 | source=name + '.js', 188 | name=name, *args, **kwargs) 189 | if 'rust' not in skip: 190 | self.rust = RustServerRunner( 191 | source='../src/examples/server/{}/main.rs'.format(name), 192 | name=name, *args, **kwargs) 193 | 194 | def __iter__(self): 195 | if hasattr(self, 'go'): 196 | yield self.go 197 | if hasattr(self, 'node'): 198 | yield self.node 199 | if hasattr(self, 'rust'): 200 | yield self.rust 201 | 202 | def run_bencher_on_all(self, bencher, concurrency): 203 | ''' 204 | Run each of the servers in the collection with the given bencher. 205 | 206 | :yields: (server runner, bench results) 207 | ''' 208 | for server_runner in self: 209 | yield (server_runner, 210 | bencher.start_server_and_bench(server_runner, concurrency)) 211 | 212 | 213 | class ServerBencher(object): 214 | 215 | TOOL = NotImplemented 216 | 217 | def start_server_and_bench(self, server_runner, concurrency): 218 | '''Start the (already compiled) server runner and run the tests.''' 219 | with tempmsg( 220 | 'Running {} benchmark of {} {} server at concurrency {}...' 221 | .format(self.TOOL, server_runner.PLATFORM, server_runner.name, 222 | concurrency)): 223 | with server_runner.activate(): 224 | return self.bench(server_runner, concurrency) 225 | 226 | def bench(self, server_runner, concurrency): 227 | ''' 228 | Actually run the tests. Requires the server to be started. 229 | 230 | Must be implemented by subclasses. 231 | ''' 232 | raise NotImplementedError() 233 | 234 | 235 | class ApacheBenchServerBencher(ServerBencher): 236 | 237 | TOOL = 'ab' 238 | 239 | def __init__(self, bin='ab', keep_alive=False): 240 | self.bin = bin 241 | self.keep_alive = keep_alive 242 | if keep_alive: 243 | self.TOOL = 'ab-keep-alive' 244 | 245 | def bench(self, server_runner, concurrency): 246 | args = [self.bin, '-n', '100000', '-c', str(concurrency)] 247 | if self.keep_alive: 248 | args.append('-k') 249 | args.append(server_runner.root_url) 250 | process = subprocess.Popen(args, stdout=subprocess.PIPE, 251 | stderr=subprocess.PIPE) 252 | stdout, stderr = process.communicate() 253 | # Might fail here if it failed. Meh; no point catching it, let it fail. 254 | rps_line = next(line for line in stdout.split(b'\n') 255 | if line.startswith(b'Requests per second:')) 256 | # Matches the 2323.84 part of: 257 | # Requests per second: 2323.84 [#/sec] (mean) 258 | return float(rps_line.split()[3]) 259 | 260 | 261 | class WrkServerBencher(ServerBencher): 262 | 263 | TOOL = 'wrk' 264 | 265 | def __init__(self, bin='wrk'): 266 | self.bin = bin 267 | 268 | def bench(self, server_runner, concurrency): 269 | process = subprocess.Popen( 270 | # defaults: --duration 10s --threads 2 --connections 10 271 | (self.bin, 272 | '--threads', str(concurrency), 273 | '--connections', str(concurrency), 274 | server_runner.root_url), 275 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 276 | stdout, stderr = process.communicate() 277 | # Might fail here if it failed. Meh; no point catching it, let it fail. 278 | rps_line = next(line for line in stdout.split(b'\n') 279 | if line.startswith(b'Requests/sec: ')) 280 | # Matches the 106353.48 part of: 281 | # Requests/sec: 106353.48 282 | return float(rps_line.split()[1]) 283 | 284 | 285 | def runners_benchers_cross_product(runners, benchers, concurrency): 286 | ''' 287 | Run all combinations of runners (a :class:`ServerRunnerCollection`) and 288 | benchers (any iterable). 289 | 290 | :yields: (runner, bencher, results) tuples 291 | ''' 292 | for bencher in benchers: 293 | for runner, results in runners.run_bencher_on_all(bencher, concurrency): 294 | yield runner, bencher, results 295 | 296 | 297 | def main(): 298 | ab = ApacheBenchServerBencher(keep_alive=False) 299 | ab_k = ApacheBenchServerBencher(keep_alive=True) 300 | wrk = WrkServerBencher() 301 | benchers = [ab, ab_k, wrk] 302 | 303 | for server_name in ('apache_fake',): 304 | runners = ServerRunnerCollection( 305 | name=server_name, 306 | build_dir='../target', 307 | hostname='127.0.0.1', 308 | port='8001') 309 | for runner in runners: 310 | with tempmsg('Compiling {} {} server...' 311 | .format(runner.PLATFORM, runner.name)): 312 | runner.compile_server() 313 | 314 | for concurrency in (1, 2, 3, 4, 8, 16, 32): 315 | for runner, bencher, result in runners_benchers_cross_product( 316 | runners, benchers, concurrency): 317 | print(' '.join((runner.PLATFORM, str(concurrency), 318 | bencher.TOOL, str(result)))) 319 | 320 | 321 | if __name__ == '__main__': 322 | main() 323 | -------------------------------------------------------------------------------- /documentation/client-plan.rst: -------------------------------------------------------------------------------- 1 | HTTP client plan 2 | ================ 3 | 4 | A large source of inspiration for this should be requests_, but we will head in 5 | a quite different direction from it. 6 | 7 | In the first stage of The Plan, I will have ``http::client`` (new code) and 8 | ``http::server`` (currently most of ``rusthttpserver``). Some things currently 9 | in ``rusthttpserver`` which are definitely common to client and server with no 10 | difference, such as the ``headers`` and ``method`` modules, will go in a shared 11 | location (``http::*``, probably). *Package separation state:* complete. 12 | 13 | Types: 14 | 15 | - ``RequestWriter``, which satisfies ``std::io::Writer``, but also provides 16 | various convenience methods for managing other ways of doing things. 17 | 18 | To begin with, the client user will formulate an ``http::client::Request``. 19 | (Initially it will be different from ``http::server::Request``, though the two 20 | will share quite a lot of functionality; whether it will be a different struct 21 | in the end, I do not know; Go has its ``net/http.Request`` shared between client 22 | and server, but then has a couple of fields either way which it is not valid to 23 | set in certain ways for a request or a response.) 24 | 25 | When the user has constructed his ``Request``, he will call the synchronous 26 | ``Request.send()``. (Connection pooling and persistent session management for 27 | things like cookies and authentication is out of scope for the first version; 28 | it can come later.) This may raise a condition (from upstream or a new 29 | one—compare with requests' ``requests.exceptions`` which contains some 30 | necessary things). It returns an ``Option``. 31 | 32 | The ``Response`` has an API very strongly reminiscent of 33 | ``http::server::ResponseWriter``, but does need to be separate, being a reader 34 | (and yes, a ``Reader`` for reading the body) rather than a writer. 35 | 36 | Character set determination needs to be able to be done somewhere; it can 37 | probably be a wrapper about a ``Reader``. 38 | 39 | The initial API will be very simple, with ``Request::new(Method, Url)`` and the 40 | use of string typing for headers:: 41 | 42 | extern crate http; 43 | use http::client::Request; 44 | use http::method::Get; 45 | use extra::url::Url; 46 | 47 | let mut request = Request::new(Get, from_str("http://rust-lang.org")); 48 | request.headers.insert(~"Connection", ~"close"); 49 | request.headers.insert(~"Referer", ~"https://google.com/"); 50 | let mut response = request.send(); 51 | assert_eq!(response.status, status::Ok); 52 | assert_eq!(response.version, (1, 1)); 53 | 54 | My initial feeling was that ``request.send()`` should consume ``request``, 55 | putting it in ``response.request`` as immutable, but I'm not sure if that'll 56 | work, as ``request.send()`` might return ``None``. Perhaps it would need to 57 | return a ``Result`` instead of an ``Option``. 58 | Still got to think about how to handle that, as we can't let the Request object 59 | get lost. 60 | 61 | This initial version will not provide any assistance with redirect handling; to 62 | begin with, Servo can manage that itself as we determine the best manner in 63 | which to handle it. (I'm familiar with the specs on matters of redirection, 64 | automatic or requiring user intervention, but I'm not certain how something 65 | like Servo is best designed to manage it.) 66 | 67 | .. _requests: http://python-requests.org/ 68 | -------------------------------------------------------------------------------- /examples/apache_fake.rs: -------------------------------------------------------------------------------- 1 | //! A sample HTTP server returning the same response as is returned by Apache httpd in its default 2 | //! configuration. Potentially useful for a smidgeon of performance comparison, though naturally 3 | //! Apache is doing a lot more than this does. 4 | 5 | #![crate_name = "apache_fake"] 6 | #![allow(unstable)] 7 | 8 | extern crate time; 9 | extern crate http; 10 | 11 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 12 | use std::io::Writer; 13 | 14 | use http::server::{Config, Server, Request, ResponseWriter}; 15 | use http::headers; 16 | 17 | #[derive(Clone)] 18 | struct ApacheFakeServer; 19 | 20 | impl Server for ApacheFakeServer { 21 | fn get_config(&self) -> Config { 22 | Config { bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } 23 | } 24 | 25 | fn handle_request(&self, _r: Request, w: &mut ResponseWriter) { 26 | w.headers.date = Some(time::now_utc()); 27 | w.headers.server = Some(String::from_str("Apache/2.2.22 (Ubuntu)")); 28 | //w.headers.last_modified = Some(String::from_str("Thu, 05 May 2011 11:46:42 GMT")); 29 | w.headers.last_modified = Some(time::Tm { 30 | tm_sec: 42, // seconds after the minute ~[0-60] 31 | tm_min: 46, // minutes after the hour ~[0-59] 32 | tm_hour: 11, // hours after midnight ~[0-23] 33 | tm_mday: 5, // days of the month ~[1-31] 34 | tm_mon: 4, // months since January ~[0-11] 35 | tm_year: 111, // years since 1900 36 | tm_wday: 4, // days since Sunday ~[0-6] 37 | tm_yday: 0, // days since January 1 ~[0-365] 38 | tm_isdst: 0, // Daylight Savings Time flag 39 | tm_utcoff: 0, // offset from UTC in seconds 40 | tm_nsec: 0, // nanoseconds 41 | }); 42 | w.headers.etag = Some(headers::etag::EntityTag { 43 | weak: false, 44 | opaque_tag: String::from_str("501b29-b1-4a285ed47404a") }); 45 | w.headers.accept_ranges = Some(headers::accept_ranges::RangeUnits( 46 | vec!(headers::accept_ranges::Bytes))); 47 | w.headers.content_length = Some(177); 48 | w.headers.vary = Some(String::from_str("Accept-Encoding")); 49 | w.headers.content_type = Some(headers::content_type::MediaType { 50 | type_: String::from_str("text"), 51 | subtype: String::from_str("html"), 52 | parameters: Vec::new() 53 | }); 54 | w.headers.extensions.insert(String::from_str("X-Pad"), String::from_str("avoid browser bug")); 55 | 56 | w.write(b"\ 57 |

It works!

\n\ 58 |

This is the default web page for this server.

\n\ 59 |

The web server software is running but no content has been added, yet.

\n\ 60 | \n").unwrap(); 61 | } 62 | } 63 | 64 | fn main() { 65 | ApacheFakeServer.serve_forever(); 66 | } 67 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "client"] 2 | 3 | #![allow(unstable)] 4 | #![feature(slicing_syntax)] 5 | 6 | extern crate http; 7 | extern crate url; 8 | use http::client::RequestWriter; 9 | use http::method::Get; 10 | use http::headers::HeaderEnum; 11 | use std::os; 12 | use std::str; 13 | use std::io::println; 14 | use url::Url; 15 | 16 | fn main() { 17 | format!("{:?}", Get); 18 | let args = os::args(); 19 | match args.len() { 20 | 0 => unreachable!(), 21 | 2 => make_and_print_request(&args[1][]), 22 | _ => { 23 | println!("Usage: {} URL", args[0]); 24 | return; 25 | }, 26 | }; 27 | } 28 | 29 | fn make_and_print_request(url: &str) { 30 | let url = Url::parse(url).ok().expect("Invalid URL :-("); 31 | let request: RequestWriter = RequestWriter::new(Get, url).unwrap(); 32 | 33 | println!("Request"); 34 | println!("======="); 35 | println!(""); 36 | println!("URL: {}", request.url); 37 | println!("Remote address: {:?}", request.remote_addr); 38 | println!("Method: {:?}", request.method); 39 | println!("Headers:"); 40 | for header in request.headers.iter() { 41 | println!(" - {}: {}", header.header_name(), header.header_value()); 42 | } 43 | 44 | println!(""); 45 | println!("Response"); 46 | println!("========"); 47 | println!(""); 48 | let mut response = match request.read_response() { 49 | Ok(response) => response, 50 | Err(_request) => panic!("This example can progress no further with no response :-("), 51 | }; 52 | println!("Status: {:?}", response.status); 53 | println!("Headers:"); 54 | for header in response.headers.iter() { 55 | println!(" - {}: {}", header.header_name(), header.header_value()); 56 | } 57 | println!("Body:"); 58 | let body = match response.read_to_end() { 59 | Ok(body) => body, 60 | Err(err) => panic!("Reading response failed: {}", err), 61 | }; 62 | println(str::from_utf8(&body[]).ok().expect("Uh oh, response wasn't UTF-8")); 63 | } 64 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | //! A very simple HTTP server which responds with the plain text "Hello, World!" to every request. 2 | 3 | #![crate_name = "hello_world"] 4 | #![allow(unstable)] 5 | 6 | extern crate time; 7 | extern crate http; 8 | 9 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 10 | use std::io::Writer; 11 | 12 | use http::server::{Config, Server, Request, ResponseWriter}; 13 | use http::headers::content_type::MediaType; 14 | 15 | #[derive(Clone)] 16 | struct HelloWorldServer; 17 | 18 | impl Server for HelloWorldServer { 19 | fn get_config(&self) -> Config { 20 | Config { bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } 21 | } 22 | 23 | fn handle_request(&self, _r: Request, w: &mut ResponseWriter) { 24 | w.headers.date = Some(time::now_utc()); 25 | w.headers.content_length = Some(14); 26 | w.headers.content_type = Some(MediaType { 27 | type_: String::from_str("text"), 28 | subtype: String::from_str("plain"), 29 | parameters: vec!((String::from_str("charset"), String::from_str("UTF-8"))) 30 | }); 31 | w.headers.server = Some(String::from_str("Example")); 32 | 33 | w.write(b"Hello, World!\n").unwrap(); 34 | } 35 | } 36 | 37 | fn main() { 38 | HelloWorldServer.serve_forever(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/info.rs: -------------------------------------------------------------------------------- 1 | //! A not-quite-trivial HTTP server which responds to requests by showing the request and response 2 | //! headers and any other information it has. 3 | 4 | #![crate_name = "info"] 5 | 6 | #![allow(unstable)] 7 | extern crate time; 8 | extern crate http; 9 | 10 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 11 | use std::io::Writer; 12 | 13 | use http::server::{Config, Server, Request, ResponseWriter}; 14 | use http::headers::HeaderEnum; 15 | use http::headers::content_type::MediaType; 16 | 17 | #[derive(Clone)] 18 | struct InfoServer; 19 | 20 | impl Server for InfoServer { 21 | fn get_config(&self) -> Config { 22 | Config { bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } 23 | } 24 | 25 | fn handle_request(&self, r: Request, w: &mut ResponseWriter) { 26 | w.headers.date = Some(time::now_utc()); 27 | w.headers.content_type = Some(MediaType { 28 | type_: String::from_str("text"), 29 | subtype: String::from_str("html"), 30 | parameters: vec!((String::from_str("charset"), String::from_str("UTF-8"))) 31 | }); 32 | w.headers.server = Some(String::from_str("Rust Thingummy/0.0-pre")); 33 | w.write(b"Rust HTTP server").unwrap(); 34 | 35 | w.write(b"

Request

").unwrap(); 36 | let s = format!("
37 |
Method
{:?}
38 |
Host
{:?}
39 |
Request URI
{:?}
40 |
HTTP version
{:?}
41 |
Close connection
{}
", 42 | r.method, 43 | r.headers.host, 44 | r.request_uri, 45 | r.version, 46 | r.close_connection); 47 | w.write(s.as_bytes()).unwrap(); 48 | w.write(b"

Extension headers

").unwrap(); 49 | w.write(b"").unwrap(); 50 | for header in r.headers.iter() { 51 | let line = format!("", 52 | header.header_name(), 53 | header.header_value()); 54 | w.write(line.as_bytes()).unwrap(); 55 | } 56 | w.write(b"
NameValue
{}{}
").unwrap(); 57 | w.write(b"

Body

").unwrap();
58 |         w.write(r.body.as_slice()).unwrap();
59 |         w.write(b"
").unwrap(); 60 | 61 | w.write(b"

Response

").unwrap(); 62 | let s = format!("
Status
{:?}
", w.status); 63 | w.write(s.as_bytes()).unwrap(); 64 | w.write(b"

Headers

").unwrap(); 65 | w.write(b"").unwrap(); 66 | { 67 | let h = w.headers.clone(); 68 | for header in h.iter() { 69 | let line = format!("", 70 | header.header_name(), 71 | header.header_value()); 72 | w.write(line.as_bytes()).unwrap(); 73 | } 74 | } 75 | w.write(b"
NameValue
{}{}
").unwrap(); 76 | } 77 | } 78 | 79 | fn main() { 80 | InfoServer.serve_forever(); 81 | } 82 | -------------------------------------------------------------------------------- /examples/one_time_server.rs: -------------------------------------------------------------------------------- 1 | //! A very simple HTTP server which responds to only one connection with the plain text "Hello, World!". 2 | 3 | #![crate_name = "one_time_server"] 4 | #![allow(unstable)] 5 | 6 | extern crate time; 7 | extern crate http; 8 | 9 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 10 | use std::io::Writer; 11 | 12 | use http::server::{Config, Server, Request, ResponseWriter}; 13 | use http::headers::content_type::MediaType; 14 | 15 | #[derive(Clone)] 16 | struct HelloWorldServer; 17 | 18 | impl Server for HelloWorldServer { 19 | fn get_config(&self) -> Config { 20 | Config { bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } 21 | } 22 | 23 | fn handle_request(&self, _r: Request, w: &mut ResponseWriter) { 24 | w.headers.date = Some(time::now_utc()); 25 | w.headers.content_length = Some(14); 26 | w.headers.content_type = Some(MediaType { 27 | type_: String::from_str("text"), 28 | subtype: String::from_str("plain"), 29 | parameters: vec!((String::from_str("charset"), String::from_str("UTF-8"))) 30 | }); 31 | w.headers.server = Some(String::from_str("Example")); 32 | 33 | w.write(b"Hello, World!\n").unwrap(); 34 | } 35 | } 36 | 37 | fn main() { 38 | match HelloWorldServer.serve_once(true, None) { 39 | Ok(_) => println!("done serving"), 40 | Err(e) => println!("failed to serve: {}", e) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/request_uri.rs: -------------------------------------------------------------------------------- 1 | //! An HTTP server demonstrating the probable direction of the library without actually being *in* 2 | //! the library. 3 | //! 4 | //! This demonstrates some handling of the RequestURI, which has several possibilities and for which 5 | //! the correct values depend on the method. 6 | 7 | #![crate_name = "request_uri"] 8 | #![allow(unstable)] 9 | 10 | extern crate time; 11 | extern crate http; 12 | 13 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 14 | use std::io::Writer; 15 | 16 | use http::server::{Config, Server, Request, ResponseWriter}; 17 | use http::server::request::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority}; 18 | use http::status::{BadRequest, MethodNotAllowed}; 19 | use http::method::{Get, Head, Post, Put, Delete, Trace, Options, Connect, Patch}; 20 | use http::headers::content_type::MediaType; 21 | 22 | #[derive(Clone)] 23 | struct RequestUriServer; 24 | 25 | impl Server for RequestUriServer { 26 | fn get_config(&self) -> Config { 27 | Config { bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } 28 | } 29 | 30 | fn handle_request(&self, r: Request, w: &mut ResponseWriter) { 31 | w.headers.date = Some(time::now_utc()); 32 | w.headers.server = Some(String::from_str("Rust Thingummy/0.1-pre")); 33 | 34 | match (&r.method, &r.request_uri) { 35 | (&Connect, _) => { 36 | // "This specification reserves the method name CONNECT for use with a proxy that 37 | // can dynamically switch to being a tunnel (e.g. SSL tunneling Tunneling TCP based 38 | // protocols through Web proxy servers)." Thus, not applicable. 39 | w.status = MethodNotAllowed; 40 | return 41 | }, 42 | (_, &Authority(_)) => { 43 | // "The authority form is only used by the CONNECT method." Thus, not applicable. 44 | w.status = BadRequest; 45 | return 46 | }, 47 | (&Options, &Star) => { 48 | // Querying server capabilities. That's nice and simple. I can handle these methods: 49 | // (TODO: let user code override this, providing a default method.) 50 | w.headers.allow = Some(vec!(Get, Head, Post, Put, Delete, Trace, Options, Connect, Patch)); 51 | w.headers.content_length = Some(0); 52 | return; 53 | }, 54 | (&Options, &AbsoluteUri(_)) | (&Options, &AbsolutePath(_)) => { 55 | }, 56 | (_, &AbsoluteUri(_)) | (_, &AbsolutePath(_)) => { 57 | }, 58 | (_, &Star) => { 59 | }, 60 | } 61 | 62 | w.headers.content_type = Some(MediaType { 63 | type_: String::from_str("text"), 64 | subtype: String::from_str("html"), 65 | parameters: Vec::new() 66 | }); 67 | 68 | w.write(b"Rust HTTP server").unwrap(); 69 | 70 | match r.request_uri { 71 | Star | Authority(_) => { 72 | w.status = BadRequest; 73 | // Actually, valid for the CONNECT method. 74 | }, 75 | AbsoluteUri(ref url) => { 76 | println!("absoluteURI, {}", url); 77 | //path = 78 | }, 79 | AbsolutePath(ref url) => { 80 | println!("absolute path, {}", url); 81 | //w.status = a 82 | }, 83 | } 84 | } 85 | } 86 | 87 | fn main() { 88 | RequestUriServer.serve_forever(); 89 | } 90 | -------------------------------------------------------------------------------- /src/http/buffer.rs: -------------------------------------------------------------------------------- 1 | /// Memory buffers for the benefit of `std::io::net` which has slow read/write. 2 | 3 | use std::io::{IoResult, Stream}; 4 | use std::cmp::min; 5 | use std::slice; 6 | use std::fmt::radix; 7 | use std::ptr; 8 | 9 | // 64KB chunks (moderately arbitrary) 10 | const READ_BUF_SIZE: usize = 0x10000; 11 | const WRITE_BUF_SIZE: usize = 0x10000; 12 | // TODO: consider removing constants and giving a buffer size in the constructor 13 | 14 | pub struct BufferedStream { 15 | pub wrapped: T, 16 | pub read_buffer: Vec, 17 | // The current position in the buffer 18 | pub read_pos: usize, 19 | // The last valid position in the reader 20 | pub read_max: usize, 21 | pub write_buffer: Vec, 22 | pub write_len: usize, 23 | 24 | pub writing_chunked_body: bool, 25 | } 26 | 27 | impl BufferedStream { 28 | pub fn new(stream: T) -> BufferedStream { 29 | let mut read_buffer = Vec::with_capacity(READ_BUF_SIZE); 30 | unsafe { read_buffer.set_len(READ_BUF_SIZE); } 31 | let mut write_buffer = Vec::with_capacity(WRITE_BUF_SIZE); 32 | unsafe { write_buffer.set_len(WRITE_BUF_SIZE); } 33 | BufferedStream { 34 | wrapped: stream, 35 | read_buffer: read_buffer, 36 | read_pos: 0us, 37 | read_max: 0us, 38 | write_buffer: write_buffer, 39 | write_len: 0us, 40 | writing_chunked_body: false, 41 | } 42 | } 43 | } 44 | 45 | impl BufferedStream { 46 | /// Poke a single byte back so it will be read next. For this to make sense, you must have just 47 | /// read that byte. If `self.pos` is 0 and `self.max` is not 0 (i.e. if the buffer is just 48 | /// filled 49 | /// Very great caution must be used in calling this as it will fail if `self.pos` is 0. 50 | pub fn poke_byte(&mut self, byte: u8) { 51 | match (self.read_pos, self.read_max) { 52 | (0, 0) => self.read_max = 1, 53 | (0, _) => panic!("poke called when buffer is full"), 54 | (_, _) => self.read_pos -= 1, 55 | } 56 | self.read_buffer[self.read_pos] = byte; 57 | } 58 | 59 | #[inline] 60 | fn fill_buffer(&mut self) -> IoResult<()> { 61 | assert_eq!(self.read_pos, self.read_max); 62 | self.read_pos = 0; 63 | match self.wrapped.read(self.read_buffer.as_mut_slice()) { 64 | Ok(i) => { 65 | self.read_max = i; 66 | Ok(()) 67 | }, 68 | Err(err) => { 69 | self.read_max = 0; 70 | Err(err) 71 | }, 72 | } 73 | } 74 | 75 | /// Slightly faster implementation of read_byte than that which is provided by ReaderUtil 76 | /// (which just uses `read()`) 77 | #[inline] 78 | pub fn read_byte(&mut self) -> IoResult { 79 | if self.read_pos == self.read_max { 80 | // Fill the buffer, giving up if we've run out of buffered content 81 | try!(self.fill_buffer()); 82 | } 83 | self.read_pos += 1; 84 | Ok(self.read_buffer[self.read_pos - 1]) 85 | } 86 | } 87 | 88 | impl BufferedStream { 89 | /// Finish off writing a response: this flushes the writer and in case of chunked 90 | /// Transfer-Encoding writes the ending zero-length chunk to indicate completion. 91 | /// 92 | /// At the time of calling this, headers MUST have been written, including the 93 | /// ending CRLF, or else an invalid HTTP response may be written. 94 | pub fn finish_response(&mut self) -> IoResult<()> { 95 | try!(self.flush()); 96 | if self.writing_chunked_body { 97 | try!(self.wrapped.write(b"0\r\n\r\n")); 98 | } 99 | Ok(()) 100 | } 101 | } 102 | 103 | impl Reader for BufferedStream { 104 | /// Read at most N bytes into `buf`, where N is the minimum of `buf.len()` and the buffer size. 105 | /// 106 | /// At present, this makes no attempt to fill its buffer proactively, instead waiting until you 107 | /// ask. 108 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 109 | if self.read_pos == self.read_max { 110 | // Fill the buffer, giving up if we've run out of buffered content 111 | try!(self.fill_buffer()); 112 | } 113 | let size = min(self.read_max - self.read_pos, buf.len()); 114 | slice::bytes::copy_memory(buf, &self.read_buffer[self.read_pos..self.read_pos + size]); 115 | self.read_pos += size; 116 | Ok(size) 117 | } 118 | } 119 | 120 | impl Writer for BufferedStream { 121 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 122 | if buf.len() + self.write_len > self.write_buffer.len() { 123 | // This is the lazy approach which may involve multiple writes where it's really not 124 | // warranted. Maybe deal with that later. 125 | if self.writing_chunked_body { 126 | let s = format!("{}\r\n", (radix(self.write_len + buf.len(), 16))); 127 | try!(self.wrapped.write(s.as_bytes())); 128 | } 129 | if self.write_len > 0 { 130 | try!(self.wrapped.write(&self.write_buffer[..self.write_len])); 131 | self.write_len = 0; 132 | } 133 | try!(self.wrapped.write(buf)); 134 | self.write_len = 0; 135 | if self.writing_chunked_body { 136 | try!(self.wrapped.write(b"\r\n")); 137 | } 138 | } else { 139 | unsafe { 140 | ptr::copy_memory(self.write_buffer.as_mut_ptr().offset(self.write_len as isize), 141 | buf.as_ptr(), buf.len()); 142 | } 143 | 144 | self.write_len += buf.len(); 145 | if self.write_len == self.write_buffer.len() { 146 | if self.writing_chunked_body { 147 | let s = format!("{}\r\n", radix(self.write_len, 16)); 148 | try!(self.wrapped.write(s.as_bytes())); 149 | try!(self.wrapped.write(&self.write_buffer[])); 150 | try!(self.wrapped.write(b"\r\n")); 151 | } else { 152 | try!(self.wrapped.write(&self.write_buffer[])); 153 | } 154 | self.write_len = 0; 155 | } 156 | } 157 | Ok(()) 158 | } 159 | 160 | fn flush(&mut self) -> IoResult<()> { 161 | if self.write_len > 0 { 162 | if self.writing_chunked_body { 163 | let s = format!("{}\r\n", radix(self.write_len, 16)); 164 | try!(self.wrapped.write(s.as_bytes())); 165 | } 166 | try!(self.wrapped.write(&self.write_buffer[..self.write_len])); 167 | if self.writing_chunked_body { 168 | try!(self.wrapped.write(b"\r\n")); 169 | } 170 | self.write_len = 0; 171 | } 172 | self.wrapped.flush() 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/http/client/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Modules for making HTTP requests. 4 | 5 | At present, requests must be constructed with `RequestWriter`, which does not expose a particularly 6 | nice-looking API. 7 | 8 | In the future there will be a more friendly API for making requests; the Python 9 | [Requests](http://python-requests.org/) library should be a heavy influence in this. 10 | 11 | In the mean time, what there is is not *so* bad. 12 | 13 | Oh yeah: don't expect to conveniently make any requests which need to send a request body yet. It's 14 | possible, but it's not elegant convenient yet. (Most notably, no transfer-encodings are supported.) 15 | 16 | */ 17 | 18 | pub use self::request::RequestWriter; 19 | pub use self::response::ResponseReader; 20 | pub use self::sslclients::NetworkStream; 21 | 22 | pub mod request; 23 | pub mod response; 24 | mod sslclients; 25 | -------------------------------------------------------------------------------- /src/http/client/request.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Things for the construction and sending of HTTP requests. 4 | 5 | If you want to make a request, `RequestWriter::new` is where you start, and 6 | `RequestWriter.read_response` is where you will send the request and read the response. 7 | 8 | ```rust 9 | extern crate http; 10 | extern crate url; 11 | 12 | use http::client::RequestWriter; 13 | use http::method::Get; 14 | use url::Url; 15 | 16 | fn main() { 17 | let url = Url::parse("http://example.com/").unwrap(); 18 | let request: RequestWriter = match RequestWriter::new(Get, url) { 19 | Ok(request) => request, 20 | Err(error) => panic!(":-( {}", error), 21 | }; 22 | 23 | let mut response = match request.read_response() { 24 | Ok(response) => response, 25 | Err((_request, error)) => panic!(":-( {}", error), 26 | }; 27 | // Now you have a `ResponseReader`; see http::client::response for docs on that. 28 | } 29 | ``` 30 | 31 | If you wish to send a request body (e.g. POST requests), I'm sorry to have to tell you that there is 32 | not *good* support for this yet. However, it can be done; here is an example: 33 | 34 | ```rust 35 | # extern crate url; 36 | # extern crate http; 37 | # use http::client::RequestWriter; 38 | # use http::method::Get; 39 | # use url::Url; 40 | # #[allow(unused_must_use)] 41 | # fn main() { 42 | # let url = Url::parse("http://example.com/").unwrap(); 43 | let data = b"var1=val1&var2=val2"; 44 | let mut request: RequestWriter = match RequestWriter::new(Get, url) { 45 | Ok(request) => request, 46 | Err(error) => panic!(":-( {}", error), 47 | }; 48 | 49 | request.headers.content_length = Some(data.len()); 50 | request.write(data); 51 | let response = match request.read_response() { 52 | Ok(response) => response, 53 | Err((_request, error)) => panic!(":-( {}", error), 54 | }; 55 | # } 56 | ``` 57 | 58 | */ 59 | 60 | use url::Url; 61 | use method::Method; 62 | use std::io::{IoError, IoResult}; 63 | use std::io::net::get_host_addresses; 64 | use std::io::net::ip::{SocketAddr, Ipv4Addr}; 65 | use buffer::BufferedStream; 66 | use headers::request::HeaderCollection; 67 | use headers::host::Host; 68 | use connecter::Connecter; 69 | 70 | use client::response::ResponseReader; 71 | 72 | /*impl ResponseReader { 73 | { 74 | let mut buf = [0u8, ..2000]; 75 | match stream.read(buf) { 76 | None => panic!("Read error :-("), // conditions for error interception, again 77 | Some(bytes_read) => { 78 | println!(str::from_bytes(buf[..bytes_read])); 79 | } 80 | } 81 | 82 | match response { 83 | Some(response) => Ok(response), 84 | None => Err(self), 85 | } 86 | } 87 | }*/ 88 | 89 | pub struct RequestWriter { 90 | // The place to write to (typically a network stream, which is 91 | // io::net::tcp::TcpStream or an SSL wrapper around that) 92 | stream: Option>, 93 | headers_written: bool, 94 | 95 | /// The originating IP address of the request. 96 | pub remote_addr: Option, 97 | 98 | /// The host name and IP address that the request was sent to; this must always be specified for 99 | /// HTTP/1.1 requests (or the request will be rejected), but for HTTP/1.0 requests the Host 100 | /// header was not defined, and so this field will probably be None in such cases. 101 | //host: Host, // Now headers.host 102 | 103 | /// The headers sent with the request. 104 | pub headers: HeaderCollection, 105 | 106 | /// The HTTP method for the request. 107 | pub method: Method, 108 | 109 | /// The URL being requested. 110 | pub url: Url, 111 | 112 | /// Should we use SSL? 113 | use_ssl: bool, 114 | } 115 | 116 | /// Low-level HTTP request writing support 117 | /// 118 | /// Moderately hacky, and due to current limitations in the TcpStream arrangement reading cannot 119 | /// take place until writing is completed. 120 | /// 121 | /// At present, this only supports making one request per connection. 122 | impl RequestWriter { 123 | /// Create a `RequestWriter` writing to the specified location 124 | pub fn new(method: Method, url: Url) -> IoResult> { 125 | RequestWriter::new_request(method, url, false, true) 126 | } 127 | 128 | pub fn new_request(method: Method, url: Url, use_ssl: bool, auto_detect_ssl: bool) -> IoResult> { 129 | let host = Host { 130 | name: url.domain().unwrap().to_string(), 131 | port: url.port(), 132 | }; 133 | 134 | let remote_addr = try!(url_to_socket_addr(&url, &host)); 135 | info!("using ip address {} for {}", remote_addr, host.name); 136 | 137 | fn url_to_socket_addr(url: &Url, host: &Host) -> IoResult { 138 | // Just grab the first IPv4 address 139 | let addrs = try!(get_host_addresses(&host.name[])); 140 | let addr = addrs.into_iter().find(|&a| { 141 | match a { 142 | Ipv4Addr(..) => true, 143 | _ => false 144 | } 145 | }); 146 | 147 | // TODO: Error handling 148 | let addr = addr.unwrap(); 149 | 150 | // Default to 80, using the port specified or 443 if the protocol is HTTPS. 151 | let port = match host.port { 152 | Some(p) => p, 153 | // FIXME: case insensitivity? 154 | None => if &url.scheme[] == "https" { 443 } else { 80 }, 155 | }; 156 | 157 | Ok(SocketAddr { 158 | ip: addr, 159 | port: port 160 | }) 161 | } 162 | 163 | let mut request = RequestWriter { 164 | stream: None, 165 | headers_written: false, 166 | remote_addr: Some(remote_addr), 167 | headers: HeaderCollection::new(), 168 | method: method, 169 | url: url, 170 | use_ssl: use_ssl, 171 | }; 172 | 173 | if auto_detect_ssl { 174 | // FIXME: case insensitivity? 175 | request.use_ssl = &request.url.scheme[] == "https"; 176 | } 177 | 178 | request.headers.host = Some(host); 179 | Ok(request) 180 | } 181 | } 182 | 183 | impl RequestWriter { 184 | 185 | /// Connect to the remote host if not already connected. 186 | pub fn try_connect(&mut self) -> IoResult<()> { 187 | if self.stream.is_none() { 188 | self.connect() 189 | } else { 190 | Ok(()) 191 | } 192 | } 193 | 194 | /// Connect to the remote host; fails if already connected. 195 | /// Returns ``true`` upon success and ``false`` upon failure (also use conditions). 196 | pub fn connect(&mut self) -> IoResult<()> { 197 | if !self.stream.is_none() { 198 | panic!("I don't think you meant to call connect() twice, you know."); 199 | } 200 | 201 | self.stream = match self.remote_addr { 202 | Some(addr) => { 203 | let stream = try!(Connecter::connect( 204 | addr, &self.headers.host.as_ref().unwrap().name[], self.use_ssl)); 205 | Some(BufferedStream::new(stream)) 206 | }, 207 | None => panic!("connect() called before remote_addr was set"), 208 | }; 209 | Ok(()) 210 | } 211 | 212 | /// Write the Request-Line and headers of the response, if we have not already done so. 213 | pub fn try_write_headers(&mut self) -> IoResult<()> { 214 | if !self.headers_written { 215 | self.write_headers() 216 | } else { 217 | Ok(()) 218 | } 219 | } 220 | 221 | /// Write the Status-Line and headers of the response, in preparation for writing the body. 222 | /// 223 | /// If the headers have already been written, this will fail. See also `try_write_headers`. 224 | pub fn write_headers(&mut self) -> IoResult<()> { 225 | // This marks the beginning of the response (RFC2616 §5) 226 | if self.headers_written { 227 | panic!("RequestWriter.write_headers() called, but headers already written"); 228 | } 229 | if self.stream.is_none() { 230 | try!(self.connect()); 231 | } 232 | 233 | // Write the Request-Line (RFC2616 §5.1) 234 | // TODO: get to the point where we can say HTTP/1.1 with good conscience 235 | let (question_mark, query) = match self.url.query { 236 | Some(ref query) => ("?", &query[]), 237 | None => ("", "") 238 | }; 239 | try!(write!(self.stream.as_mut().unwrap() as &mut Writer, 240 | "{:?} {}{}{} HTTP/1.0\r\n", 241 | self.method, self.url.serialize_path().unwrap(), question_mark, query)); 242 | 243 | try!(self.headers.write_all(self.stream.as_mut().unwrap())); 244 | self.headers_written = true; 245 | Ok(()) 246 | } 247 | 248 | /** 249 | * Send the request and construct a `ResponseReader` out of it. 250 | * 251 | * If the request sending fails in any way, a condition will be raised; if handled, the original 252 | * request will be returned as an `Err`. 253 | */ 254 | pub fn read_response(mut self) -> Result, (RequestWriter, IoError)> { 255 | match self.try_write_headers() { 256 | Ok(()) => (), 257 | Err(err) => return Err((self, err)), 258 | }; 259 | match self.flush() { 260 | Ok(()) => (), 261 | Err(err) => return Err((self, err)), 262 | }; 263 | match self.stream.take() { 264 | Some(stream) => ResponseReader::construct(stream, self), 265 | None => unreachable!(), // TODO: is it genuinely unreachable? 266 | } 267 | } 268 | } 269 | 270 | /// Write the request body. Note that any calls to `write()` will cause the headers to be sent. 271 | impl Writer for RequestWriter { 272 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 273 | if !self.headers_written { 274 | try!(self.write_headers()); 275 | } 276 | // TODO: decide whether using get_mut_ref() is sound 277 | // (it will cause failure if None) 278 | self.stream.as_mut().unwrap().write(buf) 279 | } 280 | 281 | fn flush(&mut self) -> IoResult<()> { 282 | // TODO: ditto 283 | self.stream.as_mut().unwrap().flush() 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/http/client/response.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Stream, IoResult, OtherIoError, IoError}; 2 | use client::request::RequestWriter; 3 | use rfc2616::{CR, LF, SP}; 4 | use common::read_http_version; 5 | use headers; 6 | use status::Status; 7 | 8 | use buffer::BufferedStream; 9 | use server::request::{RequestBuffer}; 10 | use headers::HeaderLineErr::{EndOfFile, EndOfHeaders, MalformedHeaderSyntax, 11 | MalformedHeaderValue}; 12 | 13 | pub struct ResponseReader { 14 | stream: BufferedStream, 15 | 16 | /// The request which this is a response to 17 | pub request: RequestWriter, 18 | 19 | /// The HTTP version number; typically `(1, 1)` or, less commonly, `(1, 0)`. 20 | pub version: (usize, usize), 21 | 22 | /// The HTTP status indicated in the response. 23 | pub status: Status, 24 | 25 | /// The headers received in the response. 26 | pub headers: headers::response::HeaderCollection, 27 | } 28 | 29 | fn bad_response_err() -> IoError { 30 | // TODO: IoError isn't right 31 | IoError { 32 | kind: OtherIoError, 33 | desc: "Server returned malformed HTTP response", 34 | detail: None, 35 | } 36 | } 37 | 38 | impl ResponseReader { 39 | pub fn construct(mut stream: BufferedStream, request: RequestWriter) 40 | -> Result, (RequestWriter, IoError)> { 41 | // TODO: raise condition at the points where Err is returned 42 | //let mut b = [0u8, ..4096]; 43 | //let len = stream.read(b); 44 | //println!("{}", ::std::str::from_bytes(b[..len.unwrap()])); 45 | let http_version = match read_http_version(&mut stream, &mut |b| b == SP) { 46 | Ok(nums) => nums, 47 | Err(_) => return Err((request, bad_response_err())), 48 | }; 49 | 50 | // Read the status code 51 | let mut digits = 0u8; 52 | let mut status_code = 0u16; 53 | loop { 54 | if digits == 4u8 { 55 | // Status code must be three digits long 56 | return Err((request, bad_response_err())); 57 | } 58 | match stream.read_byte() { 59 | Ok(b) if b >= b'0' && b <= b'9' => { 60 | status_code = status_code * 10 + b as u16 - '0' as u16; 61 | }, 62 | Ok(b) if b == SP => break, 63 | _ => return Err((request, bad_response_err())), 64 | } 65 | digits += 1; 66 | } 67 | 68 | // Read the status reason 69 | let mut reason = String::new(); 70 | loop { 71 | match stream.read_byte() { 72 | Ok(b) if b == CR => { 73 | if stream.read_byte() == Ok(LF) { 74 | break; 75 | } else { 76 | // Response-Line has CR without LF. Not yet resilient; TODO. 77 | return Err((request, bad_response_err())); 78 | } 79 | } 80 | Ok(b) => { 81 | reason.push(b as char); 82 | } 83 | Err(_) => return Err((request, bad_response_err())), 84 | } 85 | } 86 | 87 | // Now we sneakily slip back to server::RequestBuffer to avoid code duplication. This is 88 | // temporary, honest! 89 | // 90 | // You see, read_header and read_header_line will be replaced, as will this. The code will 91 | // not be shared between them as they will have ultra-smart parsers (probably using Ragel) 92 | // to provide fast loading of standard headers, and the set of defined headers is distinct 93 | // between a request and response. 94 | let headers = { 95 | let mut buffer = RequestBuffer::new(&mut stream); 96 | let mut headers = headers::response::HeaderCollection::new(); 97 | loop { 98 | let xxx = buffer.read_header::(); 99 | match xxx { 100 | //match buffer.read_header::() { 101 | Err(EndOfFile) => { 102 | //panic!("server disconnected, no more response to receive :-("); 103 | return Err((request, bad_response_err())); 104 | }, 105 | Err(EndOfHeaders) => break, 106 | Err(MalformedHeaderSyntax) => { 107 | return Err((request, bad_response_err())); 108 | }, 109 | Err(MalformedHeaderValue) => { 110 | println!("Bad header encountered. TODO: handle this better."); 111 | // Now just ignore the header 112 | }, 113 | Ok(header) => { 114 | headers.insert(header); 115 | }, 116 | } 117 | } 118 | headers 119 | }; 120 | 121 | Ok(ResponseReader { 122 | stream: stream, 123 | request: request, 124 | version: http_version, 125 | status: Status::from_code_and_reason(status_code, reason), 126 | headers: headers, 127 | }) 128 | } 129 | } 130 | 131 | impl Reader for ResponseReader { 132 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 133 | self.stream.read(buf) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/http/client/sslclients/mod.rs: -------------------------------------------------------------------------------- 1 | //! SSL client support. 2 | //! 3 | //! Which particular library is used depends upon the configuration used at 4 | //! compile time; at present it can only be OpenSSL (`--cfg openssl`); without 5 | //! that, you won't be able to use SSL (an attempt to make an HTTPS connection 6 | //! will return an error). 7 | 8 | #[cfg(feature = "ssl")] 9 | pub use self::openssl::NetworkStream; 10 | #[cfg(not(feature = "ssl"))] 11 | pub use self::none::NetworkStream; 12 | 13 | #[cfg(feature = "ssl")] 14 | mod openssl; 15 | #[cfg(not(feature = "ssl"))] 16 | mod none; 17 | -------------------------------------------------------------------------------- /src/http/client/sslclients/none.rs: -------------------------------------------------------------------------------- 1 | //! No SSL support (neither OpenSSL nor NSS were compiled in). 2 | 3 | use std::io::net::ip::SocketAddr; 4 | use std::io::net::tcp::TcpStream; 5 | use std::io::{IoResult, IoError, InvalidInput}; 6 | use connecter::Connecter; 7 | use self::NetworkStream::NormalStream; 8 | 9 | /// A TCP stream, plain text and with no SSL support. 10 | /// 11 | /// This build was made *without* SSL support; if you attempt to make an SSL 12 | /// connection you will receive an `IoError` of the `InvalidInput` kind. 13 | /// 14 | /// (To build with SSL support, use ``--cfg openssl`` or ``--cfg nss``.) 15 | pub enum NetworkStream { 16 | NormalStream(TcpStream), 17 | } 18 | 19 | impl Connecter for NetworkStream { 20 | fn connect(addr: SocketAddr, _host: &str, use_ssl: bool) -> IoResult { 21 | if use_ssl { 22 | Err(IoError { 23 | kind: InvalidInput, 24 | desc: "http crate was compiled without SSL support", 25 | detail: None, 26 | }) 27 | } else { 28 | let stream = try!(TcpStream::connect(addr)); 29 | Ok(NormalStream(stream)) 30 | } 31 | } 32 | } 33 | 34 | impl Reader for NetworkStream { 35 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 36 | match *self { 37 | NormalStream(ref mut ns) => ns.read(buf), 38 | } 39 | } 40 | } 41 | 42 | impl Writer for NetworkStream { 43 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 44 | match *self { 45 | NormalStream(ref mut ns) => ns.write(buf), 46 | } 47 | } 48 | 49 | fn flush(&mut self) -> IoResult<()> { 50 | match *self { 51 | NormalStream(ref mut ns) => ns.flush(), 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/http/client/sslclients/openssl.rs: -------------------------------------------------------------------------------- 1 | //! SSL support provided by OpenSSL. 2 | 3 | #[cfg(any(feature = "ssl", feature = "default"))] 4 | extern crate openssl; 5 | 6 | use std::io::net::ip::SocketAddr; 7 | use std::io::net::tcp::TcpStream; 8 | use std::io::{IoResult, IoError, ConnectionAborted, OtherIoError}; 9 | use self::openssl::ssl::{SslStream, SslContext, SslMethod, Ssl}; 10 | use self::openssl::ssl::error::{SslError, StreamError, SslSessionClosed, OpenSslErrors}; 11 | use self::NetworkStream::{NormalStream, SslProtectedStream}; 12 | use connecter::Connecter; 13 | 14 | /// A TCP stream, either plain text or SSL. 15 | /// 16 | /// This build was made with **OpenSSL** providing SSL support. 17 | pub enum NetworkStream { 18 | NormalStream(TcpStream), 19 | SslProtectedStream(SslStream), 20 | } 21 | 22 | impl Connecter for NetworkStream { 23 | fn connect(addr: SocketAddr, host: &str, use_ssl: bool) -> IoResult { 24 | let stream = try!(TcpStream::connect(addr)); 25 | if use_ssl { 26 | let context = try!(SslContext::new(SslMethod::Sslv23).map_err(lift_ssl_error)); 27 | let ssl = try!(Ssl::new(&context).map_err(lift_ssl_error)); 28 | try!(ssl.set_hostname(host).map_err(lift_ssl_error)); 29 | let ssl_stream = try!(SslStream::new_from(ssl, stream).map_err(lift_ssl_error)); 30 | Ok(SslProtectedStream(ssl_stream)) 31 | } else { 32 | Ok(NormalStream(stream)) 33 | } 34 | } 35 | } 36 | 37 | impl Reader for NetworkStream { 38 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 39 | match *self { 40 | NormalStream(ref mut ns) => ns.read(buf), 41 | SslProtectedStream(ref mut ns) => ns.read(buf), 42 | } 43 | } 44 | } 45 | 46 | impl Writer for NetworkStream { 47 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 48 | match *self { 49 | NormalStream(ref mut ns) => ns.write(buf), 50 | SslProtectedStream(ref mut ns) => ns.write(buf), 51 | } 52 | } 53 | 54 | fn flush(&mut self) -> IoResult<()> { 55 | match *self { 56 | NormalStream(ref mut ns) => ns.flush(), 57 | SslProtectedStream(ref mut ns) => ns.flush(), 58 | } 59 | } 60 | } 61 | 62 | fn lift_ssl_error(ssl: SslError) -> IoError { 63 | match ssl { 64 | StreamError(err) => err, 65 | SslSessionClosed => IoError { 66 | kind: ConnectionAborted, 67 | desc: "SSL Connection Closed", 68 | detail: None 69 | }, 70 | // Unfortunately throw this away. No way to support this 71 | // detail without a better Error abstraction. 72 | OpenSslErrors(errs) => IoError { 73 | kind: OtherIoError, 74 | desc: "Error in OpenSSL", 75 | detail: Some(format!("{:?}", errs)) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/http/client/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)]; 2 | 3 | use memstream::MemReaderFakeStream; 4 | use client::request::RequestWriter; 5 | use client::response::ResponseReader; 6 | 7 | fn test() { 8 | let mut request = ~RequestWriter::new(Get, from_str("http://example.com/").unwrap()); 9 | ResponseReader::construct(MemReaderFakeStream::new(b"\ 10 | HTTP/1.1 200 OK\r\n\ 11 | ETag: W/\"it's an entity-tag!\"\r\n\ 12 | Content-Length: 28\r\n\ 13 | \r\n\ 14 | And here's the request body."), request); 15 | } 16 | -------------------------------------------------------------------------------- /src/http/common.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | * Poorly categorised functions for reading things used in multiple places. 3 | * 4 | * (That is, typically, more than one of request-or-response reading-or-writing.) 5 | * 6 | * TODO: refactor all this to store things in more usefully categorised places. 7 | */ 8 | use std::num::{UnsignedInt, NumCast, Int, cast}; 9 | use std::io::{IoError, IoResult, OtherIoError}; 10 | #[cfg(test)] 11 | use std::io::MemReader; 12 | 13 | // XXX: IoError ain't a good representation of this. 14 | fn bad_input() -> IoError { 15 | IoError { 16 | kind: OtherIoError, 17 | desc: "invalid number", 18 | detail: None, 19 | } 20 | } 21 | 22 | const ASCII_ZERO: u8 = b'0'; 23 | const ASCII_NINE: u8 = b'9'; 24 | const ASCII_LOWER_A: u8 = b'a'; 25 | const ASCII_LOWER_F: u8 = b'f'; 26 | const ASCII_UPPER_A: u8 = b'A'; 27 | const ASCII_UPPER_F: u8 = b'F'; 28 | 29 | /** 30 | * Read a positive decimal integer from the given reader. 31 | * 32 | * # Arguments 33 | * 34 | * - `reader` - the reader to read the decimal digits from (and whatever byte comes next) 35 | * - `expected_end` - this function is called with the byte that follows the last decimal digit 36 | * and should return `true` if that byte is what is expected to follow the number. 37 | * 38 | * # Return value 39 | * 40 | * - `None`, if the number overflows; 41 | * - `None`, if there is a leading zero; 42 | * - `None`, if all the digits are read and the `expected_end` function is false 43 | * 44 | * Should everything work as designed (i.e. none of these conditions occur) a `Some` is returned. 45 | */ 46 | pub fn read_decimal bool> 47 | (reader: &mut R, expected_end: &mut F) 48 | -> IoResult { 49 | // Here and in `read_hexadecimal` there is the possibility of infinite sequence of zeroes. The 50 | // spec allows this, but it may not be a good thing to allow. It's not a particularly good 51 | // attack surface, though, because of the low return. 52 | let mut n: N = Int::zero(); 53 | let mut got_content = false; 54 | let ten: N = cast(10u32).unwrap(); 55 | loop { 56 | n = match reader.read_byte() { 57 | Ok(b@ASCII_ZERO...ASCII_NINE) => { 58 | // Written sanely, this is: n * 10 + (b - '0'), but we avoid 59 | // (semantically unsound) overflow by using checked operations. 60 | // There is no need in the b - '0' part as it is safe. 61 | match n.checked_mul(ten).and_then( 62 | |n| n.checked_add(cast(b - ASCII_ZERO).unwrap())) { 63 | Some(new_n) => new_n, 64 | None => return Err(bad_input()), // overflow 65 | } 66 | }, 67 | Ok(b) => return if got_content && expected_end(b) { Ok(n) } else { Err(bad_input()) }, // not a valid number 68 | Err(err) => return Err(err), // I/O error 69 | }; 70 | got_content = true; 71 | } 72 | } 73 | 74 | /** 75 | * Read a positive hexadecimal integer from the given reader. 76 | * 77 | * # Arguments 78 | * 79 | * - `reader` - the reader to read the hexadecimal digits from (and whatever byte comes next) 80 | * - `expected_end` - this function is called with the byte that follows the last hexadecimal digit 81 | * and should return `true` if that byte is what is expected to follow the number. 82 | * 83 | * # Return value 84 | * 85 | * - `None`, if the number overflows; 86 | * - `None`, if there is a leading zero; 87 | * - `None`, if all the digits are read and the `expected_end` function is false 88 | * 89 | * Should everything work as designed (i.e. none of these conditions occur) a `Some` is returned. 90 | */ 91 | pub fn read_hexadecimal bool> 92 | (reader: &mut R, expected_end: &mut F) 93 | -> IoResult { 94 | let mut n: N = Int::zero(); 95 | let mut got_content = false; 96 | let sixteen: N = cast(16u32).unwrap(); 97 | loop { 98 | n = match reader.read_byte() { 99 | Ok(b@ASCII_ZERO...ASCII_NINE) => { 100 | match n.checked_mul(sixteen).and_then( 101 | |n| n.checked_add(cast(b - ASCII_ZERO).unwrap())) { 102 | Some(new_n) => new_n, 103 | None => return Err(bad_input()), // overflow 104 | } 105 | }, 106 | Ok(b@ASCII_LOWER_A...ASCII_LOWER_F) => { 107 | match n.checked_mul(sixteen).and_then( 108 | |n| n.checked_add(cast(b - ASCII_LOWER_A + 10).unwrap())) { 109 | Some(new_n) => new_n, 110 | None => return Err(bad_input()), // overflow 111 | } 112 | }, 113 | Ok(b@ASCII_UPPER_A...ASCII_UPPER_F) => { 114 | match n.checked_mul(sixteen).and_then( 115 | |n| n.checked_add(cast(b - ASCII_UPPER_A + 10).unwrap())) { 116 | Some(new_n) => new_n, 117 | None => return Err(bad_input()), // overflow 118 | } 119 | }, 120 | Ok(b) => return if got_content && expected_end(b) { Ok(n) } else { Err(bad_input()) }, // not a valid number 121 | Err(err) => return Err(err), // I/O error 122 | }; 123 | got_content = true; 124 | } 125 | } 126 | 127 | /** 128 | * Read an HTTP-Version (e.g. "HTTP/1.1") from a stream. 129 | * 130 | * # Arguments 131 | * 132 | * - `reader` - the reader to read the HTTP-Version from (and whatever byte comes next) 133 | * - `expected_end` - this function is called with the byte that follows the HTTP-Version 134 | * and should return `true` if that byte is what is expected to follow the HTTP-Version. 135 | * 136 | * # Return value 137 | * 138 | * - `None`, if the HTTP-Version is malformed in any way; 139 | * - `None`, if the `expected_end` function returns false; 140 | * - A `Some`, if all goes well. 141 | */ 142 | #[inline] 143 | pub fn read_http_version bool> 144 | (reader: &mut R, expected_end: &mut F) 145 | -> IoResult<(usize, usize)> { 146 | // I'd read into a [0u8, ..5], but that buffer is not guaranteed to be 147 | // filled, so I must read it byte by byte to guarantee correctness. 148 | // (Sure, no sane person/library would send the first packet with "HTT" 149 | // and leave the "P/1.1" to the next packet, but it's *possible*.) 150 | let b0 = try!(reader.read_byte()); 151 | let b1 = try!(reader.read_byte()); 152 | let b2 = try!(reader.read_byte()); 153 | let b3 = try!(reader.read_byte()); 154 | let b4 = try!(reader.read_byte()); 155 | if (b0 != b'h' && b0 != b'H') || 156 | (b1 != b't' && b1 != b'T') || 157 | (b2 != b't' && b2 != b'T') || 158 | (b3 != b'p' && b3 != b'P') || 159 | b4 != b'/' { 160 | return Err(bad_input()); 161 | } 162 | 163 | let major = try!(read_decimal(reader, &mut |b| b == b'.')); 164 | let minor = try!(read_decimal(reader, expected_end)); 165 | Ok((major, minor)) 166 | } 167 | 168 | // I couldn't think what to call it. Ah well. It's just trivial syntax sugar, anyway. 169 | macro_rules! test_reads { 170 | ($func:ident, $($value:expr => $expected:expr),*) => {{ 171 | $( 172 | assert_eq!( 173 | concat_idents!(read_, $func)(&mut MemReader::new($value.bytes().collect::>()), 174 | &mut |b| b == 0).ok(), 175 | $expected); 176 | )* 177 | }} 178 | } 179 | 180 | #[test] 181 | fn test_read_http_version() { 182 | test_reads!(http_version, 183 | "HTTP/25.17\0" => Some((25, 17)), 184 | "http/1.0\0" => Some((1, 0)), 185 | "http 1.0\0" => None, 186 | "HTTP/1.0.\0" => None, 187 | "HTTP/1.0.\0" => None 188 | ); 189 | } 190 | 191 | #[test] 192 | fn test_read_decimal() { 193 | test_reads!(decimal, 194 | "0\0" => Some(0us), 195 | "0\0" => Some(0u8), 196 | "0\0" => Some(0u64), 197 | "9\0" => Some(9u8), 198 | "42\0" => Some(42u64), 199 | "0123456789\0" => Some(123456789u64), 200 | "00000000000000000000000031\0" => Some(31u64), 201 | 202 | // End of stream in middle of number 203 | "0" => None::, 204 | 205 | // No number 206 | "\0" => None::, 207 | 208 | // Invalid character 209 | "0a\0" => None::, 210 | 211 | // At overflow bounds 212 | "255\0" => Some(255u8), 213 | "256\0" => None::, 214 | "256\0" => Some(256u16) 215 | ); 216 | } 217 | 218 | #[test] 219 | fn test_read_hexadecimal() { 220 | test_reads!(hexadecimal, 221 | "0\0" => Some(0us), 222 | "0\0" => Some(0u8), 223 | "0\0" => Some(0u64), 224 | "f\0" => Some(0xfu8), 225 | "42\0" => Some(0x42u64), 226 | "012345\0" => Some(0x12345u64), 227 | "00000000000000000000000031\0" => Some(0x31u64), 228 | "0123456789AbCdEf\0" => Some(0x123456789abcdefu64), 229 | 230 | // End of stream in middle of number 231 | "0" => None::, 232 | 233 | // No number 234 | "\0" => None::, 235 | 236 | // Invalid character 237 | "fg\0" => None::, 238 | 239 | // At overflow bounds 240 | "ff\0" => Some(0xffu8), 241 | "100\0" => None::, 242 | "100\0" => Some(0x100u16) 243 | ); 244 | } 245 | -------------------------------------------------------------------------------- /src/http/connecter.rs: -------------------------------------------------------------------------------- 1 | // The spelling "Connecter" is deliberate, by the way. 2 | 3 | use std::io::IoResult; 4 | use std::io::net::ip::SocketAddr; 5 | 6 | /// A trait for the concept of opening a stream connected to a IP socket address. 7 | /// 8 | /// Why is this here? So that we can implement things which must make 9 | /// connections in terms of *anything* that can make such a connection rather 10 | /// than in terms of `TcpStream` only. This is handy for testing and for SSL. 11 | pub trait Connecter { 12 | fn connect(addr: SocketAddr, host: &str, use_ssl: bool) -> IoResult; 13 | } 14 | -------------------------------------------------------------------------------- /src/http/headers/accept_ranges.rs: -------------------------------------------------------------------------------- 1 | //! The Accept-Ranges request header, defined in RFC 2616, Section 14.5. 2 | 3 | use std::io::IoResult; 4 | use std::ascii::AsciiExt; 5 | 6 | pub use self::AcceptableRanges::{RangeUnits, NoAcceptableRanges}; 7 | pub use self::RangeUnit::{Bytes, OtherRangeUnit}; 8 | 9 | #[derive(Clone, PartialEq, Eq)] 10 | // RFC 2616: range-unit = bytes-unit | other-range-unit 11 | pub enum RangeUnit { 12 | Bytes, // bytes-unit = "bytes" 13 | OtherRangeUnit(String), // other-range-unit = token 14 | } 15 | 16 | #[derive(Clone, PartialEq, Eq)] 17 | // RFC 2616: acceptable-ranges = 1#range-unit | "none" 18 | pub enum AcceptableRanges { 19 | RangeUnits(Vec), 20 | NoAcceptableRanges, 21 | } 22 | 23 | impl super::HeaderConvertible for AcceptableRanges { 24 | fn from_stream(reader: &mut super::HeaderValueByteIterator) 25 | -> Option { 26 | let mut range_units = Vec::new(); 27 | loop { 28 | match reader.read_token() { 29 | Some(token) => { 30 | let token = token.to_ascii_lowercase(); 31 | match &token[] { 32 | "bytes" => range_units.push(Bytes), 33 | "none" if range_units.len() == 0 => return Some(NoAcceptableRanges), 34 | _ => range_units.push(OtherRangeUnit(token)), 35 | } 36 | }, 37 | None => break, 38 | } 39 | } 40 | Some(RangeUnits(range_units)) 41 | } 42 | 43 | fn to_stream(&self, writer: &mut W) -> IoResult<()> { 44 | match *self { 45 | NoAcceptableRanges => writer.write(b"none"), 46 | RangeUnits(ref range_units) => { 47 | for ru in range_units.iter() { 48 | try!(writer.write(match *ru { 49 | Bytes => b"bytes", 50 | OtherRangeUnit(ref ru) => ru.as_bytes(), 51 | })); 52 | } 53 | Ok(()) 54 | }, 55 | } 56 | } 57 | 58 | fn http_value(&self) -> String { 59 | match *self { 60 | NoAcceptableRanges => String::from_str("none"), 61 | RangeUnits(ref range_units) => { 62 | let mut result = String::new(); 63 | for ru in range_units.iter() { 64 | match ru { 65 | &Bytes => result.push_str("bytes"), 66 | &OtherRangeUnit(ref ru) => result.push_str(&ru[]), 67 | } 68 | } 69 | result 70 | }, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/http/headers/connection.rs: -------------------------------------------------------------------------------- 1 | //! The Connection general header, defined in RFC 2616, Section 14.10. 2 | 3 | // TODO: check if the token thing is correct or whether it's any number of tokens. Also, how and 4 | // whether they should be interpreted (I recall its being a header name thing for legacy code, 5 | // perhaps I should normalise header case or some such thing?) 6 | 7 | use std::fmt; 8 | use std::io::IoResult; 9 | use headers::serialization_utils::normalise_header_name; 10 | 11 | use self::Connection::{Token, Close}; 12 | 13 | /// A value for the Connection header. Note that should it be a ``Token``, the string is in 14 | /// normalised header case (e.g. "Keep-Alive"). 15 | #[derive(Clone, PartialEq, Eq)] 16 | pub enum Connection { 17 | Token(String), 18 | Close, 19 | } 20 | 21 | impl fmt::Show for Connection { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | f.write_str(match *self { 24 | Token(ref s) => &s[], 25 | Close => "close", 26 | }) 27 | } 28 | } 29 | 30 | impl super::CommaListHeaderConvertible for Connection {} 31 | 32 | impl super::HeaderConvertible for Connection { 33 | fn from_stream(reader: &mut super::HeaderValueByteIterator) 34 | -> Option { 35 | let s = match reader.read_token() { 36 | Some(s) => normalise_header_name(&s[]), 37 | None => return None, 38 | }; 39 | if &s[] == "Close" { 40 | Some(Close) 41 | } else { 42 | Some(Token(s)) 43 | } 44 | } 45 | 46 | fn to_stream(&self, writer: &mut W) -> IoResult<()> { 47 | writer.write(match *self { 48 | Close => b"close", 49 | Token(ref s) => s.as_bytes(), 50 | }) 51 | } 52 | 53 | fn http_value(&self) -> String { 54 | match *self { 55 | Close => String::from_str("close"), 56 | Token(ref s) => s.clone(), 57 | } 58 | } 59 | } 60 | 61 | #[test] 62 | fn test_connection() { 63 | use headers::test_utils::{assert_conversion_correct, 64 | assert_interpretation_correct, 65 | assert_invalid}; 66 | assert_conversion_correct("close", vec!(Close)); 67 | assert_conversion_correct("Foo", vec!(Token(String::from_str("Foo")))); 68 | assert_conversion_correct("Foo, Keep-Alive", vec!(Token(String::from_str("Foo")), Token(String::from_str("Keep-Alive")))); 69 | assert_conversion_correct("Foo, close", vec!(Token(String::from_str("Foo")), Close)); 70 | assert_conversion_correct("close, Bar", vec!(Close, Token(String::from_str("Bar")))); 71 | 72 | assert_interpretation_correct("close", vec!(Close)); 73 | assert_interpretation_correct("foo", vec!(Token(String::from_str("Foo")))); 74 | assert_interpretation_correct("close \r\n , keep-ALIVE", vec!(Close, Token(String::from_str("Keep-Alive")))); 75 | assert_interpretation_correct("foo,close", vec!(Token(String::from_str("Foo")), Close)); 76 | assert_interpretation_correct("close, bar", vec!(Close, Token(String::from_str("Bar")))); 77 | assert_interpretation_correct("CLOSE", Close); 78 | 79 | assert_invalid::>("foo bar"); 80 | assert_invalid::>("foo bar"); 81 | assert_invalid::>("foo, bar baz"); 82 | assert_invalid::>("foo, , baz"); 83 | } 84 | -------------------------------------------------------------------------------- /src/http/headers/content_type.rs: -------------------------------------------------------------------------------- 1 | //! The Content-Type entity header, defined in RFC 2616, Section 14.17. 2 | use headers::serialization_utils::{push_parameters, WriterUtil}; 3 | use std::io::IoResult; 4 | use std::fmt; 5 | 6 | #[derive(Clone, PartialEq, Eq)] 7 | pub struct MediaType { 8 | pub type_: String, 9 | pub subtype: String, 10 | pub parameters: Vec<(String, String)>, 11 | } 12 | 13 | impl MediaType { 14 | pub fn new(type_: String, subtype: String, parameters: Vec<(String, String)>) -> MediaType { 15 | MediaType { 16 | type_: type_, 17 | subtype: subtype, 18 | parameters: parameters, 19 | } 20 | } 21 | } 22 | 23 | 24 | 25 | impl fmt::Show for MediaType { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | // Idea: 28 | //let s = String::new(); 29 | //s.push_token(self.type_); 30 | //s.push_char('/'); 31 | //s.push_token(self.subtype); 32 | //s.push_parameters(self.parameters); 33 | //s 34 | let s = format!("{}/{}", self.type_, self.subtype); 35 | f.write_str(&push_parameters(s, &self.parameters[])[]) 36 | } 37 | } 38 | 39 | impl super::HeaderConvertible for MediaType { 40 | fn from_stream(reader: &mut super::HeaderValueByteIterator) -> Option { 41 | let type_ = match reader.read_token() { 42 | Some(v) => v, 43 | None => return None, 44 | }; 45 | if reader.next() != Some(b'/') { 46 | return None; 47 | } 48 | let subtype = match reader.read_token() { 49 | Some(v) => v, 50 | None => return None, 51 | }; 52 | match reader.read_parameters() { 53 | // At the time of writing, ``Some(parameters) if reader.verify_consumed()`` was not 54 | // permitted: "cannot bind by-move into a pattern guard" 55 | Some(parameters) => { 56 | reader.some_if_consumed(MediaType { 57 | type_: type_, 58 | subtype: subtype, 59 | parameters: parameters, 60 | }) 61 | }, 62 | None => None, 63 | } 64 | } 65 | 66 | fn to_stream(&self, writer: &mut W) -> IoResult<()> { 67 | try!(writer.write_token(&self.type_)); 68 | try!(writer.write(b"/")); 69 | try!(writer.write_token(&self.subtype)); 70 | writer.write_parameters(&self.parameters[]) 71 | } 72 | 73 | fn http_value(&self) -> String { 74 | format!("{:?}", self) 75 | } 76 | } 77 | 78 | #[test] 79 | fn test_content_type() { 80 | use headers::test_utils::{assert_conversion_correct, assert_interpretation_correct, 81 | assert_invalid}; 82 | assert_conversion_correct("type/subtype", MediaType::new(String::from_str("type"), String::from_str("subtype"), Vec::new())); 83 | assert_conversion_correct("type/subtype;key=value", 84 | MediaType::new(String::from_str("type"), String::from_str("subtype"), vec!((String::from_str("key"), String::from_str("value"))))); 85 | assert_conversion_correct("type/subtype;key=value;q=0.1", 86 | MediaType::new(String::from_str("type"), String::from_str("subtype"), vec!((String::from_str("key"), String::from_str("value")), (String::from_str("q"), String::from_str("0.1"))))); 87 | assert_interpretation_correct("type/subtype ; key = value ; q = 0.1", 88 | MediaType::new(String::from_str("type"), String::from_str("subtype"), vec!((String::from_str("key"), String::from_str("value")), (String::from_str("q"), String::from_str("0.1"))))); 89 | 90 | assert_invalid::(""); 91 | assert_invalid::("/"); 92 | assert_invalid::("type/subtype,foo=bar"); 93 | assert_invalid::("type /subtype"); 94 | assert_invalid::("type/ subtype"); 95 | assert_invalid::("type/subtype;foo=bar,foo=bar"); 96 | } 97 | -------------------------------------------------------------------------------- /src/http/headers/etag.rs: -------------------------------------------------------------------------------- 1 | use headers::serialization_utils::{push_quoted_string, quoted_string, WriterUtil}; 2 | use std::io::IoResult; 3 | use std::fmt; 4 | 5 | #[derive(Clone, PartialEq, Eq)] 6 | pub struct EntityTag { 7 | pub weak: bool, 8 | pub opaque_tag: String, 9 | } 10 | 11 | pub fn weak_etag(opaque_tag: String) -> EntityTag { 12 | EntityTag { 13 | weak: true, 14 | opaque_tag: opaque_tag, 15 | } 16 | } 17 | 18 | pub fn strong_etag(opaque_tag: String) -> EntityTag { 19 | EntityTag { 20 | weak: false, 21 | opaque_tag: opaque_tag, 22 | } 23 | } 24 | 25 | impl fmt::Show for EntityTag { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | if self.weak { 28 | f.write_str(&push_quoted_string(String::from_str("W/"), &self.opaque_tag)[]) 29 | } else { 30 | f.write_str("ed_string(&self.opaque_tag)[]) 31 | } 32 | } 33 | } 34 | 35 | impl super::HeaderConvertible for EntityTag { 36 | fn from_stream(reader: &mut super::HeaderValueByteIterator) -> Option { 37 | let weak = match reader.next() { 38 | Some(b) if b == b'W' || b == b'w' => { 39 | if reader.next() != Some(b'/') || reader.next() != Some(b'"') { 40 | return None; 41 | } 42 | true 43 | }, 44 | Some(b) if b == b'"' => { 45 | false 46 | }, 47 | _ => { 48 | return None; 49 | } 50 | }; 51 | let opaque_tag = match reader.read_quoted_string(true) { 52 | Some(tag) => tag, 53 | None => return None, 54 | }; 55 | reader.some_if_consumed(EntityTag { 56 | weak: weak, 57 | opaque_tag: opaque_tag, 58 | }) 59 | } 60 | 61 | fn to_stream(&self, writer: &mut W) -> IoResult<()> { 62 | if self.weak { 63 | try!(writer.write(b"W/")); 64 | } 65 | writer.write_quoted_string(&self.opaque_tag) 66 | } 67 | 68 | fn http_value(&self) -> String { 69 | format!("{:?}", self) 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_etag() { 75 | use headers::test_utils::{assert_conversion_correct, assert_interpretation_correct, 76 | assert_invalid}; 77 | assert_conversion_correct("\"\"", strong_etag(String::new())); 78 | assert_conversion_correct("\"fO0\"", strong_etag(String::from_str("fO0"))); 79 | assert_conversion_correct("\"fO0 bar\"", strong_etag(String::from_str("fO0 bar"))); 80 | assert_conversion_correct("\"fO0 \\\"bar\"", strong_etag(String::from_str("fO0 \"bar"))); 81 | assert_conversion_correct("\"fO0 \\\"bar\\\"\"", strong_etag(String::from_str("fO0 \"bar\""))); 82 | 83 | assert_conversion_correct("W/\"\"", weak_etag(String::new())); 84 | assert_conversion_correct("W/\"fO0\"", weak_etag(String::from_str("fO0"))); 85 | assert_conversion_correct("W/\"fO0 bar\"", weak_etag(String::from_str("fO0 bar"))); 86 | assert_conversion_correct("W/\"fO0 \\\"bar\"", weak_etag(String::from_str("fO0 \"bar"))); 87 | assert_conversion_correct("W/\"fO0 \\\"bar\\\"\"", weak_etag(String::from_str("fO0 \"bar\""))); 88 | assert_interpretation_correct("w/\"fO0\"", weak_etag(String::from_str("fO0"))); 89 | 90 | assert_invalid::(""); 91 | assert_invalid::("fO0"); 92 | assert_invalid::("\"\\\""); 93 | assert_invalid::("\"\"\"\""); 94 | } 95 | -------------------------------------------------------------------------------- /src/http/headers/host.rs: -------------------------------------------------------------------------------- 1 | //! The Host request header, defined in RFC 2616, Section 14.23. 2 | 3 | use std::io::Reader; 4 | use std::fmt; 5 | 6 | /// A simple little thing for the host of a request 7 | #[derive(Clone, PartialEq, Eq)] 8 | pub struct Host { 9 | 10 | /// The name of the host that was requested 11 | pub name: String, 12 | 13 | /// If unspecified, assume the default port was used (80 for HTTP, 443 for HTTPS). 14 | /// In that case, you shouldn't need to worry about it in URLs that you build, provided you 15 | /// include the scheme. 16 | pub port: Option, 17 | } 18 | 19 | impl fmt::Show for Host { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self.port { 22 | Some(port) => write!(f, "{}:{}", self.name, port), 23 | None => f.write_str(&self.name[]), 24 | } 25 | } 26 | } 27 | 28 | impl super::HeaderConvertible for Host { 29 | fn from_stream(reader: &mut super::HeaderValueByteIterator) -> Option { 30 | let s = reader.collect_to_string(); 31 | // TODO: this doesn't support IPv6 address access (e.g. "[::1]") 32 | // Do this properly with correct authority parsing. 33 | let mut hi = s.splitn(1, ':'); 34 | Some(Host { 35 | name: String::from_str(hi.next().unwrap()), 36 | port: match hi.next() { 37 | Some(name) => name.parse(), 38 | None => None, 39 | }, 40 | }) 41 | } 42 | 43 | fn http_value(&self) -> String { 44 | format!("{:?}", self) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/http/headers/serialization_utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for assisting with conversion of headers from and to the HTTP text form. 2 | 3 | use std::io::IoResult; 4 | use rfc2616::is_token; 5 | 6 | /// Normalise an HTTP header name. 7 | /// 8 | /// Rules: 9 | /// 10 | /// - The first character is capitalised 11 | /// - Any character immediately following `-` (HYPHEN-MINUS) is capitalised 12 | /// - All other characters are made lowercase 13 | /// 14 | /// # Examples 15 | /// 16 | /// ~~~ .{rust} 17 | /// # use http::headers::serialization_utils::normalise_header_name; 18 | /// assert_eq!(normalise_header_name("foo-bar"), String::from_str("Foo-Bar")); 19 | /// assert_eq!(normalise_header_name("FOO-BAR"), String::from_str("Foo-Bar")); 20 | /// ~~~ 21 | pub fn normalise_header_name(name: &str) -> String { 22 | let mut result: String = String::with_capacity(name.len()); 23 | let mut capitalise = true; 24 | for c in name.chars() { 25 | result.push(match capitalise { 26 | true => c.to_uppercase(), 27 | false => c.to_lowercase(), 28 | }); 29 | capitalise = c == '-'; 30 | } 31 | result 32 | } 33 | 34 | /// Split a value on commas, as is common for HTTP headers. 35 | /// 36 | /// This does not handle quoted-strings intelligently. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ~~~ .{rust} 41 | /// # use http::headers::serialization_utils::comma_split; 42 | /// assert_eq!( 43 | /// comma_split(" en;q=0.8, en_AU, text/html"), 44 | /// vec![String::from_str("en;q=0.8"), String::from_str("en_AU"), String::from_str("text/html")] 45 | /// ) 46 | /// ~~~ 47 | pub fn comma_split(value: &str) -> Vec { 48 | value.split(',').map(|w| String::from_str(w.trim_left())).collect() 49 | } 50 | 51 | pub fn comma_split_iter<'a>(value: &'a str) 52 | -> ::std::iter::Map<&'a str, &'a str, ::std::str::Split<'a, char>, fn(&str) -> &str> { 53 | fn trim(w: &str) -> &str {w.trim_left()} 54 | 55 | value.split(',').map(trim as fn(&str) -> &str) 56 | } 57 | 58 | pub trait WriterUtil: Writer { 59 | fn write_maybe_quoted_string(&mut self, s: &String) -> IoResult<()> { 60 | if is_token(s) { 61 | self.write(s.as_bytes()) 62 | } else { 63 | self.write_quoted_string(s) 64 | } 65 | } 66 | 67 | fn write_quoted_string(&mut self, s: &String) -> IoResult<()> { 68 | try!(self.write(b"\"")); 69 | for b in s.as_bytes().iter() { 70 | if *b == b'\\' || *b == b'"' { 71 | try!(self.write(b"\\")); 72 | } 73 | // XXX This doesn't seem right. 74 | try!(self.write(&[*b])); 75 | } 76 | self.write(b"\"") 77 | } 78 | 79 | fn write_parameter(&mut self, k: &str, v: &String) -> IoResult<()> { 80 | try!(self.write(k.as_bytes())); 81 | try!(self.write(b"=")); 82 | self.write_maybe_quoted_string(v) 83 | } 84 | 85 | fn write_parameters(&mut self, parameters: &[(String, String)]) -> IoResult<()> { 86 | for &(ref k, ref v) in parameters.iter() { 87 | try!(self.write(b";")); 88 | try!(self.write_parameter(&k[], v)); 89 | } 90 | Ok(()) 91 | } 92 | 93 | fn write_quality(&mut self, quality: Option) -> IoResult<()> { 94 | // TODO: remove second and third decimal places if zero, and use a better quality type anyway 95 | match quality { 96 | Some(qvalue) => write!(&mut *self, ";q={:0.3}", qvalue), 97 | None => Ok(()), 98 | } 99 | } 100 | 101 | #[inline] 102 | fn write_token(&mut self, token: &String) -> IoResult<()> { 103 | assert!(is_token(token)); 104 | self.write(token.as_bytes()) 105 | } 106 | } 107 | 108 | impl WriterUtil for W { } 109 | 110 | /// Join a vector of values with commas, as is common for HTTP headers. 111 | /// 112 | /// # Examples 113 | /// 114 | /// ~~~ .{rust} 115 | /// # use http::headers::serialization_utils::comma_join; 116 | /// assert_eq!( 117 | /// comma_join(&[String::from_str("en;q=0.8"), String::from_str("en_AU"), String::from_str("text/html")]), 118 | /// String::from_str("en;q=0.8, en_AU, text/html") 119 | /// ) 120 | /// ~~~ 121 | #[inline] 122 | pub fn comma_join(values: &[String]) -> String { 123 | let mut out = String::new(); 124 | let mut iter = values.iter(); 125 | match iter.next() { 126 | Some(s) => out.push_str(&s[]), 127 | None => return out 128 | } 129 | 130 | for value in iter { 131 | out.push_str(", "); 132 | out.push_str(&value[]); 133 | } 134 | out 135 | } 136 | 137 | /// Push a ( token | quoted-string ) onto a string and return it again 138 | pub fn push_maybe_quoted_string(mut s: String, t: &String) -> String { 139 | if is_token(t) { 140 | s.push_str(&t[]); 141 | s 142 | } else { 143 | push_quoted_string(s, t) 144 | } 145 | } 146 | 147 | /// Make a string into a ( token | quoted-string ), preferring a token 148 | pub fn maybe_quoted_string(s: &String) -> String { 149 | if is_token(s) { 150 | s.clone() 151 | } else { 152 | quoted_string(s) 153 | } 154 | } 155 | 156 | /// Quote a string, to turn it into an RFC 2616 quoted-string 157 | pub fn push_quoted_string(mut s: String, t: &String) -> String { 158 | let i = s.len(); 159 | s.reserve(t.len() + i + 2); 160 | s.push('"'); 161 | for c in t.chars() { 162 | if c == '\\' || c == '"' { 163 | s.push('\\'); 164 | } 165 | s.push(c); 166 | } 167 | s.push('"'); 168 | s 169 | } 170 | 171 | /// Quote a string, to turn it into an RFC 2616 quoted-string 172 | pub fn quoted_string(s: &String) -> String { 173 | push_quoted_string(String::new(), s) 174 | } 175 | 176 | /// Parse a quoted-string. Returns ``None`` if the string is not a valid quoted-string. 177 | pub fn unquote_string(s: &String) -> Option { 178 | enum State { Start, Normal, Escaping, End } 179 | 180 | let mut state = State::Start; 181 | let mut output = String::new(); 182 | // Strings with escapes cause overallocation, but it's not worth a second pass to avoid this! 183 | output.reserve(s.len() - 2); 184 | let mut iter = s.chars(); 185 | loop { 186 | state = match (state, iter.next()) { 187 | (State::Start, Some(c)) if c == '"' => State::Normal, 188 | (State::Start, Some(_)) => return None, 189 | (State::Normal, Some(c)) if c == '\\' => State::Escaping, 190 | (State::Normal, Some(c)) if c == '"' => State::End, 191 | (State::Normal, Some(c)) | (State::Escaping, Some(c)) => { 192 | output.push(c); 193 | State::Normal 194 | }, 195 | (State::End, Some(_)) => return None, 196 | (State::End, None) => return Some(output), 197 | (_, None) => return None, 198 | } 199 | } 200 | } 201 | 202 | /// Parse a ( token | quoted-string ). Returns ``None`` if it is not valid. 203 | pub fn maybe_unquote_string(s: &String) -> Option { 204 | if is_token(s) { 205 | Some(s.clone()) 206 | } else { 207 | unquote_string(s) 208 | } 209 | } 210 | 211 | // Takes and emits the String instead of the &mut str for a simpler, fluid interface 212 | pub fn push_parameter(mut s: String, k: &String, v: &String) -> String { 213 | s.push_str(&k[]); 214 | s.push('='); 215 | push_maybe_quoted_string(s, v) 216 | } 217 | 218 | // pub fn push_parameters(mut s: String, parameters: &[(K, V)]) -> String { 219 | pub fn push_parameters(mut s: String, parameters: &[(String, String)]) -> String { 220 | for &(ref k, ref v) in parameters.iter() { 221 | s.push(';'); 222 | s = push_parameter(s, k, v); 223 | } 224 | s 225 | } 226 | 227 | #[cfg(test)] 228 | mod test { 229 | use super::{normalise_header_name, comma_split, comma_split_iter, comma_join, 230 | push_parameter, push_parameters, push_maybe_quoted_string, push_quoted_string, 231 | maybe_quoted_string, quoted_string, unquote_string, maybe_unquote_string}; 232 | 233 | #[test] 234 | fn test_normalise_header_name() { 235 | assert_eq!(normalise_header_name("foö-bar"), String::from_str("Foö-Bar")); 236 | assert_eq!(normalise_header_name("foo-bar"), String::from_str("Foo-Bar")); 237 | assert_eq!(normalise_header_name("FOO-BAR"), String::from_str("Foo-Bar")); 238 | } 239 | 240 | #[test] 241 | fn test_comma_split() { 242 | // Simple 0-element case 243 | assert_eq!(comma_split(""), vec!(String::new())); 244 | // Simple 1-element case 245 | assert_eq!(comma_split("foo"), vec!(String::from_str("foo"))); 246 | // Simple 2-element case 247 | assert_eq!(comma_split("foo,bar"), vec!(String::from_str("foo"), String::from_str("bar"))); 248 | // Simple >2-element case 249 | assert_eq!(comma_split("foo,bar,baz,quux"), vec!(String::from_str("foo"), String::from_str("bar"), String::from_str("baz"), String::from_str("quux"))); 250 | // Doesn't handle quoted-string intelligently 251 | assert_eq!(comma_split("\"foo,bar\",baz"), vec!(String::from_str("\"foo"), String::from_str("bar\""), String::from_str("baz"))); 252 | // Doesn't do right trimming, but does left 253 | assert_eq!(comma_split(" foo;q=0.8 , bar/* "), vec!(String::from_str("foo;q=0.8 "), String::from_str("bar/* "))); 254 | } 255 | 256 | #[test] 257 | fn test_comma_split_iter() { 258 | // These are the same cases as in test_comma_split above. 259 | let s = ""; 260 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec![""]); 261 | let s = "foo"; 262 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec!["foo"]); 263 | let s = "foo,bar"; 264 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec!["foo", "bar"]); 265 | let s = "foo,bar,baz,quux"; 266 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec!["foo", "bar", "baz", "quux"]); 267 | let s = "\"foo,bar\",baz"; 268 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec!["\"foo", "bar\"", "baz"]); 269 | let s = " foo;q=0.8 , bar/* "; 270 | assert_eq!(comma_split_iter(s).collect::< Vec<&'static str> >(), vec!["foo;q=0.8 ", "bar/* "]); 271 | } 272 | 273 | #[test] 274 | fn test_comma_join() { 275 | assert_eq!(comma_join(&[String::new()]), String::new()); 276 | assert_eq!(comma_join(&[String::from_str("foo")]), String::from_str("foo")); 277 | assert_eq!(comma_join(&[String::from_str("foo"), String::from_str("bar")]), String::from_str("foo, bar")); 278 | assert_eq!(comma_join(&[String::from_str("foo"), String::from_str("bar"), String::from_str("baz"), String::from_str("quux")]), String::from_str("foo, bar, baz, quux")); 279 | assert_eq!(comma_join(&[String::from_str("\"foo,bar\""), String::from_str("baz")]), String::from_str("\"foo,bar\", baz")); 280 | assert_eq!(comma_join(&[String::from_str(" foo;q=0.8 "), String::from_str("bar/* ")]), String::from_str(" foo;q=0.8 , bar/* ")); 281 | } 282 | 283 | #[test] 284 | fn test_push_maybe_quoted_string() { 285 | assert_eq!(push_maybe_quoted_string(String::from_str("foo,"), &String::from_str("bar")), String::from_str("foo,bar")); 286 | assert_eq!(push_maybe_quoted_string(String::from_str("foo,"), &String::from_str("bar/baz")), String::from_str("foo,\"bar/baz\"")); 287 | } 288 | 289 | #[test] 290 | fn test_maybe_quoted_string() { 291 | assert_eq!(maybe_quoted_string(&String::from_str("bar")), String::from_str("bar")); 292 | assert_eq!(maybe_quoted_string(&String::from_str("bar/baz \"yay\"")), String::from_str("\"bar/baz \\\"yay\\\"\"")); 293 | } 294 | 295 | #[test] 296 | fn test_push_quoted_string() { 297 | assert_eq!(push_quoted_string(String::from_str("foo,"), &String::from_str("bar")), String::from_str("foo,\"bar\"")); 298 | assert_eq!(push_quoted_string(String::from_str("foo,"), &String::from_str("bar/baz \"yay\\\"")), 299 | String::from_str("foo,\"bar/baz \\\"yay\\\\\\\"\"")); 300 | } 301 | 302 | #[test] 303 | fn test_quoted_string() { 304 | assert_eq!(quoted_string(&String::from_str("bar")), String::from_str("\"bar\"")); 305 | assert_eq!(quoted_string(&String::from_str("bar/baz \"yay\\\"")), String::from_str("\"bar/baz \\\"yay\\\\\\\"\"")); 306 | } 307 | 308 | #[test] 309 | fn test_unquote_string() { 310 | assert_eq!(unquote_string(&String::from_str("bar")), None); 311 | assert_eq!(unquote_string(&String::from_str("\"bar\"")), Some(String::from_str("bar"))); 312 | assert_eq!(unquote_string(&String::from_str("\"bar/baz \\\"yay\\\\\\\"\"")), Some(String::from_str("bar/baz \"yay\\\""))); 313 | assert_eq!(unquote_string(&String::from_str("\"bar")), None); 314 | assert_eq!(unquote_string(&String::from_str(" \"bar\"")), None); 315 | assert_eq!(unquote_string(&String::from_str("\"bar\" ")), None); 316 | assert_eq!(unquote_string(&String::from_str("\"bar\" \"baz\"")), None); 317 | assert_eq!(unquote_string(&String::from_str("\"bar/baz \\\"yay\\\\\"\"")), None); 318 | } 319 | 320 | #[test] 321 | fn test_maybe_unquote_string() { 322 | assert_eq!(maybe_unquote_string(&String::from_str("bar")), Some(String::from_str("bar"))); 323 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar\"")), Some(String::from_str("bar"))); 324 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar/baz \\\"yay\\\\\\\"\"")), Some(String::from_str("bar/baz \"yay\\\""))); 325 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar")), None); 326 | assert_eq!(maybe_unquote_string(&String::from_str(" \"bar\"")), None); 327 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar\" ")), None); 328 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar\" \"baz\"")), None); 329 | assert_eq!(maybe_unquote_string(&String::from_str("\"bar/baz \\\"yay\\\\\"\"")), None); 330 | } 331 | 332 | #[test] 333 | fn test_push_parameter() { 334 | assert_eq!(push_parameter(String::from_str("foo"), &String::from_str("bar"), &String::from_str("baz")), String::from_str("foobar=baz")); 335 | assert_eq!(push_parameter(String::from_str("foo"), &String::from_str("bar"), &String::from_str("baz/quux")), String::from_str("foobar=\"baz/quux\"")); 336 | } 337 | 338 | #[test] 339 | fn test_push_parameters() { 340 | assert_eq!(push_parameters(String::from_str("foo"), &[][]), String::from_str("foo")); 341 | assert_eq!(push_parameters(String::from_str("foo"), &[(String::from_str("bar"), String::from_str("baz"))][]), String::from_str("foo;bar=baz")); 342 | assert_eq!(push_parameters(String::from_str("foo"), &[(String::from_str("bar"), String::from_str("baz/quux"))][]), String::from_str("foo;bar=\"baz/quux\"")); 343 | assert_eq!(push_parameters(String::from_str("foo"), &[(String::from_str("bar"), String::from_str("baz")), (String::from_str("quux"), String::from_str("fuzz"))][]), 344 | String::from_str("foo;bar=baz;quux=fuzz")); 345 | assert_eq!(push_parameters(String::from_str("foo"), &[(String::from_str("bar"), String::from_str("baz")), (String::from_str("quux"), String::from_str("fuzz zee"))][]), 346 | String::from_str("foo;bar=baz;quux=\"fuzz zee\"")); 347 | assert_eq!(push_parameters(String::from_str("foo"), &[(String::from_str("bar"), String::from_str("baz/quux")), (String::from_str("fuzz"), String::from_str("zee"))][]), 348 | String::from_str("foo;bar=\"baz/quux\";fuzz=zee")); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/http/headers/test_utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::{MemReader, MemWriter}; 2 | use std::fmt; 3 | use std::vec::Vec; 4 | use headers::{HeaderConvertible, HeaderValueByteIterator}; 5 | 6 | pub fn from_stream_with_str(s: &str) -> Option { 7 | let mut bytes = s.bytes().collect::>(); 8 | bytes.push_all(b"\r\n/"); 9 | let mut reader = MemReader::new(bytes); 10 | let mut iter = HeaderValueByteIterator::new(&mut reader); 11 | HeaderConvertible::from_stream(&mut iter) 12 | } 13 | 14 | pub fn to_stream_into_str(v: &T) -> String { 15 | let mut writer = MemWriter::new(); 16 | v.to_stream(&mut writer).unwrap(); 17 | String::from_utf8(writer.get_ref().to_vec()).unwrap() 18 | } 19 | 20 | // Verify that a value cannot be successfully interpreted as a header value of the specified type. 21 | #[inline] 22 | pub fn assert_invalid(string: &str) { 23 | assert_eq!(from_stream_with_str::(string), None); 24 | } 25 | 26 | // Verify that all of the methods from the HeaderConvertible trait work correctly for the given 27 | // valid header value and correct decoded value. 28 | #[inline] 29 | pub fn assert_conversion_correct(string: &'static str, value: T) { 30 | assert_eq!(from_stream_with_str(string), Some(value.clone())); 31 | let s = to_stream_into_str(&value); 32 | assert_eq!(&s[], string); 33 | let s = value.http_value(); 34 | assert_eq!(&s[], string); 35 | } 36 | 37 | // Verify that from_stream interprets the given valid header value correctly. 38 | #[inline] 39 | pub fn assert_interpretation_correct(string: &'static str, value: T) { 40 | assert_eq!(from_stream_with_str(string), Some(value)); 41 | } 42 | -------------------------------------------------------------------------------- /src/http/headers/transfer_encoding.rs: -------------------------------------------------------------------------------- 1 | //! The Transfer-Encoding request header, defined in RFC 2616, sections 14.41 and 3.6. 2 | //! 3 | //! Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding 4 | 5 | use std::ascii::AsciiExt; 6 | use std::io::IoResult; 7 | use headers::serialization_utils::{WriterUtil, push_parameters}; 8 | 9 | pub use self::TransferCoding::{Chunked, TransferExtension}; 10 | 11 | /// RFC 2616, section 3.6: 12 | /// 13 | /// transfer-coding = "chunked" | transfer-extension 14 | /// transfer-extension = token *( ";" parameter ) 15 | #[derive(Clone, PartialEq, Eq)] 16 | pub enum TransferCoding { 17 | Chunked, 18 | TransferExtension(String, Vec<(String, String)>), 19 | } 20 | 21 | impl super::CommaListHeaderConvertible for TransferCoding {} 22 | 23 | impl super::HeaderConvertible for TransferCoding { 24 | fn from_stream(reader: &mut super::HeaderValueByteIterator) 25 | -> Option { 26 | match reader.read_token() { 27 | Some(token) => { 28 | // XXX is this actually the best way to do this? 29 | let token = token.to_ascii_lowercase(); 30 | if token == "chunked" { 31 | Some(Chunked) 32 | } else { 33 | match reader.read_parameters() { 34 | Some(parameters) => Some(TransferExtension(token, parameters)), 35 | None => None, 36 | } 37 | } 38 | } 39 | None => None, 40 | } 41 | } 42 | 43 | fn to_stream(&self, writer: &mut W) -> IoResult<()> { 44 | match *self { 45 | Chunked => writer.write(b"chunked"), 46 | TransferExtension(ref token, ref parameters) => { 47 | try!(writer.write_token(token)); 48 | writer.write_parameters(¶meters[]) 49 | } 50 | } 51 | } 52 | 53 | fn http_value(&self) -> String { 54 | match *self { 55 | Chunked => String::from_str("chunked"), 56 | TransferExtension(ref token, ref parameters) => { 57 | push_parameters(token.clone(), ¶meters[]) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/http/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "http"] 2 | 3 | #![doc(html_root_url = "http://www.rust-ci.org/chris-morgan/rust-http/doc/")] 4 | 5 | #![deny(non_camel_case_types)] 6 | //#[deny(missing_doc)]; 7 | #![allow(unstable)] 8 | 9 | #[macro_use] extern crate log; 10 | extern crate url; 11 | extern crate time; 12 | extern crate collections; 13 | 14 | pub mod buffer; 15 | pub mod client; 16 | pub mod common; 17 | pub mod connecter; 18 | pub mod server; 19 | pub mod method; 20 | pub mod headers; 21 | pub mod rfc2616; 22 | include!(concat!(env!("OUT_DIR"), "/status.rs")); // defines pub mod status 23 | 24 | /// TODO: submit upstream 25 | #[cfg(test)] 26 | pub mod memstream; 27 | -------------------------------------------------------------------------------- /src/http/memstream.rs: -------------------------------------------------------------------------------- 1 | /// TODO: submit upstream 2 | 3 | use std::io::{IoResult, Seek, SeekStyle}; 4 | use std::io::{MemReader, MemWriter}; 5 | 6 | /// Writes to an owned, growable byte vector but also implements read with fail-on-call methods. 7 | struct MemWriterFakeStream(MemWriter); 8 | 9 | impl MemWriterFakeStream { 10 | pub fn new() -> MemWriterFakeStream { MemWriterFakeStream(MemWriter::new()) } 11 | 12 | pub fn get_ref(&self) -> &[u8] { 13 | let &MemWriterFakeStream(ref s) = self; 14 | s.get_ref() 15 | } 16 | } 17 | 18 | impl Writer for MemWriterFakeStream { 19 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 20 | let &mut MemWriterFakeStream(ref mut s) = self; 21 | s.write(buf) 22 | } 23 | 24 | fn flush(&mut self) -> IoResult<()> { 25 | let &mut MemWriterFakeStream(ref mut s) = self; 26 | s.flush() 27 | } 28 | } 29 | 30 | impl Reader for MemWriterFakeStream { 31 | fn read(&mut self, _buf: &mut [u8]) -> IoResult { 32 | panic!("Uh oh, you didn't aught to call MemWriterFakeStream.read()!") 33 | } 34 | } 35 | 36 | /// Reads from an owned byte vector, but also implements write with fail-on-call methods. 37 | pub struct MemReaderFakeStream(MemReader); 38 | 39 | impl MemReaderFakeStream { 40 | pub fn new(buf: Vec) -> MemReaderFakeStream { MemReaderFakeStream(MemReader::new(buf)) } 41 | } 42 | 43 | impl Reader for MemReaderFakeStream { 44 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 45 | let &mut MemReaderFakeStream(ref mut s) = self; 46 | s.read(buf) 47 | } 48 | } 49 | 50 | impl Seek for MemReaderFakeStream { 51 | fn tell(&self) -> IoResult { 52 | let &MemReaderFakeStream(ref s) = self; 53 | s.tell() 54 | } 55 | 56 | fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> { 57 | let &mut MemReaderFakeStream(ref mut s) = self; 58 | s.seek(pos, style) 59 | } 60 | } 61 | 62 | impl Writer for MemReaderFakeStream { 63 | fn write(&mut self, _buf: &[u8]) -> IoResult<()> { 64 | panic!("Uh oh, you didn't aught to call MemReaderFakeStream.write()!") 65 | } 66 | fn flush(&mut self) -> IoResult<()> { 67 | panic!("Uh oh, you didn't aught to call MemReaderFakeStream.flush()!") 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod test { 73 | use super::{MemReaderFakeStream, MemWriterFakeStream}; 74 | 75 | #[test] 76 | fn test_mem_writer_fake_stream() { 77 | let mut writer = MemWriterFakeStream::new(); 78 | assert_eq!(writer.get_ref(), []); 79 | assert_eq!(writer.write(&[0]), Ok(())); 80 | assert_eq!(writer.get_ref(), [0]); 81 | assert_eq!(writer.write(&[1, 2, 3]), Ok(())); 82 | assert_eq!(writer.write(&[4, 5, 6, 7]), Ok(())); 83 | assert_eq!(writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7]); 84 | } 85 | 86 | #[test] 87 | fn test_mem_reader_fake_stream() { 88 | let mut reader = MemReaderFakeStream::new(vec!(0, 1, 2, 3, 4, 5, 6, 7)); 89 | let mut buf = vec![]; 90 | assert_eq!(reader.read(buf.as_mut_slice()), Ok(0)); 91 | assert_eq!(reader.tell(), Ok(0)); 92 | let mut buf = vec![0]; 93 | assert_eq!(reader.read(buf.as_mut_slice()), Ok(1)); 94 | assert_eq!(reader.tell(), Ok(1)); 95 | assert_eq!(buf, vec![0]); 96 | let mut buf = vec![0, 0, 0, 0]; 97 | assert_eq!(reader.read(buf.as_mut_slice()), Ok(4)); 98 | assert_eq!(reader.tell(), Ok(5)); 99 | assert_eq!(buf, vec![1, 2, 3, 4]); 100 | assert_eq!(reader.read(buf.as_mut_slice()), Ok(3)); 101 | assert_eq!(&buf[0..3], [5, 6, 7]); 102 | assert_eq!(reader.read(buf.as_mut_slice()).ok(), None); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/http/method.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | use std::ascii::AsciiExt; 4 | 5 | pub use self::Method::{Options, Get, Head, Post, Put, Delete, Trace, 6 | Connect, Patch, ExtensionMethod}; 7 | 8 | /// HTTP methods, as defined in RFC 2616, §5.1.1. 9 | /// 10 | /// Method names are case-sensitive. 11 | #[derive(PartialEq, Eq, Clone, Hash)] 12 | pub enum Method { 13 | Options, 14 | Get, 15 | Head, 16 | Post, 17 | Put, 18 | Delete, 19 | Trace, 20 | Connect, 21 | Patch, // RFC 5789 22 | ExtensionMethod(String), 23 | } 24 | 25 | impl FromStr for Method { 26 | /** 27 | * Get a *known* `Method` from an *ASCII* string, regardless of case. 28 | * 29 | * If you want to support unregistered methods, use `from_str_or_new` instead. 30 | * 31 | * (If the string isn't ASCII, this will at present fail: TODO fix that.) 32 | */ 33 | fn from_str(method: &str) -> Option { 34 | if !method.is_ascii() { 35 | return None; 36 | } 37 | match method { 38 | "OPTIONS" => Some(Options), 39 | "GET" => Some(Get), 40 | "HEAD" => Some(Head), 41 | "POST" => Some(Post), 42 | "PUT" => Some(Put), 43 | "DELETE" => Some(Delete), 44 | "TRACE" => Some(Trace), 45 | "CONNECT" => Some(Connect), 46 | "PATCH" => Some(Patch), 47 | _ => None 48 | } 49 | } 50 | } 51 | 52 | impl fmt::Show for Method { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | f.write_str(match *self { 55 | Options => "OPTIONS", 56 | Get => "GET", 57 | Head => "HEAD", 58 | Post => "POST", 59 | Put => "PUT", 60 | Delete => "DELETE", 61 | Trace => "TRACE", 62 | Connect => "CONNECT", 63 | Patch => "PATCH", 64 | ExtensionMethod(ref s) => &s[], 65 | }) 66 | } 67 | } 68 | 69 | impl Method { 70 | /** 71 | * Get a `Method` from an *ASCII* string. 72 | * 73 | * (If the string isn't ASCII, this will at present fail.) 74 | */ 75 | pub fn from_str_or_new(method: &str) -> Option { 76 | assert!(method.is_ascii()); 77 | Some(match method { 78 | "OPTIONS" => Options, 79 | "GET" => Get, 80 | "HEAD" => Head, 81 | "POST" => Post, 82 | "PUT" => Put, 83 | "DELETE" => Delete, 84 | "TRACE" => Trace, 85 | "CONNECT" => Connect, 86 | "PATCH" => Patch, 87 | _ => ExtensionMethod(String::from_str(method)), 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/http/rfc2616.rs: -------------------------------------------------------------------------------- 1 | //! Values taken from RFC 2616 2 | 3 | use std::fmt; 4 | use std::str::FromStr; 5 | use std::ascii::AsciiExt; 6 | 7 | /// OCTET: any 8-bit sequence of data (typechecking ensures this to be true) 8 | #[inline] 9 | pub fn is_octet(_: u8) -> bool { true } 10 | 11 | /// CHAR: any US-ASCII character (octets 0 - 127) 12 | #[inline] 13 | pub fn is_char(octet: u8) -> bool { octet < 128 } 14 | 15 | /// UPALPHA: any US-ASCII uppercase letter "A".."Z"> 16 | #[inline] 17 | pub fn is_upalpha(octet: u8) -> bool { octet >= b'A' && octet <= b'Z' } 18 | 19 | /// LOALPHA: any US-ASCII lowercase letter "a".."z"> 20 | #[inline] 21 | pub fn is_loalpha(octet: u8) -> bool { octet >= b'a' && octet <= b'z' } 22 | 23 | /// ALPHA: UPALPHA | LOALPHA 24 | #[inline] 25 | pub fn is_alpha(octet: u8) -> bool { is_upalpha(octet) || is_loalpha(octet) } 26 | 27 | /// DIGIT: any US-ASCII digit "0".."9" 28 | #[inline] 29 | pub fn is_digit(octet: u8) -> bool { octet >= b'0' && octet <= b'9' } 30 | 31 | /// CTL: any US-ASCII control character (octets 0 - 31) and DEL (127) 32 | #[inline] 33 | pub fn is_ctl(octet: u8) -> bool { octet < 32 || octet == 127 } 34 | 35 | /// CR: US-ASCII CR, carriage return (13) 36 | pub const CR: u8 = b'\r'; 37 | 38 | /// LF: US-ASCII LF, linefeed (10) 39 | pub const LF: u8 = b'\n'; 40 | 41 | /// SP: US-ASCII SP, space (32) 42 | pub const SP: u8 = b' '; 43 | 44 | /// HT: US-ASCII HT, horizontal-tab (9) 45 | pub const HT: u8 = b'\t'; 46 | 47 | /// US-ASCII colon (58) 48 | pub const COLON: u8 = b':'; 49 | 50 | /// <">: US-ASCII double-quote mark (34) 51 | pub const DOUBLE_QUOTE: u8 = b'"'; 52 | 53 | /// "\": US-ASCII backslash (92) 54 | pub const BACKSLASH: u8 = b'\\'; 55 | 56 | // CRLF: CR LF 57 | //const CRLF: [u8] = [CR, LF]; 58 | 59 | // LWS: [CRLF] 1*( SP | HT ) 60 | /*#[inline] 61 | fn is_lws(octets: &[u8]) -> bool { 62 | let mut has_crlf = false; 63 | let mut iter = octets.iter(); 64 | if len(octets) == 0 { 65 | return false; 66 | } 67 | if len(octets) > 2 && octets[0] == CR && octets[1] == LF { 68 | iter = iter.next().next(); 69 | } 70 | iter.all(|&octet| octet == SP || octet == HT) 71 | } 72 | */ 73 | 74 | /* 75 | The TEXT rule is only used for descriptive field contents and values 76 | that are not intended to be interpreted by the message parser. Words 77 | of *TEXT MAY contain characters from character sets other than ISO- 78 | 8859-1 [22] only when encoded according to the rules of RFC 2047 79 | [14]. 80 | 81 | TEXT = 83 | 84 | A CRLF is allowed in the definition of TEXT only as part of a header 85 | field continuation. It is expected that the folding LWS will be 86 | replaced with a single SP before interpretation of the TEXT value. 87 | */ 88 | 89 | /// HEX: "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" | DIGIT 90 | #[inline] 91 | pub fn is_hex(octet: u8) -> bool { 92 | (octet >= b'A' && octet <= b'F') || 93 | (octet >= b'a' && octet <= b'f') || 94 | is_digit(octet) 95 | } 96 | 97 | /// token = 1* 98 | #[inline] 99 | pub fn is_token_item(o: u8) -> bool { 100 | is_char(o) && !is_ctl(o) && !is_separator(o) 101 | } 102 | 103 | #[inline] 104 | pub fn is_token(s: &String) -> bool { 105 | s[].bytes().all(|b| is_token_item(b)) 106 | } 107 | 108 | 109 | /// separators: "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" 110 | /// | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" 111 | /// | "}" | SP | HT 112 | #[inline] 113 | pub fn is_separator(o: u8) -> bool { 114 | o == b'(' || o == b')' || o == b'<' || o == b'>' || o == b'@' || 115 | o == b',' || o == b';' || o == b':' || o == b'\\' || o == b'"' || 116 | o == b'/' || o == b'[' || o == b']' || o == b'?' || o == b'=' || 117 | o == b'{' || o == b'}' || o == SP || o == HT 118 | } 119 | 120 | /* 121 | * Comments can be included in some HTTP header fields by surrounding 122 | * the comment text with parentheses. Comments are only allowed in 123 | * fields containing "comment" as part of their field value definition. 124 | * In all other fields, parentheses are considered part of the field 125 | * value. 126 | * 127 | * comment = "(" *( ctext | quoted-pair | comment ) ")" 128 | * ctext = 129 | * 130 | * A string of text is parsed as a single word if it is quoted using 131 | * double-quote marks. 132 | * 133 | * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) 134 | * qdtext = > 135 | * 136 | * The backslash character ("\") MAY be used as a single-character 137 | * quoting mechanism only within quoted-string and comment constructs. 138 | * 139 | * quoted-pair = "\" CHAR 140 | */ 141 | //#[inline] 142 | //fn is_quoted_pair(o: &[u8, ..2]) { o[0] == 92 } 143 | 144 | // IANA is assigned as maintaining the registry for these things: 145 | // see https://www.iana.org/assignments/http-parameters/http-parameters.xml 146 | 147 | /// Content-coding value tokens 148 | #[derive(Copy)] 149 | pub enum ContentCoding { 150 | // An encoding format produced by the file compression program "gzip" (GNU zip) as described 151 | // in RFC 1952 [25]. This format is a Lempel-Ziv coding (LZ77) with a 32 bit CRC. 152 | Gzip, 153 | 154 | // The encoding format produced by the common UNIX file compression program "compress". This 155 | // format is an adaptive Lempel-Ziv-Welch coding (LZW). 156 | // 157 | // Use of program names for the identification of encoding formats is not desirable and is 158 | // discouraged for future encodings. Their use here is representative of historical 159 | // practice, not good design. For compatibility with previous implementations of HTTP, 160 | // applications SHOULD consider "x-gzip" and "x-compress" to be equivalent to "gzip" and 161 | // "compress" respectively. 162 | Compress, 163 | 164 | // The "zlib" format defined in RFC 1950 [31] in combination with the "deflate" compression 165 | // mechanism described in RFC 1951 [29]. 166 | Deflate, 167 | 168 | // The default (identity) encoding; the use of no transformation whatsoever. This 169 | // content-coding is used only in the Accept- Encoding header, and SHOULD NOT be used in the 170 | // Content-Encoding header. 171 | Identity, 172 | 173 | // IANA has also assigned the following currently unsupported content codings: 174 | // 175 | // - "exi": W3C Efficient XML Interchange 176 | // - "pack200-gzip" (Network Transfer Format for Java Archives) 177 | } 178 | 179 | impl fmt::Show for ContentCoding { 180 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 181 | f.write_str(match *self { 182 | ContentCoding::Gzip => "gzip", 183 | ContentCoding::Compress => "compress", 184 | ContentCoding::Deflate => "deflate", 185 | ContentCoding::Identity => "identity", 186 | }) 187 | } 188 | } 189 | 190 | impl FromStr for ContentCoding { 191 | fn from_str(s: &str) -> Option { 192 | if s.eq_ignore_ascii_case("gzip") { 193 | Some(ContentCoding::Gzip) 194 | } else if s.eq_ignore_ascii_case("compress") { 195 | Some(ContentCoding::Compress) 196 | } else if s.eq_ignore_ascii_case("deflate") { 197 | Some(ContentCoding::Deflate) 198 | } else if s.eq_ignore_ascii_case("identity") { 199 | Some(ContentCoding::Identity) 200 | } else { 201 | None 202 | } 203 | } 204 | } 205 | 206 | /// Transfer-coding value tokens 207 | // Identity is in RFC 2616 but is withdrawn in RFC 2616 errata ID 408 208 | // http://www.rfc-editor.org/errata_search.php?rfc=2616&eid=408 209 | #[derive(Copy)] 210 | pub enum TransferCoding { 211 | Chunked, // RFC 2616, §3.6.1 212 | Gzip, // See above 213 | Compress, // See above 214 | Deflate, // See above 215 | } 216 | 217 | impl fmt::Show for TransferCoding { 218 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 219 | f.write_str(match *self { 220 | TransferCoding::Chunked => "chunked", 221 | TransferCoding::Gzip => "gzip", 222 | TransferCoding::Compress => "compress", 223 | TransferCoding::Deflate => "deflate", 224 | }) 225 | } 226 | } 227 | 228 | impl FromStr for TransferCoding { 229 | fn from_str(s: &str) -> Option { 230 | if s.eq_ignore_ascii_case("gzip") { 231 | Some(TransferCoding::Gzip) 232 | } else if s.eq_ignore_ascii_case("compress") { 233 | Some(TransferCoding::Compress) 234 | } else if s.eq_ignore_ascii_case("deflate") { 235 | Some(TransferCoding::Deflate) 236 | } else if s.eq_ignore_ascii_case("chunked") { 237 | Some(TransferCoding::Chunked) 238 | } else { 239 | None 240 | } 241 | } 242 | } 243 | 244 | // A server which receives an entity-body with a transfer-coding it does 245 | // not understand SHOULD return 501 (Unimplemented), and close the 246 | // connection. A server MUST NOT send transfer-codings to an HTTP/1.0 247 | // client. 248 | 249 | // [My note: implication of this is that I need to track HTTP version number of clients.] 250 | -------------------------------------------------------------------------------- /src/http/server/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Listener, Acceptor, IoResult}; 2 | use std::io::net::ip::SocketAddr; 3 | use time::precise_time_ns; 4 | use std::thread::Thread; 5 | use std::sync::mpsc::{channel, Receiver}; 6 | 7 | use std::io::net::tcp::TcpListener; 8 | 9 | use buffer::BufferedStream; 10 | 11 | pub use self::request::{RequestBuffer, Request}; 12 | pub use self::response::ResponseWriter; 13 | 14 | pub mod request; 15 | pub mod response; 16 | 17 | pub trait Server: Send + Clone { 18 | fn handle_request(&self, request: Request, response: &mut ResponseWriter) -> (); 19 | 20 | // XXX: this could also be implemented on the serve methods 21 | fn get_config(&self) -> Config; 22 | 23 | /** 24 | * Attempt to bind to the address and port and start serving forever. 25 | * 26 | * This will only return if the initial connection fails or something else blows up. 27 | */ 28 | fn serve_forever(self) { 29 | let config = self.get_config(); 30 | debug!("About to bind to {}", config.bind_address); 31 | let mut acceptor = match TcpListener::bind(config.bind_address).listen() { 32 | Err(err) => { 33 | error!("bind or listen failed :-(: {}", err); 34 | return; 35 | }, 36 | Ok(acceptor) => acceptor, 37 | }; 38 | debug!("listening"); 39 | let (perf_sender, perf_receiver) = channel(); 40 | Thread::spawn(move || { 41 | perf_dumper(perf_receiver); 42 | }); 43 | loop { 44 | let time_start = precise_time_ns(); 45 | let stream = match acceptor.accept() { 46 | Err(error) => { 47 | debug!("accept failed: {}", error); 48 | // Question: is this the correct thing to do? We should probably be more 49 | // intelligent, for there are some accept failures that are likely to be 50 | // permanent, such that continuing would be a very bad idea, such as 51 | // ENOBUFS/ENOMEM; and some where it should just be ignored, e.g. 52 | // ECONNABORTED. TODO. 53 | continue; 54 | }, 55 | Ok(socket) => socket, 56 | }; 57 | let child_perf_sender = perf_sender.clone(); 58 | let child_self = self.clone(); 59 | Thread::spawn(move || { 60 | let mut time_start = time_start; 61 | let mut stream = BufferedStream::new(stream); 62 | debug!("accepted connection"); 63 | let mut first = true; 64 | loop { // A keep-alive loop, condition at end 65 | let mut time_spawned = precise_time_ns(); 66 | let (request, err_status) = Request::load(&mut stream); 67 | let close_connection = request.close_connection; 68 | let time_request_made = precise_time_ns(); 69 | if !first { 70 | // Subsequent requests on this connection have no spawn time. 71 | // Moreover we cannot detect the time spent parsing the request as we have 72 | // not exposed the time when the first byte was received. 73 | time_start = time_request_made; 74 | time_spawned = time_request_made; 75 | } 76 | let mut response = ResponseWriter::new(&mut stream); 77 | let time_response_made = precise_time_ns(); 78 | match err_status { 79 | Ok(()) => { 80 | child_self.handle_request(request, &mut response); 81 | // Ensure that we actually do send a response: 82 | match response.try_write_headers() { 83 | Err(err) => { 84 | error!("Writing headers failed: {}", err); 85 | return; // Presumably bad connection, so give up. 86 | }, 87 | Ok(_) => (), 88 | } 89 | }, 90 | Err(status) => { 91 | // Uh oh, it's a response that I as a server cannot cope with. 92 | // No good user-agent should have caused this, so for the moment 93 | // at least I am content to send no body in the response. 94 | response.status = status; 95 | response.headers.content_length = Some(0); 96 | match response.write_headers() { 97 | Err(err) => { 98 | error!("Writing headers failed: {}", err); 99 | return; // Presumably bad connection, so give up. 100 | }, 101 | Ok(_) => (), 102 | } 103 | }, 104 | } 105 | // Ensure the request is flushed, any Transfer-Encoding completed, etc. 106 | match response.finish_response() { 107 | Err(err) => { 108 | error!("finishing response failed: {}", err); 109 | return; // Presumably bad connection, so give up. 110 | }, 111 | Ok(_) => (), 112 | } 113 | let time_finished = precise_time_ns(); 114 | child_perf_sender.send((time_start, time_spawned, time_request_made, time_response_made, time_finished)).unwrap(); 115 | 116 | if close_connection { 117 | break; 118 | } 119 | first = false; 120 | } 121 | }); 122 | } 123 | } 124 | 125 | /** 126 | * Attempt to bind to the address and port and serve for only one request. 127 | * 128 | * The server will wait for one request and handle it before the program continues. 129 | * It will not be kept alive. 130 | * 131 | * # Arguments 132 | * - `retry_accept` - try to accept an other connection if accept failed. 133 | * - `timeout_ms` - optional timeout in milliseconds. 134 | */ 135 | fn serve_once(&self, retry_accept: bool, timeout_ms: Option) -> IoResult<()> { 136 | let config = self.get_config(); 137 | debug!("About to bind to {}", config.bind_address); 138 | let mut acceptor = try!(TcpListener::bind(config.bind_address).listen()); 139 | debug!("listening for one request"); 140 | 141 | loop { 142 | acceptor.set_timeout(timeout_ms); 143 | let stream = match acceptor.accept() { 144 | Err(error) => { 145 | debug!("accept failed: {}", error); 146 | // Question: is this the correct thing to do? We should probably be more 147 | // intelligent, for there are some accept failures that are likely to be 148 | // permanent, such that continuing would be a very bad idea, such as 149 | // ENOBUFS/ENOMEM; and some where it should just be ignored, e.g. 150 | // ECONNABORTED. TODO. 151 | if retry_accept { 152 | continue; 153 | } else { 154 | return Err(error); 155 | } 156 | }, 157 | Ok(socket) => socket, 158 | }; 159 | 160 | let mut stream = BufferedStream::new(stream); 161 | debug!("accepted connection"); 162 | let (request, err_status) = Request::load(&mut stream); 163 | let mut response = ResponseWriter::new(&mut stream); 164 | match err_status { 165 | Ok(()) => { 166 | self.handle_request(request, &mut response); 167 | // Ensure that we actually do send a response: 168 | try!(response.try_write_headers()); 169 | }, 170 | Err(status) => { 171 | // Uh oh, it's a response that I as a server cannot cope with. 172 | // No good user-agent should have caused this, so for the moment 173 | // at least I am content to send no body in the response. 174 | response.status = status; 175 | response.headers.content_length = Some(0); 176 | try!(response.write_headers()); 177 | }, 178 | } 179 | // Ensure the request is flushed, any Transfer-Encoding completed, etc. 180 | try!(response.finish_response()); 181 | 182 | break; 183 | } 184 | 185 | Ok(()) 186 | } 187 | } 188 | 189 | /// The necessary configuration for an HTTP server. 190 | /// 191 | /// At present, only the IP address and port to bind to are needed, but it's possible that other 192 | /// options may turn up later. 193 | #[derive(Copy)] 194 | pub struct Config { 195 | pub bind_address: SocketAddr, 196 | } 197 | 198 | const PERF_DUMP_FREQUENCY : u64 = 10_000; 199 | 200 | /// Simple function to dump out perf stats every `PERF_DUMP_FREQUENCY` requests 201 | fn perf_dumper(perf_receiver: Receiver<(u64, u64, u64, u64, u64)>) { 202 | // Total durations 203 | let mut td_spawn = 0u64; 204 | let mut td_request = 0u64; 205 | let mut td_response = 0u64; 206 | let mut td_handle = 0u64; 207 | let mut td_total = 0u64; 208 | let mut i = 0u64; 209 | loop { 210 | let data = perf_receiver.recv().unwrap(); 211 | let (start, spawned, request_made, response_made, finished) = data; 212 | td_spawn += spawned - start; 213 | td_request += request_made - spawned; 214 | td_response += response_made - request_made; 215 | td_handle += finished - response_made; 216 | td_total += finished - start; 217 | i += 1; 218 | if i % PERF_DUMP_FREQUENCY == 0 { 219 | println!(""); 220 | println!("{} requests made thus far. Current means:", i); 221 | println!("- Total: 100%, {:12}", 222 | td_total as f64 / i as f64); 223 | println!("- Spawn: {:3}%, {:12}", 224 | 100f64 * td_spawn as f64 / td_total as f64, 225 | td_spawn as f64 / i as f64); 226 | println!("- Load request: {:3}%, {:12}", 227 | 100f64 * td_request as f64 / td_total as f64, 228 | td_request as f64 / i as f64); 229 | println!("- Initialise response: {:3}%, {:12}", 230 | 100f64 * td_response as f64 / td_total as f64, 231 | td_response as f64 / i as f64); 232 | println!("- Handle: {:3}%, {:12}", 233 | 100f64 * td_handle as f64 / td_total as f64, 234 | td_handle as f64 / i as f64); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/http/server/request.rs: -------------------------------------------------------------------------------- 1 | use url::Url; 2 | use method::Method; 3 | use method::Method::Options; 4 | use status; 5 | use status::Status::{BadRequest, RequestUriTooLong, HttpVersionNotSupported}; 6 | use std::io::{Stream, IoResult}; 7 | use std::io::net::ip::SocketAddr; 8 | use std::io::net::tcp::TcpStream; 9 | use std::fmt; 10 | use rfc2616::{CR, LF, SP}; 11 | use headers; 12 | use buffer::BufferedStream; 13 | use common::read_http_version; 14 | 15 | use headers::HeaderLineErr; 16 | use headers::HeaderLineErr::{EndOfFile, EndOfHeaders, MalformedHeaderSyntax, 17 | MalformedHeaderValue}; 18 | 19 | use self::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority}; 20 | 21 | // /// Line/header can't be more than 4KB long (note that with the compacting of LWS the actual source 22 | // /// data could be longer than 4KB) 23 | // const MAX_LINE_LEN: usize = 0x1000; 24 | 25 | const MAX_REQUEST_URI_LEN: usize = 1024; 26 | pub const MAX_METHOD_LEN: usize = 64; 27 | 28 | pub struct RequestBuffer<'a, S: 'a> { 29 | /// The socket connection to read from 30 | pub stream: &'a mut BufferedStream, 31 | } 32 | 33 | impl<'a, S: Stream> RequestBuffer<'a, S> { 34 | pub fn new(stream: &'a mut BufferedStream) -> RequestBuffer<'a, S> { 35 | RequestBuffer { 36 | stream: stream, 37 | } 38 | } 39 | 40 | pub fn read_request_line(&mut self) -> Result<(Method, RequestUri, (usize, usize)), 41 | status::Status> { 42 | let method = match self.read_method() { 43 | Ok(m) => m, 44 | // TODO: this is a very common case, if a connection is kept open but then closed or 45 | // timed out. We should handle that case specially if we can improve perf—check if the 46 | // peer is still there and just drop the request if it is not 47 | Err(_) => return Err(BadRequest), 48 | }; 49 | 50 | // Finished reading the method, including consuming a single SP. 51 | // Before we read the Request-URI, we should consume *SP (it's invalid, 52 | // but the spec recommends supporting it anyway). 53 | let mut next_byte; 54 | loop { 55 | match self.stream.read_byte() { 56 | Ok(_b@SP) => continue, 57 | Ok(b) => { 58 | next_byte = b; 59 | break; 60 | }, 61 | _ => return Err(BadRequest), 62 | }; 63 | } 64 | 65 | // Good, we're now into the Request-URI. Bear in mind that as well as 66 | // ending in SP, it can for HTTP/0.9 end in CR LF or LF. 67 | let mut raw_request_uri = String::new(); 68 | loop { 69 | if next_byte == CR { 70 | // For CR, we must have an LF immediately afterwards. 71 | if self.stream.read_byte() != Ok(LF) { 72 | return Err(BadRequest); 73 | } else { 74 | // Simplify it by just dealing with the LF possibility 75 | next_byte = LF; 76 | break; 77 | } 78 | } else if next_byte == SP || next_byte == LF { 79 | break; 80 | } 81 | 82 | if raw_request_uri.len() == MAX_REQUEST_URI_LEN { 83 | return Err(RequestUriTooLong) 84 | } 85 | raw_request_uri.push(next_byte as char); 86 | 87 | next_byte = match self.stream.read_byte() { 88 | Ok(b) => b, 89 | _ => return Err(BadRequest), 90 | } 91 | } 92 | 93 | // Now parse it into a RequestUri. 94 | let request_uri = match RequestUri::from_string(raw_request_uri) { 95 | Some(r) => r, 96 | None => return Err(BadRequest), 97 | }; 98 | 99 | // At this point, we need to consider what came immediately after the 100 | // Request-URI. If it was a SP, then we expect (again after allowing for 101 | // possible *SP, though illegal) to get an HTTP-Version. If it was CR LF 102 | // or LF, we consider it to be HTTP/0.9. 103 | if next_byte == LF { 104 | // Good, we got CR LF or LF; HTTP/0.9 it is. 105 | return Ok((method, request_uri, (0, 9))); 106 | } 107 | 108 | // By this point, next_byte can only be SP. Now we want an HTTP-Version. 109 | 110 | let mut read_b = 0; 111 | 112 | // FIXME: we still have one inconsistency here: this isn't trimming *SP. 113 | match read_http_version(self.stream, &mut |b| { read_b = b; b == CR || b == LF }) { 114 | Ok(vv) => { 115 | if read_b == LF || self.stream.read_byte() == Ok(LF) { 116 | Ok((method, request_uri, vv)) // LF or CR LF: valid 117 | } else { 118 | Err(BadRequest) // CR but no LF: not valid 119 | } 120 | }, 121 | Err(_) => Err(BadRequest), // invalid: ... not valid ;-) 122 | } 123 | } 124 | 125 | #[inline] 126 | fn read_method(&mut self) -> IoResult { 127 | mod dummy { include!(concat!(env!("OUT_DIR"), "/read_method.rs")); } 128 | 129 | dummy::dummy::read_method(self.stream) 130 | } 131 | 132 | /// Read a header (name, value) pair. 133 | /// 134 | /// This is not necessarily just a line ending with CRLF; there are much fancier rules at work. 135 | /// Where appropriate (TODO, it's everywhere at present) linear whitespace is collapsed into a 136 | /// single space. 137 | /// 138 | /// # Error values 139 | /// 140 | /// - `EndOfHeaders`: I have no more headers to give; go forth and conquer on the body! 141 | /// - `EndOfFile`: socket was closed unexpectedly; probable best behavour is to drop the request 142 | /// - `MalformedHeaderValue`: header's value is invalid; normally, ignore it. 143 | /// - `MalformedHeaderSyntax`: bad request; you could drop it or try returning 400 Bad Request 144 | pub fn read_header(&mut self) -> Result { 145 | match headers::header_enum_from_stream(&mut *self.stream) { 146 | //match headers::HeaderEnum::from_stream(self.stream) { 147 | (Err(m), None) => Err(m), 148 | (Err(m), Some(b)) => { 149 | self.stream.poke_byte(b); 150 | Err(m) 151 | }, 152 | (Ok(header), Some(b)) => { 153 | self.stream.poke_byte(b); 154 | Ok(header) 155 | } 156 | (Ok(header), None) => { 157 | // This should have read an extra byte, on account of the CR LF SP possibility 158 | error!("header with no next byte, did reading go wrong?"); 159 | Ok(header) 160 | } 161 | } 162 | } 163 | } 164 | 165 | impl<'a, S: Stream> Reader for RequestBuffer<'a, S> { 166 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 167 | self.stream.read(buf) 168 | } 169 | } 170 | 171 | #[test] 172 | fn test_request_uri_from_string() { 173 | assert_eq!(RequestUri::from_string("*".to_string()), Some(RequestUri::Star)); 174 | assert_eq!(RequestUri::from_string("/abc".to_string()), Some(RequestUri::AbsolutePath("/abc".to_string()))); 175 | let url = "http://example.com/abc"; 176 | match RequestUri::from_string(url.to_string()) { 177 | Some(RequestUri::AbsoluteUri(url)) => { 178 | assert_eq!(url.domain(), Some("example.com")); 179 | assert_eq!(url.path(), Some(&["abc".to_string()][])); 180 | }, 181 | _ => panic!("Parse failed for {}", url), 182 | }; 183 | assert_eq!(RequestUri::from_string("".to_string()), None); 184 | assert_eq!(RequestUri::from_string(" ".to_string()), Some(Authority(" ".to_string()))); 185 | Url::parse("").unwrap_err(); // Url::parse() should return error for empty string 186 | } 187 | 188 | #[test] 189 | fn test_read_request_line() { 190 | use method::Method::{Get, Options, Connect, ExtensionMethod}; 191 | use buffer::BufferedStream; 192 | use memstream::MemReaderFakeStream; 193 | 194 | macro_rules! tt { 195 | ($value:expr => $expected:expr) => {{ 196 | let expected = $expected; 197 | let mut stream = BufferedStream::new( 198 | MemReaderFakeStream::new($value.bytes().collect::>())); 199 | assert_eq!(RequestBuffer::new(&mut stream).read_request_line(), expected); 200 | }} 201 | } 202 | 203 | tt!("GET / HTTP/1.1\n" => Ok((Get, AbsolutePath(String::from_str("/")), (1, 1)))); 204 | tt!("GET / HTTP/1.1\r\n" => Ok((Get, AbsolutePath(String::from_str("/")), (1, 1)))); 205 | tt!("OPTIONS /foo/bar HTTP/1.1\r\n" => Ok((Options, AbsolutePath(String::from_str("/foo/bar")), (1, 1)))); 206 | tt!("OPTIONS * HTTP/1.1\r\n" => Ok((Options, Star, (1, 1)))); 207 | tt!("CONNECT example.com HTTP/1.1\r\n" => Ok((Connect, 208 | Authority(String::from_str("example.com")), 209 | (1, 1)))); 210 | tt!("FOO /\r\n" => Ok((ExtensionMethod(String::from_str("FOO")), AbsolutePath(String::from_str("/")), (0, 9)))); 211 | tt!("FOO /\n" => Ok((ExtensionMethod(String::from_str("FOO")), AbsolutePath(String::from_str("/")), (0, 9)))); 212 | tt!("get http://example.com/ HTTP/42.17\r\n" 213 | => Ok((ExtensionMethod(String::from_str("get")), 214 | AbsoluteUri(Url::parse("http://example.com/").unwrap()), 215 | (42, 17)))); 216 | 217 | // Now for some failing cases. 218 | 219 | // method name is not a token 220 | tt!("GE,T / HTTP/1.1\r\n" => Err(BadRequest)); 221 | 222 | // Request-URI is missing ("HTTP/1.1" isn't a valid Request-URI; I confirmed this by tracing the 223 | // rule through RFC 2396: the "/" prevents it from being a reg_name authority, and it doesn't 224 | // satisfy any of the other possibilities for Request-URI either) 225 | tt!("GET HTTP/1.1\r\n" => Err(BadRequest)); 226 | 227 | // Invalid HTTP-Version 228 | tt!("GET / HTTX/1.1\r\n" => Err(BadRequest)); 229 | } 230 | 231 | /// An HTTP request sent to the server. 232 | pub struct Request { 233 | /// The originating IP address of the request. 234 | pub remote_addr: Option, 235 | 236 | /// The host name and IP address that the request was sent to; this must always be specified for 237 | /// HTTP/1.1 requests (or the request will be rejected), but for HTTP/1.0 requests the Host 238 | /// header was not defined, and so this field will probably be None in such cases. 239 | //host: Option, // Now in headers.host 240 | 241 | /// The headers sent with the request. 242 | pub headers: headers::request::HeaderCollection, 243 | 244 | /// The body of the request; empty for such methods as GET. 245 | pub body: Vec, 246 | 247 | /// The HTTP method for the request. 248 | pub method: Method, 249 | 250 | /// The URI that was requested, as found in the Request-URI of the Request-Line. 251 | /// You will almost never need to use this; you should prefer the `url` field instead. 252 | pub request_uri: RequestUri, 253 | 254 | /// Whether to close the TCP connection when the request has been served. 255 | /// The alternative is keeping the connection open and waiting for another request. 256 | pub close_connection: bool, 257 | 258 | /// The HTTP version number; typically `(1, 1)` or, less commonly, `(1, 0)`. 259 | pub version: (usize, usize) 260 | } 261 | 262 | /// The URI (Request-URI in RFC 2616) as specified in the Status-Line of an HTTP request 263 | #[derive(PartialEq, Eq)] 264 | pub enum RequestUri { 265 | /// 'The asterisk "*" means that the request does not apply to a particular resource, but to the 266 | /// server itself, and is only allowed when the method used does not necessarily apply to a 267 | /// resource. One example would be "OPTIONS * HTTP/1.1" ' 268 | Star, 269 | 270 | /// 'The absoluteURI form is REQUIRED when the request is being made to a proxy. The proxy is 271 | /// requested to forward the request or service it from a valid cache, and return the response. 272 | /// Note that the proxy MAY forward the request on to another proxy or directly to the server 273 | /// specified by the absoluteURI. In order to avoid request loops, a proxy MUST be able to 274 | /// recognize all of its server names, including any aliases, local variations, and the numeric 275 | /// IP address. An example Request-Line would be: 276 | /// "GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1"' 277 | AbsoluteUri(Url), 278 | 279 | /// 'To allow for transition to absoluteURIs in all requests in future versions of HTTP, all 280 | /// HTTP/1.1 servers MUST accept the absoluteURI form in requests, even though HTTP/1.1 clients 281 | /// will only generate them in requests to proxies.' 282 | /// 283 | /// TODO: this shouldn't be a string; it should be further parsed. `extra::net::url` has some 284 | /// stuff which might help, but isn't public. 285 | AbsolutePath(String), 286 | 287 | /// 'The authority form is only used by the CONNECT method (CONNECT).' 288 | /// 289 | /// TODO: this shouldn't be a string; it should be further parsed. `extra::net::url` has some 290 | /// stuff which might help, but isn't public. 291 | Authority(String), 292 | } 293 | 294 | impl RequestUri { 295 | /// Interpret a RFC2616 Request-URI 296 | fn from_string(request_uri: String) -> Option { 297 | if request_uri.is_empty() { 298 | None 299 | } else if &request_uri[] == "*" { 300 | Some(Star) 301 | } else if request_uri.as_bytes()[0] as char == '/' { 302 | Some(AbsolutePath(request_uri)) 303 | } else if request_uri.contains("/") { 304 | // An authority can't have a slash in it 305 | match Url::parse(&request_uri[]) { 306 | Ok(url) => Some(AbsoluteUri(url)), 307 | Err(_) => None, 308 | } 309 | } else { 310 | // TODO: parse authority with extra::net::url 311 | Some(Authority(request_uri)) 312 | } 313 | } 314 | } 315 | 316 | impl fmt::Show for RequestUri { 317 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 318 | match *self { 319 | Star => f.write_str("*"), 320 | AbsoluteUri(ref url) => url.fmt(f), 321 | AbsolutePath(ref s) => f.write_str(&s[]), 322 | Authority(ref s) => f.write_str(&s[]), 323 | } 324 | } 325 | } 326 | 327 | impl Request { 328 | 329 | /// Get a response from an open socket. 330 | pub fn load(stream: &mut BufferedStream) -> (Request, Result<(), status::Status>) { 331 | let mut buffer = RequestBuffer::new(stream); 332 | 333 | // Start out with dummy values 334 | let mut request = Request { 335 | remote_addr: buffer.stream.wrapped.peer_name().ok(), 336 | headers: headers::request::HeaderCollection::new(), 337 | body: Vec::new(), 338 | method: Options, 339 | request_uri: Star, 340 | close_connection: true, 341 | version: (0, 0), 342 | }; 343 | 344 | let (method, request_uri, version) = match buffer.read_request_line() { 345 | Ok(vals) => vals, 346 | Err(err) => return (request, Err(err)), 347 | }; 348 | request.method = method; 349 | request.request_uri = request_uri; 350 | request.version = version; 351 | 352 | // request.close_connection is deliberately left set to true so that in case of a bad 353 | // request we can close the connection 354 | let close_connection = match version { 355 | (1, 0) => true, 356 | (1, 1) => false, 357 | _ => return (request, Err(HttpVersionNotSupported)), 358 | }; 359 | 360 | loop { 361 | match buffer.read_header() { 362 | Err(EndOfFile) => panic!("client disconnected, nowhere to send response"), 363 | Err(EndOfHeaders) => break, 364 | Err(MalformedHeaderSyntax) => { 365 | println!("BAD REQUEST: malformed header (TODO: is this right?)"); 366 | return (request, Err(BadRequest)); 367 | }, 368 | Err(MalformedHeaderValue) => { 369 | println!("Bad header encountered. TODO: handle this better."); 370 | // Now just ignore the header 371 | }, 372 | Ok(header) => { 373 | request.headers.insert(header); 374 | }, 375 | } 376 | } 377 | 378 | // HTTP/1.0 doesn't have Host, but HTTP/1.1 requires it 379 | if request.version == (1, 1) && request.headers.host.is_none() { 380 | println!("BAD REQUEST: host is none for HTTP/1.1 request"); 381 | return (request, Err(BadRequest)); 382 | } 383 | 384 | request.close_connection = close_connection; 385 | match request.headers.connection { 386 | Some(ref h) => for v in h.iter() { 387 | match *v { 388 | headers::connection::Connection::Close => { 389 | request.close_connection = true; 390 | break; 391 | }, 392 | headers::connection::Connection::Token(ref s) if &s[] == "keep-alive" => { 393 | request.close_connection = false; 394 | // No break; let it be overridden by close should some weird person do that 395 | }, 396 | headers::connection::Connection::Token(_) => (), 397 | } 398 | }, 399 | None => (), 400 | } 401 | 402 | // Read body if its length is specified 403 | match request.headers.content_length { 404 | Some(length) => { 405 | match buffer.read_exact(length) { 406 | Ok(body) => request.body = body, 407 | Err(_) => return (request, Err(BadRequest)) 408 | } 409 | }, 410 | None => () 411 | } 412 | 413 | (request, Ok(())) 414 | } 415 | } 416 | 417 | 418 | 419 | /* What follows is most of Go's net/http module's definition of Request. 420 | 421 | pub struct Request { 422 | // GET, POST, etc. 423 | method: ~Method, 424 | 425 | // The URL requested, constructed from the request line and (if available) 426 | // the Host header. 427 | url: ~Url, 428 | 429 | // The HTTP protocol version used; typically (1, 1) 430 | protocol: (usize, usize), 431 | 432 | // Request headers, all nicely and correctly parsed. 433 | headers: ~Headers, 434 | 435 | // The message body. 436 | body: Reader, 437 | 438 | // ContentLength records the length of the associated content. 439 | // The value -1 indicates that the length is unknown. 440 | // Values >= 0 indicate that the given number of bytes may 441 | // be read from Body. 442 | // For outgoing requests, a value of 0 means unknown if Body is not nil. 443 | content_length: i64, 444 | 445 | // TransferEncoding lists the transfer encodings from outermost to 446 | // innermost. An empty list denotes the "identity" encoding. 447 | // TransferEncoding can usually be ignored; chunked encoding is 448 | // automatically added and removed as necessary when sending and 449 | // receiving requests. 450 | transfer_encoding: ~[~str], 451 | 452 | // Close indicates whether to close the connection after 453 | // replying to this request. 454 | close: bool, 455 | 456 | // The host on which the URL is sought. 457 | // Per RFC 2616, this is either the value of the Host: header 458 | // or the host name given in the URL itself. 459 | // It may be of the form "host:port". 460 | host: ~str, 461 | 462 | // Form contains the parsed form data, including both the URL 463 | // field's query parameters and the POST or PUT form data. 464 | // This field is only available after ParseForm is called. 465 | // The HTTP client ignores Form and uses Body instead. 466 | form: url.Values, 467 | 468 | // PostForm contains the parsed form data from POST or PUT 469 | // body parameters. 470 | // This field is only available after ParseForm is called. 471 | // The HTTP client ignores PostForm and uses Body instead. 472 | post_form: url.Values, 473 | 474 | // MultipartForm is the parsed multipart form, including file uploads. 475 | // This field is only available after ParseMultipartForm is called. 476 | // The HTTP client ignores MultipartForm and uses Body instead. 477 | multipart_form: *multipart.Form, 478 | 479 | // Trailer maps trailer keys to values. Like for Header, if the 480 | // response has multiple trailer lines with the same key, they will be 481 | // concatenated, delimited by commas. 482 | // For server requests, Trailer is only populated after Body has been 483 | // closed or fully consumed. 484 | // Trailer support is only partially complete. 485 | trailer: ~Headers, 486 | 487 | // RemoteAddr allows HTTP servers and other software to record 488 | // the network address that sent the request, usually for 489 | // logging. This field is not filled in by ReadRequest and 490 | // has no defined format. The HTTP server in this package 491 | // sets RemoteAddr to an "IP:port" address before invoking a 492 | // handler. 493 | // This field is ignored by the HTTP client. 494 | remote_addr: string, 495 | 496 | // RequestURI is the unmodified Request-URI of the 497 | // Request-Line (RFC 2616, Section 5.1) as sent by the client 498 | // to a server. Usually the URL field should be used instead. 499 | // It is an error to set this field in an HTTP client request. 500 | request_uri: string, 501 | 502 | // TLS allows HTTP servers and other software to record 503 | // information about the TLS connection on which the request 504 | // was received. This field is not filled in by ReadRequest. 505 | // The HTTP server in this package sets the field for 506 | // TLS-enabled connections before invoking a handler; 507 | // otherwise it leaves the field nil. 508 | // This field is ignored by the HTTP client. 509 | tls: *tls.ConnectionState, 510 | }*/ 511 | -------------------------------------------------------------------------------- /src/http/server/response.rs: -------------------------------------------------------------------------------- 1 | use std::io::IoResult; 2 | use std::io::net::tcp::TcpStream; 3 | 4 | use buffer::BufferedStream; 5 | use status; 6 | use headers::response::HeaderCollection; 7 | use headers::content_type::MediaType; 8 | use headers::transfer_encoding::TransferCoding::Chunked; 9 | 10 | /* 11 | * The HTTP version tag which will be used for the response. 12 | * 13 | * At present, responses will always respond with `HTTP/1.1`, as there doesn't 14 | * seem much value in responding HTTP/1.0 when we don't really support it. 15 | * Others do this too, so there's my justification. 16 | */ 17 | //const RESPONSE_HTTP_VERSION: &'static str = "HTTP/1.1"; 18 | // Maybe we could provide a response interface 19 | 20 | pub struct ResponseWriter<'a> { 21 | // The place to write to (typically a TCP stream, io::net::tcp::TcpStream) 22 | writer: &'a mut BufferedStream, 23 | headers_written: bool, 24 | pub headers: HeaderCollection, 25 | pub status: status::Status, 26 | } 27 | 28 | impl<'a> ResponseWriter<'a> { 29 | /// Create a `ResponseWriter` writing to the specified location 30 | pub fn new(writer: &'a mut BufferedStream) -> ResponseWriter<'a> { 31 | ResponseWriter { 32 | writer: writer, 33 | headers_written: false, 34 | headers: HeaderCollection::new(), 35 | status: status::Status::Ok, 36 | } 37 | } 38 | 39 | /// Write a response with the specified Content-Type and content; the Content-Length header is 40 | /// set based upon the contents 41 | pub fn write_content_auto(&mut self, content_type: MediaType, content: String) -> IoResult<()> { 42 | self.headers.content_type = Some(content_type); 43 | let cbytes = content.as_bytes(); 44 | self.headers.content_length = Some(cbytes.len()); 45 | try!(self.write_headers()); 46 | self.write(cbytes) 47 | } 48 | 49 | /// Write the Status-Line and headers of the response, if we have not already done so. 50 | pub fn try_write_headers(&mut self) -> IoResult<()> { 51 | if !self.headers_written { 52 | self.write_headers() 53 | } else { 54 | Ok(()) 55 | } 56 | } 57 | 58 | /// Write the Status-Line and headers of the response, in preparation for writing the body. 59 | /// 60 | /// This also overrides the value of the Transfer-Encoding header 61 | /// (``self.headers.transfer_encoding``), ensuring it is ``None`` if the Content-Length header 62 | /// has been specified, or to ``chunked`` if it has not, thus switching to the chunked coding. 63 | /// 64 | /// If the headers have already been written, this will fail. See also `try_write_headers`. 65 | pub fn write_headers(&mut self) -> IoResult<()> { 66 | // This marks the beginning of the response (RFC2616 §6) 67 | if self.headers_written { 68 | panic!("ResponseWriter.write_headers() called, but headers already written"); 69 | } 70 | 71 | // Write the Status-Line (RFC2616 §6.1) 72 | // XXX: might be better not to hardcode HTTP/1.1. 73 | // XXX: Rust's current lack of statement-duration lifetime handling prevents this from being 74 | // one statement ("error: borrowed value does not live long enough") 75 | let s = format!("HTTP/1.1 {:?}\r\n", self.status); 76 | try!(self.writer.write(s.as_bytes())); 77 | 78 | // FIXME: this is not an impressive way of handling it, but so long as chunked is the only 79 | // transfer-coding we want to deal with it's tolerable. However, it is *meant* to be an 80 | // extensible thing, whereby client and server could agree upon extra transformations to 81 | // apply. In such a case, chunked MUST come last. This way prevents it from being extensible 82 | // thus, which is suboptimal. 83 | if self.headers.content_length == None { 84 | self.headers.transfer_encoding = Some(vec!(Chunked)); 85 | } else { 86 | self.headers.transfer_encoding = None; 87 | } 88 | try!(self.headers.write_all(&mut *self.writer)); 89 | self.headers_written = true; 90 | if self.headers.content_length == None { 91 | // Flush so that the chunked body stuff can start working correctly. TODO: don't 92 | // actually flush it entirely, or else it'll send the headers in a separate TCP packet, 93 | // which is bad for performance. 94 | try!(self.writer.flush()); 95 | self.writer.writing_chunked_body = true; 96 | } 97 | Ok(()) 98 | } 99 | 100 | pub fn finish_response(&mut self) -> IoResult<()> { 101 | try!(self.writer.finish_response()); 102 | // Ensure that we switch away from chunked in case another request comes on the same socket 103 | self.writer.writing_chunked_body = false; 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl<'a> Writer for ResponseWriter<'a> { 109 | 110 | fn write(&mut self, buf: &[u8]) -> IoResult<()> { 111 | if !self.headers_written { 112 | try!(self.write_headers()); 113 | } 114 | self.writer.write(buf) 115 | } 116 | 117 | fn flush(&mut self) -> IoResult<()> { 118 | self.writer.flush() 119 | } 120 | 121 | } 122 | --------------------------------------------------------------------------------