feat(lib): switch to non-blocking (asynchronous) IO
BREAKING CHANGE: This breaks a lot of the Client and Server APIs. Check the documentation for how Handlers can be used for asynchronous events.
This commit is contained in:
@@ -3,6 +3,8 @@ matrix:
|
|||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
|
rust: stable
|
||||||
|
env: FEATURES="--no-default-features --features security-framework"
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES="--features nightly"
|
env: FEATURES="--features nightly"
|
||||||
- rust: beta
|
- rust: beta
|
||||||
|
|||||||
@@ -16,13 +16,15 @@ httparse = "1.0"
|
|||||||
language-tags = "0.2"
|
language-tags = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
mime = "0.2"
|
mime = "0.2"
|
||||||
num_cpus = "0.2"
|
rotor = "0.6"
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
|
spmc = "0.2"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
traitobject = "0.0.1"
|
traitobject = "0.0.1"
|
||||||
typeable = "0.1"
|
typeable = "0.1"
|
||||||
unicase = "1.0"
|
unicase = "1.0"
|
||||||
url = "1.0"
|
url = "1.0"
|
||||||
|
vecio = "0.1"
|
||||||
|
|
||||||
[dependencies.cookie]
|
[dependencies.cookie]
|
||||||
version = "0.2"
|
version = "0.2"
|
||||||
@@ -40,16 +42,13 @@ optional = true
|
|||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.solicit]
|
|
||||||
version = "0.4"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "0.7"
|
version = "0.7"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.3"
|
env_logger = "0.3"
|
||||||
|
num_cpus = "0.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ssl"]
|
default = ["ssl"]
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -10,12 +10,12 @@ A Modern HTTP library for Rust.
|
|||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [Stable](http://hyperium.github.io/hyper)
|
- [Released](http://hyperium.github.io/hyper)
|
||||||
- [Master](http://hyperium.github.io/hyper/master)
|
- [Master](http://hyperium.github.io/hyper/master)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Hyper is a fast, modern HTTP implementation written in and for Rust. It
|
hyper is a fast, modern HTTP implementation written in and for Rust. It
|
||||||
is a low-level typesafe abstraction over raw HTTP, providing an elegant
|
is a low-level typesafe abstraction over raw HTTP, providing an elegant
|
||||||
layer over "stringly-typed" HTTP.
|
layer over "stringly-typed" HTTP.
|
||||||
|
|
||||||
@@ -23,53 +23,3 @@ Hyper offers both an HTTP client and server which can be used to drive
|
|||||||
complex web applications written entirely in Rust.
|
complex web applications written entirely in Rust.
|
||||||
|
|
||||||
The documentation is located at [http://hyperium.github.io/hyper](http://hyperium.github.io/hyper).
|
The documentation is located at [http://hyperium.github.io/hyper](http://hyperium.github.io/hyper).
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
### Hello World Server:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
use hyper::Server;
|
|
||||||
use hyper::server::Request;
|
|
||||||
use hyper::server::Response;
|
|
||||||
|
|
||||||
fn hello(_: Request, res: Response) {
|
|
||||||
res.send(b"Hello World!").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Server::http("127.0.0.1:3000").unwrap()
|
|
||||||
.handle(hello).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Client:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use hyper::Client;
|
|
||||||
use hyper::header::Connection;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Create a client.
|
|
||||||
let client = Client::new();
|
|
||||||
|
|
||||||
// Creating an outgoing request.
|
|
||||||
let mut res = client.get("http://rust-lang.org/")
|
|
||||||
// set a header
|
|
||||||
.header(Connection::close())
|
|
||||||
// let 'er go!
|
|
||||||
.send().unwrap();
|
|
||||||
|
|
||||||
// Read the Response.
|
|
||||||
let mut body = String::new();
|
|
||||||
res.read_to_string(&mut body).unwrap();
|
|
||||||
|
|
||||||
println!("Response: {}", body);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
#![deny(warnings)]
|
|
||||||
#![feature(test)]
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::{self, Read, Write, Cursor};
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use hyper::net;
|
|
||||||
|
|
||||||
static README: &'static [u8] = include_bytes!("../README.md");
|
|
||||||
|
|
||||||
struct MockStream {
|
|
||||||
read: Cursor<Vec<u8>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MockStream {
|
|
||||||
fn new() -> MockStream {
|
|
||||||
let head = b"HTTP/1.1 200 OK\r\nServer: Mock\r\n\r\n";
|
|
||||||
let mut res = head.to_vec();
|
|
||||||
res.extend_from_slice(README);
|
|
||||||
MockStream {
|
|
||||||
read: Cursor::new(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for MockStream {
|
|
||||||
fn clone(&self) -> MockStream {
|
|
||||||
MockStream {
|
|
||||||
read: Cursor::new(self.read.get_ref().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for MockStream {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.read.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for MockStream {
|
|
||||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
|
||||||
// we're mocking, what do we care.
|
|
||||||
Ok(msg.len())
|
|
||||||
}
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Foo;
|
|
||||||
|
|
||||||
impl hyper::header::Header for Foo {
|
|
||||||
fn header_name() -> &'static str {
|
|
||||||
"x-foo"
|
|
||||||
}
|
|
||||||
fn parse_header(_: &[Vec<u8>]) -> hyper::Result<Foo> {
|
|
||||||
Err(hyper::Error::Header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl hyper::header::HeaderFormat for Foo {
|
|
||||||
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
fmt.write_str("Bar")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl net::NetworkStream for MockStream {
|
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
|
||||||
Ok("127.0.0.1:1337".parse().unwrap())
|
|
||||||
}
|
|
||||||
fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> {
|
|
||||||
// can't time out
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> {
|
|
||||||
// can't time out
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MockConnector;
|
|
||||||
|
|
||||||
impl net::NetworkConnector for MockConnector {
|
|
||||||
type Stream = MockStream;
|
|
||||||
fn connect(&self, _: &str, _: u16, _: &str) -> hyper::Result<MockStream> {
|
|
||||||
Ok(MockStream::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_mock_hyper(b: &mut test::Bencher) {
|
|
||||||
let url = "http://127.0.0.1:1337/";
|
|
||||||
b.iter(|| {
|
|
||||||
let mut req = hyper::client::Request::with_connector(
|
|
||||||
hyper::Get, hyper::Url::parse(url).unwrap(), &MockConnector
|
|
||||||
).unwrap();
|
|
||||||
req.headers_mut().set(Foo);
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
req
|
|
||||||
.start().unwrap()
|
|
||||||
.send().unwrap()
|
|
||||||
.read_to_string(&mut s).unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,61 @@ extern crate env_logger;
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use hyper::Client;
|
use hyper::client::{Client, Request, Response, DefaultTransport as HttpStream};
|
||||||
use hyper::header::Connection;
|
use hyper::header::Connection;
|
||||||
|
use hyper::{Decoder, Encoder, Next};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Dump(mpsc::Sender<()>);
|
||||||
|
|
||||||
|
impl Drop for Dump {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.0.send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read() -> Next {
|
||||||
|
Next::read().timeout(Duration::from_secs(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hyper::client::Handler<HttpStream> for Dump {
|
||||||
|
fn on_request(&mut self, req: &mut Request) -> Next {
|
||||||
|
req.headers_mut().set(Connection::close());
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response(&mut self, res: Response) -> Next {
|
||||||
|
println!("Response: {}", res.status());
|
||||||
|
println!("Headers:\n{}", res.headers());
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
match io::copy(decoder, &mut io::stdout()) {
|
||||||
|
Ok(0) => Next::end(),
|
||||||
|
Ok(_) => read(),
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => Next::read(),
|
||||||
|
_ => {
|
||||||
|
println!("ERROR: {}", e);
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&mut self, err: hyper::Error) -> Next {
|
||||||
|
println!("ERROR: {}", err);
|
||||||
|
Next::remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init().unwrap();
|
||||||
@@ -20,26 +72,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = match env::var("HTTP_PROXY") {
|
let (tx, rx) = mpsc::channel();
|
||||||
Ok(mut proxy) => {
|
let client = Client::new().expect("Failed to create a Client");
|
||||||
// parse the proxy, message if it doesn't make sense
|
client.request(url.parse().unwrap(), Dump(tx)).unwrap();
|
||||||
let mut port = 80;
|
|
||||||
if let Some(colon) = proxy.rfind(':') {
|
|
||||||
port = proxy[colon + 1..].parse().unwrap_or_else(|e| {
|
|
||||||
panic!("HTTP_PROXY is malformed: {:?}, port parse error: {}", proxy, e);
|
|
||||||
});
|
|
||||||
proxy.truncate(colon);
|
|
||||||
}
|
|
||||||
Client::with_http_proxy(proxy, port)
|
|
||||||
},
|
|
||||||
_ => Client::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut res = client.get(&*url)
|
// wait till done
|
||||||
.header(Connection::close())
|
let _ = rx.recv();
|
||||||
.send().unwrap();
|
client.close();
|
||||||
|
|
||||||
println!("Response: {}", res.status);
|
|
||||||
println!("Headers:\n{}", res.headers);
|
|
||||||
io::copy(&mut res, &mut io::stdout()).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
#![deny(warnings)]
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use hyper::Client;
|
|
||||||
use hyper::header::Connection;
|
|
||||||
use hyper::http::h2;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init().unwrap();
|
|
||||||
|
|
||||||
let url = match env::args().nth(1) {
|
|
||||||
Some(url) => url,
|
|
||||||
None => {
|
|
||||||
println!("Usage: client <url>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = Client::with_protocol(h2::new_protocol());
|
|
||||||
|
|
||||||
// `Connection: Close` is not a valid header for HTTP/2, but the client handles it gracefully.
|
|
||||||
let mut res = client.get(&*url)
|
|
||||||
.header(Connection::close())
|
|
||||||
.send().unwrap();
|
|
||||||
|
|
||||||
println!("Response: {}", res.status);
|
|
||||||
println!("Headers:\n{}", res.headers);
|
|
||||||
io::copy(&mut res, &mut io::stdout()).unwrap();
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#![deny(warnings)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
// TODO: only import header!, blocked by https://github.com/rust-lang/rust/issues/25003
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
extern crate serde;
|
|
||||||
|
|
||||||
// A header in the form of `X-Foo: some random string`
|
|
||||||
header! {
|
|
||||||
(Foo, "X-Foo") => [String]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,53 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate num_cpus;
|
||||||
|
|
||||||
use hyper::server::{Request, Response};
|
use std::io::Write;
|
||||||
|
|
||||||
|
use hyper::{Decoder, Encoder, Next};
|
||||||
|
use hyper::net::{HttpStream, HttpListener};
|
||||||
|
use hyper::server::{Server, Handler, Request, Response};
|
||||||
|
|
||||||
static PHRASE: &'static [u8] = b"Hello World!";
|
static PHRASE: &'static [u8] = b"Hello World!";
|
||||||
|
|
||||||
fn hello(_: Request, res: Response) {
|
struct Hello;
|
||||||
res.send(PHRASE).unwrap();
|
|
||||||
|
impl Handler<HttpStream> for Hello {
|
||||||
|
fn on_request(&mut self, _: Request) -> Next {
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
fn on_request_readable(&mut self, _: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
fn on_response(&mut self, response: &mut Response) -> Next {
|
||||||
|
use hyper::header::ContentLength;
|
||||||
|
response.headers_mut().set(ContentLength(PHRASE.len() as u64));
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
let n = encoder.write(PHRASE).unwrap();
|
||||||
|
debug_assert_eq!(n, PHRASE.len());
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init().unwrap();
|
||||||
let _listening = hyper::Server::http("127.0.0.1:3000").unwrap()
|
|
||||||
.handle(hello);
|
let listener = HttpListener::bind(&"127.0.0.1:3000".parse().unwrap()).unwrap();
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..num_cpus::get() {
|
||||||
|
let listener = listener.try_clone().unwrap();
|
||||||
|
handles.push(::std::thread::spawn(move || {
|
||||||
|
Server::new(listener)
|
||||||
|
.handle(|_| Hello).unwrap()
|
||||||
|
}));
|
||||||
|
}
|
||||||
println!("Listening on http://127.0.0.1:3000");
|
println!("Listening on http://127.0.0.1:3000");
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,171 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
use std::io::copy;
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
use hyper::{Get, Post};
|
use hyper::{Get, Post, StatusCode, RequestUri, Decoder, Encoder, Next};
|
||||||
use hyper::server::{Server, Request, Response};
|
use hyper::header::ContentLength;
|
||||||
use hyper::uri::RequestUri::AbsolutePath;
|
use hyper::net::HttpStream;
|
||||||
|
use hyper::server::{Server, Handler, Request, Response};
|
||||||
|
|
||||||
macro_rules! try_return(
|
struct Echo {
|
||||||
($e:expr) => {{
|
buf: Vec<u8>,
|
||||||
match $e {
|
read_pos: usize,
|
||||||
Ok(v) => v,
|
write_pos: usize,
|
||||||
Err(e) => { println!("Error: {}", e); return; }
|
eof: bool,
|
||||||
|
route: Route,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Route {
|
||||||
|
NotFound,
|
||||||
|
Index,
|
||||||
|
Echo(Body),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Body {
|
||||||
|
Len(u64),
|
||||||
|
Chunked
|
||||||
|
}
|
||||||
|
|
||||||
|
static INDEX: &'static [u8] = b"Try POST /echo";
|
||||||
|
|
||||||
|
impl Echo {
|
||||||
|
fn new() -> Echo {
|
||||||
|
Echo {
|
||||||
|
buf: vec![0; 4096],
|
||||||
|
read_pos: 0,
|
||||||
|
write_pos: 0,
|
||||||
|
eof: false,
|
||||||
|
route: Route::NotFound,
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
fn echo(mut req: Request, mut res: Response) {
|
impl Handler<HttpStream> for Echo {
|
||||||
match req.uri {
|
fn on_request(&mut self, req: Request) -> Next {
|
||||||
AbsolutePath(ref path) => match (&req.method, &path[..]) {
|
match *req.uri() {
|
||||||
(&Get, "/") | (&Get, "/echo") => {
|
RequestUri::AbsolutePath(ref path) => match (req.method(), &path[..]) {
|
||||||
try_return!(res.send(b"Try POST /echo"));
|
(&Get, "/") | (&Get, "/echo") => {
|
||||||
return;
|
info!("GET Index");
|
||||||
|
self.route = Route::Index;
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
(&Post, "/echo") => {
|
||||||
|
info!("POST Echo");
|
||||||
|
let mut is_more = true;
|
||||||
|
self.route = if let Some(len) = req.headers().get::<ContentLength>() {
|
||||||
|
is_more = **len > 0;
|
||||||
|
Route::Echo(Body::Len(**len))
|
||||||
|
} else {
|
||||||
|
Route::Echo(Body::Chunked)
|
||||||
|
};
|
||||||
|
if is_more {
|
||||||
|
Next::read_and_write()
|
||||||
|
} else {
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Next::write(),
|
||||||
},
|
},
|
||||||
(&Post, "/echo") => (), // fall through, fighting mutable borrows
|
_ => Next::write()
|
||||||
_ => {
|
|
||||||
*res.status_mut() = hyper::NotFound;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
fn on_request_readable(&mut self, transport: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
match self.route {
|
||||||
|
Route::Echo(ref body) => {
|
||||||
|
if self.read_pos < self.buf.len() {
|
||||||
|
match transport.read(&mut self.buf[self.read_pos..]) {
|
||||||
|
Ok(0) => {
|
||||||
|
debug!("Read 0, eof");
|
||||||
|
self.eof = true;
|
||||||
|
Next::write()
|
||||||
|
},
|
||||||
|
Ok(n) => {
|
||||||
|
self.read_pos += n;
|
||||||
|
match *body {
|
||||||
|
Body::Len(max) if max <= self.read_pos as u64 => {
|
||||||
|
self.eof = true;
|
||||||
|
Next::write()
|
||||||
|
},
|
||||||
|
_ => Next::read_and_write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => Next::read_and_write(),
|
||||||
|
_ => {
|
||||||
|
println!("read error {:?}", e);
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut res = try_return!(res.start());
|
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||||
try_return!(copy(&mut req, &mut res));
|
match self.route {
|
||||||
|
Route::NotFound => {
|
||||||
|
res.set_status(StatusCode::NotFound);
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
Route::Index => {
|
||||||
|
res.headers_mut().set(ContentLength(INDEX.len() as u64));
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
Route::Echo(body) => {
|
||||||
|
if let Body::Len(len) = body {
|
||||||
|
res.headers_mut().set(ContentLength(len));
|
||||||
|
}
|
||||||
|
Next::read_and_write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_writable(&mut self, transport: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
match self.route {
|
||||||
|
Route::Index => {
|
||||||
|
transport.write(INDEX).unwrap();
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
Route::Echo(..) => {
|
||||||
|
if self.write_pos < self.read_pos {
|
||||||
|
match transport.write(&self.buf[self.write_pos..self.read_pos]) {
|
||||||
|
Ok(0) => panic!("write ZERO"),
|
||||||
|
Ok(n) => {
|
||||||
|
self.write_pos += n;
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => Next::write(),
|
||||||
|
_ => {
|
||||||
|
println!("write error {:?}", e);
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !self.eof {
|
||||||
|
Next::read()
|
||||||
|
} else {
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init().unwrap();
|
||||||
let server = Server::http("127.0.0.1:1337").unwrap();
|
let server = Server::http(&"127.0.0.1:1337".parse().unwrap()).unwrap();
|
||||||
let _guard = server.handle(echo);
|
let (listening, server) = server.handle(|_| Echo::new()).unwrap();
|
||||||
println!("Listening on http://127.0.0.1:1337");
|
println!("Listening on http://{}", listening);
|
||||||
|
server.run();
|
||||||
}
|
}
|
||||||
|
|||||||
278
examples/sync.rs
Normal file
278
examples/sync.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
extern crate hyper;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::{Arc, mpsc};
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
listening: hyper::server::Listening,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request<'a> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
inner: hyper::server::Request,
|
||||||
|
tx: &'a mpsc::Sender<Action>,
|
||||||
|
rx: &'a mpsc::Receiver<io::Result<usize>>,
|
||||||
|
ctrl: &'a hyper::Control,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Request<'a> {
|
||||||
|
fn new(inner: hyper::server::Request, tx: &'a mpsc::Sender<Action>, rx: &'a mpsc::Receiver<io::Result<usize>>, ctrl: &'a hyper::Control) -> Request<'a> {
|
||||||
|
Request {
|
||||||
|
inner: inner,
|
||||||
|
tx: tx,
|
||||||
|
rx: rx,
|
||||||
|
ctrl: ctrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Read for Request<'a> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.tx.send(Action::Read(buf.as_mut_ptr(), buf.len())).unwrap();
|
||||||
|
self.ctrl.ready(hyper::Next::read()).unwrap();
|
||||||
|
self.rx.recv().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Fresh {}
|
||||||
|
pub enum Streaming {}
|
||||||
|
|
||||||
|
pub struct Response<'a, W = Fresh> {
|
||||||
|
status: hyper::StatusCode,
|
||||||
|
headers: hyper::Headers,
|
||||||
|
version: hyper::HttpVersion,
|
||||||
|
tx: &'a mpsc::Sender<Action>,
|
||||||
|
rx: &'a mpsc::Receiver<io::Result<usize>>,
|
||||||
|
ctrl: &'a hyper::Control,
|
||||||
|
_marker: PhantomData<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Response<'a, Fresh> {
|
||||||
|
fn new(tx: &'a mpsc::Sender<Action>, rx: &'a mpsc::Receiver<io::Result<usize>>, ctrl: &'a hyper::Control) -> Response<'a, Fresh> {
|
||||||
|
Response {
|
||||||
|
status: hyper::Ok,
|
||||||
|
headers: hyper::Headers::new(),
|
||||||
|
version: hyper::HttpVersion::Http11,
|
||||||
|
tx: tx,
|
||||||
|
rx: rx,
|
||||||
|
ctrl: ctrl,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(self) -> io::Result<Response<'a, Streaming>> {
|
||||||
|
self.tx.send(Action::Respond(self.version.clone(), self.status.clone(), self.headers.clone())).unwrap();
|
||||||
|
self.ctrl.ready(hyper::Next::write()).unwrap();
|
||||||
|
let res = self.rx.recv().unwrap();
|
||||||
|
res.map(move |_| Response {
|
||||||
|
status: self.status,
|
||||||
|
headers: self.headers,
|
||||||
|
version: self.version,
|
||||||
|
tx: self.tx,
|
||||||
|
rx: self.rx,
|
||||||
|
ctrl: self.ctrl,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(mut self, msg: &[u8]) -> io::Result<()> {
|
||||||
|
self.headers.set(hyper::header::ContentLength(msg.len() as u64));
|
||||||
|
self.start().and_then(|mut res| res.write_all(msg)).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Write for Response<'a, Streaming> {
|
||||||
|
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
||||||
|
self.tx.send(Action::Write(msg.as_ptr(), msg.len())).unwrap();
|
||||||
|
self.ctrl.ready(hyper::Next::write()).unwrap();
|
||||||
|
let res = self.rx.recv().unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
panic!("Response.flush() not impemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SynchronousHandler {
|
||||||
|
req_tx: mpsc::Sender<hyper::server::Request>,
|
||||||
|
tx: mpsc::Sender<io::Result<usize>>,
|
||||||
|
rx: mpsc::Receiver<Action>,
|
||||||
|
reading: Option<(*mut u8, usize)>,
|
||||||
|
writing: Option<(*const u8, usize)>,
|
||||||
|
respond: Option<(hyper::HttpVersion, hyper::StatusCode, hyper::Headers)>
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for SynchronousHandler {}
|
||||||
|
|
||||||
|
impl SynchronousHandler {
|
||||||
|
fn next(&mut self) -> hyper::Next {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Action::Read(ptr, len)) => {
|
||||||
|
self.reading = Some((ptr, len));
|
||||||
|
hyper::Next::read()
|
||||||
|
},
|
||||||
|
Ok(Action::Respond(ver, status, headers)) => {
|
||||||
|
self.respond = Some((ver, status, headers));
|
||||||
|
hyper::Next::write()
|
||||||
|
},
|
||||||
|
Ok(Action::Write(ptr, len)) => {
|
||||||
|
self.writing = Some((ptr, len));
|
||||||
|
hyper::Next::write()
|
||||||
|
}
|
||||||
|
Err(mpsc::TryRecvError::Empty) => {
|
||||||
|
// we're too fast, the other thread hasn't had a chance to respond
|
||||||
|
hyper::Next::wait()
|
||||||
|
}
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
|
// they dropped it
|
||||||
|
// TODO: should finish up sending response, whatever it was
|
||||||
|
hyper::Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reading(&mut self) -> Option<(*mut u8, usize)> {
|
||||||
|
self.reading.take().or_else(|| {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Action::Read(ptr, len)) => {
|
||||||
|
Some((ptr, len))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writing(&mut self) -> Option<(*const u8, usize)> {
|
||||||
|
self.writing.take().or_else(|| {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Action::Write(ptr, len)) => {
|
||||||
|
Some((ptr, len))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn respond(&mut self) -> Option<(hyper::HttpVersion, hyper::StatusCode, hyper::Headers)> {
|
||||||
|
self.respond.take().or_else(|| {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Action::Respond(ver, status, headers)) => {
|
||||||
|
Some((ver, status, headers))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hyper::server::Handler<hyper::net::HttpStream> for SynchronousHandler {
|
||||||
|
fn on_request(&mut self, req: hyper::server::Request) -> hyper::Next {
|
||||||
|
if let Err(_) = self.req_tx.send(req) {
|
||||||
|
return hyper::Next::end();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_readable(&mut self, decoder: &mut hyper::Decoder<hyper::net::HttpStream>) -> hyper::Next {
|
||||||
|
if let Some(raw) = self.reading() {
|
||||||
|
let slice = unsafe { ::std::slice::from_raw_parts_mut(raw.0, raw.1) };
|
||||||
|
if self.tx.send(decoder.read(slice)).is_err() {
|
||||||
|
return hyper::Next::end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response(&mut self, req: &mut hyper::server::Response) -> hyper::Next {
|
||||||
|
use std::iter::Extend;
|
||||||
|
if let Some(head) = self.respond() {
|
||||||
|
req.set_status(head.1);
|
||||||
|
req.headers_mut().extend(head.2.iter());
|
||||||
|
if self.tx.send(Ok(0)).is_err() {
|
||||||
|
return hyper::Next::end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// wtf happened?
|
||||||
|
panic!("no head to respond with");
|
||||||
|
}
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_writable(&mut self, encoder: &mut hyper::Encoder<hyper::net::HttpStream>) -> hyper::Next {
|
||||||
|
if let Some(raw) = self.writing() {
|
||||||
|
let slice = unsafe { ::std::slice::from_raw_parts(raw.0, raw.1) };
|
||||||
|
if self.tx.send(encoder.write(slice)).is_err() {
|
||||||
|
return hyper::Next::end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
Read(*mut u8, usize),
|
||||||
|
Write(*const u8, usize),
|
||||||
|
Respond(hyper::HttpVersion, hyper::StatusCode, hyper::Headers),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Action {}
|
||||||
|
|
||||||
|
trait Handler: Send + Sync + 'static {
|
||||||
|
fn handle(&self, req: Request, res: Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Handler for F where F: Fn(Request, Response) + Send + Sync + 'static {
|
||||||
|
fn handle(&self, req: Request, res: Response) {
|
||||||
|
(self)(req, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
fn handle<H: Handler>(addr: &str, handler: H) -> Server {
|
||||||
|
let handler = Arc::new(handler);
|
||||||
|
let (listening, server) = hyper::Server::http(&addr.parse().unwrap()).unwrap()
|
||||||
|
.handle(move |ctrl| {
|
||||||
|
let (req_tx, req_rx) = mpsc::channel();
|
||||||
|
let (blocking_tx, blocking_rx) = mpsc::channel();
|
||||||
|
let (async_tx, async_rx) = mpsc::channel();
|
||||||
|
let handler = handler.clone();
|
||||||
|
thread::Builder::new().name("handler-thread".into()).spawn(move || {
|
||||||
|
let req = Request::new(req_rx.recv().unwrap(), &blocking_tx, &async_rx, &ctrl);
|
||||||
|
let res = Response::new(&blocking_tx, &async_rx, &ctrl);
|
||||||
|
handler.handle(req, res);
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
SynchronousHandler {
|
||||||
|
req_tx: req_tx,
|
||||||
|
tx: async_tx,
|
||||||
|
rx: blocking_rx,
|
||||||
|
reading: None,
|
||||||
|
writing: None,
|
||||||
|
respond: None,
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
thread::spawn(move || {
|
||||||
|
server.run();
|
||||||
|
});
|
||||||
|
Server {
|
||||||
|
listening: listening
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init().unwrap();
|
||||||
|
let s = Server::handle("127.0.0.1:0", |mut req: Request, res: Response| {
|
||||||
|
let mut body = [0; 256];
|
||||||
|
let n = req.read(&mut body).unwrap();
|
||||||
|
println!("!!!: received: {:?}", ::std::str::from_utf8(&body[..n]).unwrap());
|
||||||
|
|
||||||
|
res.send(b"Hello World!").unwrap();
|
||||||
|
});
|
||||||
|
println!("listening on {}", s.listening.addr());
|
||||||
|
}
|
||||||
164
src/buffer.rs
164
src/buffer.rs
@@ -1,164 +0,0 @@
|
|||||||
use std::cmp;
|
|
||||||
use std::io::{self, Read, BufRead};
|
|
||||||
|
|
||||||
pub struct BufReader<R> {
|
|
||||||
inner: R,
|
|
||||||
buf: Vec<u8>,
|
|
||||||
pos: usize,
|
|
||||||
cap: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
const INIT_BUFFER_SIZE: usize = 4096;
|
|
||||||
const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100;
|
|
||||||
|
|
||||||
impl<R: Read> BufReader<R> {
|
|
||||||
#[inline]
|
|
||||||
pub fn new(rdr: R) -> BufReader<R> {
|
|
||||||
BufReader::with_capacity(rdr, INIT_BUFFER_SIZE)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn with_capacity(rdr: R, cap: usize) -> BufReader<R> {
|
|
||||||
BufReader {
|
|
||||||
inner: rdr,
|
|
||||||
buf: vec![0; cap],
|
|
||||||
pos: 0,
|
|
||||||
cap: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_ref(&self) -> &R { &self.inner }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut R { &mut self.inner }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_buf(&self) -> &[u8] {
|
|
||||||
if self.pos < self.cap {
|
|
||||||
trace!("get_buf [u8; {}][{}..{}]", self.buf.len(), self.pos, self.cap);
|
|
||||||
&self.buf[self.pos..self.cap]
|
|
||||||
} else {
|
|
||||||
trace!("get_buf []");
|
|
||||||
&[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn into_inner(self) -> R { self.inner }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_into_buf(&mut self) -> io::Result<usize> {
|
|
||||||
self.maybe_reserve();
|
|
||||||
let v = &mut self.buf;
|
|
||||||
trace!("read_into_buf buf[{}..{}]", self.cap, v.len());
|
|
||||||
if self.cap < v.capacity() {
|
|
||||||
let nread = try!(self.inner.read(&mut v[self.cap..]));
|
|
||||||
self.cap += nread;
|
|
||||||
Ok(nread)
|
|
||||||
} else {
|
|
||||||
trace!("read_into_buf at full capacity");
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn maybe_reserve(&mut self) {
|
|
||||||
let cap = self.buf.capacity();
|
|
||||||
if self.cap == cap && cap < MAX_BUFFER_SIZE {
|
|
||||||
self.buf.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap);
|
|
||||||
let new = self.buf.capacity() - self.buf.len();
|
|
||||||
trace!("reserved {}", new);
|
|
||||||
unsafe { grow_zerofill(&mut self.buf, new) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
unsafe fn grow_zerofill(buf: &mut Vec<u8>, additional: usize) {
|
|
||||||
use std::ptr;
|
|
||||||
let len = buf.len();
|
|
||||||
buf.set_len(len + additional);
|
|
||||||
ptr::write_bytes(buf.as_mut_ptr().offset(len as isize), 0, additional);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> Read for BufReader<R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
if self.cap == self.pos && buf.len() >= self.buf.len() {
|
|
||||||
return self.inner.read(buf);
|
|
||||||
}
|
|
||||||
let nread = {
|
|
||||||
let mut rem = try!(self.fill_buf());
|
|
||||||
try!(rem.read(buf))
|
|
||||||
};
|
|
||||||
self.consume(nread);
|
|
||||||
Ok(nread)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> BufRead for BufReader<R> {
|
|
||||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
|
||||||
if self.pos == self.cap {
|
|
||||||
self.cap = try!(self.inner.read(&mut self.buf));
|
|
||||||
self.pos = 0;
|
|
||||||
}
|
|
||||||
Ok(&self.buf[self.pos..self.cap])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn consume(&mut self, amt: usize) {
|
|
||||||
self.pos = cmp::min(self.pos + amt, self.cap);
|
|
||||||
if self.pos == self.cap {
|
|
||||||
self.pos = 0;
|
|
||||||
self.cap = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use std::io::{self, Read, BufRead};
|
|
||||||
use super::BufReader;
|
|
||||||
|
|
||||||
struct SlowRead(u8);
|
|
||||||
|
|
||||||
impl Read for SlowRead {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let state = self.0;
|
|
||||||
self.0 += 1;
|
|
||||||
(&match state % 3 {
|
|
||||||
0 => b"foo",
|
|
||||||
1 => b"bar",
|
|
||||||
_ => b"baz",
|
|
||||||
}[..]).read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_consume_and_get_buf() {
|
|
||||||
let mut rdr = BufReader::new(SlowRead(0));
|
|
||||||
rdr.read_into_buf().unwrap();
|
|
||||||
rdr.consume(1);
|
|
||||||
assert_eq!(rdr.get_buf(), b"oo");
|
|
||||||
rdr.read_into_buf().unwrap();
|
|
||||||
rdr.read_into_buf().unwrap();
|
|
||||||
assert_eq!(rdr.get_buf(), b"oobarbaz");
|
|
||||||
rdr.consume(5);
|
|
||||||
assert_eq!(rdr.get_buf(), b"baz");
|
|
||||||
rdr.consume(3);
|
|
||||||
assert_eq!(rdr.get_buf(), b"");
|
|
||||||
assert_eq!(rdr.pos, 0);
|
|
||||||
assert_eq!(rdr.cap, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resize() {
|
|
||||||
let raw = b"hello world";
|
|
||||||
let mut rdr = BufReader::with_capacity(&raw[..], 5);
|
|
||||||
rdr.read_into_buf().unwrap();
|
|
||||||
assert_eq!(rdr.get_buf(), b"hello");
|
|
||||||
rdr.read_into_buf().unwrap();
|
|
||||||
assert_eq!(rdr.get_buf(), b"hello world");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
219
src/client/connect.rs
Normal file
219
src/client/connect.rs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
use std::collections::hash_map::{HashMap, Entry};
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use rotor::mio::tcp::TcpStream;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use net::{HttpStream, HttpsStream, Transport, SslClient};
|
||||||
|
use super::dns::Dns;
|
||||||
|
use super::Registration;
|
||||||
|
|
||||||
|
/// A connector creates a Transport to a remote address..
|
||||||
|
pub trait Connect {
|
||||||
|
/// Type of Transport to create
|
||||||
|
type Output: Transport;
|
||||||
|
/// The key used to determine if an existing socket can be used.
|
||||||
|
type Key: Eq + Hash + Clone;
|
||||||
|
/// Returns the key based off the Url.
|
||||||
|
fn key(&self, &Url) -> Option<Self::Key>;
|
||||||
|
/// Connect to a remote address.
|
||||||
|
fn connect(&mut self, &Url) -> io::Result<Self::Key>;
|
||||||
|
/// Returns a connected socket and associated host.
|
||||||
|
fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>;
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn register(&mut self, Registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scheme = String;
|
||||||
|
type Port = u16;
|
||||||
|
|
||||||
|
/// A connector for the `http` scheme.
|
||||||
|
pub struct HttpConnector {
|
||||||
|
dns: Option<Dns>,
|
||||||
|
threads: usize,
|
||||||
|
resolving: HashMap<String, Vec<(&'static str, String, u16)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpConnector {
|
||||||
|
/// Set the number of resolver threads.
|
||||||
|
///
|
||||||
|
/// Default is 4.
|
||||||
|
pub fn threads(mut self, threads: usize) -> HttpConnector {
|
||||||
|
debug_assert!(self.dns.is_none(), "setting threads after Dns is created does nothing");
|
||||||
|
self.threads = threads;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpConnector {
|
||||||
|
fn default() -> HttpConnector {
|
||||||
|
HttpConnector {
|
||||||
|
dns: None,
|
||||||
|
threads: 4,
|
||||||
|
resolving: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HttpConnector {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("HttpConnector")
|
||||||
|
.field("threads", &self.threads)
|
||||||
|
.field("resolving", &self.resolving)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connect for HttpConnector {
|
||||||
|
type Output = HttpStream;
|
||||||
|
type Key = (&'static str, String, u16);
|
||||||
|
|
||||||
|
fn key(&self, url: &Url) -> Option<Self::Key> {
|
||||||
|
if url.scheme() == "http" {
|
||||||
|
Some((
|
||||||
|
"http",
|
||||||
|
url.host_str().expect("http scheme must have host").to_owned(),
|
||||||
|
url.port().unwrap_or(80),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
|
||||||
|
debug!("Http::connect({:?})", url);
|
||||||
|
if let Some(key) = self.key(url) {
|
||||||
|
let host = url.host_str().expect("http scheme must have a host");
|
||||||
|
self.dns.as_ref().expect("dns workers lost").resolve(host);
|
||||||
|
self.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone());
|
||||||
|
Ok(key)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> {
|
||||||
|
let (host, addr) = match self.dns.as_ref().expect("dns workers lost").resolved() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => return None
|
||||||
|
};
|
||||||
|
debug!("Http::resolved <- ({:?}, {:?})", host, addr);
|
||||||
|
match self.resolving.entry(host) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let resolved = entry.get_mut().remove(0);
|
||||||
|
if entry.get().is_empty() {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
let port = resolved.2;
|
||||||
|
match addr {
|
||||||
|
Ok(addr) => {
|
||||||
|
Some((resolved, TcpStream::connect(&SocketAddr::new(addr, port))
|
||||||
|
.map(HttpStream)))
|
||||||
|
},
|
||||||
|
Err(e) => Some((resolved, Err(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
trace!("^-- resolved but not in hashmap?");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(&mut self, reg: Registration) {
|
||||||
|
self.dns = Some(Dns::new(reg.notify, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A connector that can protect HTTP streams using SSL.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct HttpsConnector<S: SslClient> {
|
||||||
|
http: HttpConnector,
|
||||||
|
ssl: S
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: SslClient> HttpsConnector<S> {
|
||||||
|
/// Create a new connector using the provided SSL implementation.
|
||||||
|
pub fn new(s: S) -> HttpsConnector<S> {
|
||||||
|
HttpsConnector {
|
||||||
|
http: HttpConnector::default(),
|
||||||
|
ssl: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: SslClient> Connect for HttpsConnector<S> {
|
||||||
|
type Output = HttpsStream<S::Stream>;
|
||||||
|
type Key = (&'static str, String, u16);
|
||||||
|
|
||||||
|
fn key(&self, url: &Url) -> Option<Self::Key> {
|
||||||
|
let scheme = match url.scheme() {
|
||||||
|
"http" => "http",
|
||||||
|
"https" => "https",
|
||||||
|
_ => return None
|
||||||
|
};
|
||||||
|
Some((
|
||||||
|
scheme,
|
||||||
|
url.host_str().expect("http scheme must have host").to_owned(),
|
||||||
|
url.port_or_known_default().expect("http scheme must have a port"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
|
||||||
|
debug!("Https::connect({:?})", url);
|
||||||
|
if let Some(key) = self.key(url) {
|
||||||
|
let host = url.host_str().expect("http scheme must have a host");
|
||||||
|
self.http.dns.as_ref().expect("dns workers lost").resolve(host);
|
||||||
|
self.http.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone());
|
||||||
|
Ok(key)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http or https"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)> {
|
||||||
|
self.http.connected().map(|(key, res)| {
|
||||||
|
let res = res.and_then(|http| {
|
||||||
|
if key.0 == "https" {
|
||||||
|
self.ssl.wrap_client(http, &key.1)
|
||||||
|
.map(HttpsStream::Https)
|
||||||
|
.map_err(|e| match e {
|
||||||
|
::Error::Io(e) => e,
|
||||||
|
e => io::Error::new(io::ErrorKind::Other, e)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(HttpsStream::Http(http))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(key, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(&mut self, reg: Registration) {
|
||||||
|
self.http.register(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub type DefaultConnector = HttpConnector;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub type DefaultConnector = HttpsConnector<::net::Openssl>;
|
||||||
|
|
||||||
|
#[cfg(feature = "security-framework")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub type DefaultConnector = HttpsConnector<::net::SecureTransportClient>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub type DefaultTransport = <DefaultConnector as Connect>::Output;
|
||||||
|
|
||||||
|
fn _assert_defaults() {
|
||||||
|
fn _assert<T, U>() where T: Connect<Output=U>, U: Transport {}
|
||||||
|
|
||||||
|
_assert::<DefaultConnector, DefaultTransport>();
|
||||||
|
}
|
||||||
84
src/client/dns.rs
Normal file
84
src/client/dns.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::net::{IpAddr, ToSocketAddrs};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use ::spmc;
|
||||||
|
|
||||||
|
use http::channel;
|
||||||
|
|
||||||
|
pub struct Dns {
|
||||||
|
tx: spmc::Sender<String>,
|
||||||
|
rx: channel::Receiver<Answer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Answer = (String, io::Result<IpAddr>);
|
||||||
|
|
||||||
|
impl Dns {
|
||||||
|
pub fn new(notify: (channel::Sender<Answer>, channel::Receiver<Answer>), threads: usize) -> Dns {
|
||||||
|
let (tx, rx) = spmc::channel();
|
||||||
|
for _ in 0..threads {
|
||||||
|
work(rx.clone(), notify.0.clone());
|
||||||
|
}
|
||||||
|
Dns {
|
||||||
|
tx: tx,
|
||||||
|
rx: notify.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve<T: Into<String>>(&self, hostname: T) {
|
||||||
|
self.tx.send(hostname.into()).expect("Workers all died horribly");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolved(&self) -> Result<Answer, channel::TryRecvError> {
|
||||||
|
self.rx.try_recv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn work(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut worker = Worker::new(rx, notify);
|
||||||
|
let rx = worker.rx.as_ref().expect("Worker lost rx");
|
||||||
|
let notify = worker.notify.as_ref().expect("Worker lost notify");
|
||||||
|
while let Ok(host) = rx.recv() {
|
||||||
|
debug!("resolve {:?}", host);
|
||||||
|
let res = match (&*host, 80).to_socket_addrs().map(|mut i| i.next()) {
|
||||||
|
Ok(Some(addr)) => (host, Ok(addr.ip())),
|
||||||
|
Ok(None) => (host, Err(io::Error::new(io::ErrorKind::Other, "no addresses found"))),
|
||||||
|
Err(e) => (host, Err(e))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(_) = notify.send(res) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
worker.shutdown = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Worker {
|
||||||
|
rx: Option<spmc::Receiver<String>>,
|
||||||
|
notify: Option<channel::Sender<Answer>>,
|
||||||
|
shutdown: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worker {
|
||||||
|
fn new(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) -> Worker {
|
||||||
|
Worker {
|
||||||
|
rx: Some(rx),
|
||||||
|
notify: Some(notify),
|
||||||
|
shutdown: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Worker {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.shutdown {
|
||||||
|
trace!("Worker.drop panicked, restarting");
|
||||||
|
work(self.rx.take().expect("Worker lost rx"),
|
||||||
|
self.notify.take().expect("Worker lost notify"));
|
||||||
|
} else {
|
||||||
|
trace!("Worker.drop shutdown, closing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1048
src/client/mod.rs
1048
src/client/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -1,240 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::io;
|
|
||||||
use std::net::{SocketAddr, Shutdown};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use method::Method;
|
|
||||||
use net::{NetworkConnector, HttpConnector, NetworkStream, SslClient};
|
|
||||||
|
|
||||||
#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
|
|
||||||
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> {
|
|
||||||
Proxy {
|
|
||||||
connector: HttpConnector,
|
|
||||||
proxy: proxy,
|
|
||||||
ssl: Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "security-framework")]
|
|
||||||
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> {
|
|
||||||
Proxy {
|
|
||||||
connector: HttpConnector,
|
|
||||||
proxy: proxy,
|
|
||||||
ssl: Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
|
|
||||||
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, self::no_ssl::Plaintext> {
|
|
||||||
Proxy {
|
|
||||||
connector: HttpConnector,
|
|
||||||
proxy: proxy,
|
|
||||||
ssl: self::no_ssl::Plaintext,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Proxy<C, S>
|
|
||||||
where C: NetworkConnector + Send + Sync + 'static,
|
|
||||||
C::Stream: NetworkStream + Send + Clone,
|
|
||||||
S: SslClient<C::Stream> {
|
|
||||||
pub connector: C,
|
|
||||||
pub proxy: (Cow<'static, str>, u16),
|
|
||||||
pub ssl: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<C, S> NetworkConnector for Proxy<C, S>
|
|
||||||
where C: NetworkConnector + Send + Sync + 'static,
|
|
||||||
C::Stream: NetworkStream + Send + Clone,
|
|
||||||
S: SslClient<C::Stream> {
|
|
||||||
type Stream = Proxied<C::Stream, S::Stream>;
|
|
||||||
|
|
||||||
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
|
|
||||||
use httparse;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use ::version::HttpVersion::Http11;
|
|
||||||
trace!("{:?} proxy for '{}://{}:{}'", self.proxy, scheme, host, port);
|
|
||||||
match scheme {
|
|
||||||
"http" => {
|
|
||||||
self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http")
|
|
||||||
.map(Proxied::Normal)
|
|
||||||
},
|
|
||||||
"https" => {
|
|
||||||
let mut stream = try!(self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http"));
|
|
||||||
trace!("{:?} CONNECT {}:{}", self.proxy, host, port);
|
|
||||||
try!(write!(&mut stream, "{method} {host}:{port} {version}\r\nHost: {host}:{port}\r\n\r\n",
|
|
||||||
method=Method::Connect, host=host, port=port, version=Http11));
|
|
||||||
try!(stream.flush());
|
|
||||||
let mut buf = [0; 1024];
|
|
||||||
let mut n = 0;
|
|
||||||
while n < buf.len() {
|
|
||||||
n += try!(stream.read(&mut buf[n..]));
|
|
||||||
let mut headers = [httparse::EMPTY_HEADER; 10];
|
|
||||||
let mut res = httparse::Response::new(&mut headers);
|
|
||||||
if try!(res.parse(&buf[..n])).is_complete() {
|
|
||||||
let code = res.code.expect("complete parsing lost code");
|
|
||||||
if code >= 200 && code < 300 {
|
|
||||||
trace!("CONNECT success = {:?}", code);
|
|
||||||
return self.ssl.wrap_client(stream, host)
|
|
||||||
.map(Proxied::Tunneled)
|
|
||||||
} else {
|
|
||||||
trace!("CONNECT response = {:?}", code);
|
|
||||||
return Err(::Error::Status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(::Error::TooLarge)
|
|
||||||
},
|
|
||||||
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Proxied<T1, T2> {
|
|
||||||
Normal(T1),
|
|
||||||
Tunneled(T2)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl<T1, T2> Proxied<T1, T2> {
|
|
||||||
pub fn into_normal(self) -> Result<T1, Self> {
|
|
||||||
match self {
|
|
||||||
Proxied::Normal(t1) => Ok(t1),
|
|
||||||
_ => Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_tunneled(self) -> Result<T2, Self> {
|
|
||||||
match self {
|
|
||||||
Proxied::Tunneled(t2) => Ok(t2),
|
|
||||||
_ => Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T1: NetworkStream, T2: NetworkStream> io::Read for Proxied<T1, T2> {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref mut t) => io::Read::read(t, buf),
|
|
||||||
Proxied::Tunneled(ref mut t) => io::Read::read(t, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T1: NetworkStream, T2: NetworkStream> io::Write for Proxied<T1, T2> {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref mut t) => io::Write::write(t, buf),
|
|
||||||
Proxied::Tunneled(ref mut t) => io::Write::write(t, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref mut t) => io::Write::flush(t),
|
|
||||||
Proxied::Tunneled(ref mut t) => io::Write::flush(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T1: NetworkStream, T2: NetworkStream> NetworkStream for Proxied<T1, T2> {
|
|
||||||
#[inline]
|
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref mut s) => s.peer_addr(),
|
|
||||||
Proxied::Tunneled(ref mut s) => s.peer_addr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref inner) => inner.set_read_timeout(dur),
|
|
||||||
Proxied::Tunneled(ref inner) => inner.set_read_timeout(dur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref inner) => inner.set_write_timeout(dur),
|
|
||||||
Proxied::Tunneled(ref inner) => inner.set_write_timeout(dur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
Proxied::Normal(ref mut s) => s.close(how),
|
|
||||||
Proxied::Tunneled(ref mut s) => s.close(how)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
|
|
||||||
mod no_ssl {
|
|
||||||
use std::io;
|
|
||||||
use std::net::{Shutdown, SocketAddr};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use net::{SslClient, NetworkStream};
|
|
||||||
|
|
||||||
pub struct Plaintext;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Void {}
|
|
||||||
|
|
||||||
impl io::Read for Void {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Write for Void {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkStream for Void {
|
|
||||||
#[inline]
|
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_read_timeout(&self, _dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_write_timeout(&self, _dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn close(&mut self, _how: Shutdown) -> io::Result<()> {
|
|
||||||
match *self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: NetworkStream + Send + Clone> SslClient<T> for Plaintext {
|
|
||||||
type Stream = Void;
|
|
||||||
|
|
||||||
fn wrap_client(&self, _stream: T, _host: &str) -> ::Result<Self::Stream> {
|
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +1,52 @@
|
|||||||
//! Client Requests
|
//! Client Requests
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use method::Method;
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use header::Host;
|
use http::RequestHead;
|
||||||
use net::{NetworkStream, NetworkConnector, DefaultConnector, Fresh, Streaming};
|
use method::Method;
|
||||||
use version;
|
use uri::RequestUri;
|
||||||
use client::{Response, get_host_and_port};
|
use version::HttpVersion;
|
||||||
|
|
||||||
use http::{HttpMessage, RequestHead};
|
|
||||||
use http::h1::Http11Message;
|
|
||||||
|
|
||||||
|
|
||||||
/// A client request to a remote server.
|
/// A client request to a remote server.
|
||||||
/// The W type tracks the state of the request, Fresh vs Streaming.
|
#[derive(Debug)]
|
||||||
pub struct Request<W> {
|
pub struct Request<'a> {
|
||||||
/// The target URI for this request.
|
head: &'a mut RequestHead
|
||||||
pub url: Url,
|
|
||||||
|
|
||||||
/// The HTTP version of this request.
|
|
||||||
pub version: version::HttpVersion,
|
|
||||||
|
|
||||||
message: Box<HttpMessage>,
|
|
||||||
headers: Headers,
|
|
||||||
method: Method,
|
|
||||||
|
|
||||||
_marker: PhantomData<W>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W> Request<W> {
|
impl<'a> Request<'a> {
|
||||||
|
/// Read the Request Url.
|
||||||
|
#[inline]
|
||||||
|
pub fn uri(&self) -> &RequestUri { &self.head.subject.1 }
|
||||||
|
|
||||||
|
/// Readthe Request Version.
|
||||||
|
#[inline]
|
||||||
|
pub fn version(&self) -> &HttpVersion { &self.head.version }
|
||||||
|
|
||||||
/// Read the Request headers.
|
/// Read the Request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &Headers { &self.headers }
|
pub fn headers(&self) -> &Headers { &self.head.headers }
|
||||||
|
|
||||||
/// Read the Request method.
|
/// Read the Request method.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&self) -> Method { self.method.clone() }
|
pub fn method(&self) -> &Method { &self.head.subject.0 }
|
||||||
|
|
||||||
/// Set the write timeout.
|
/// Set the Method of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
pub fn set_method(&mut self, method: Method) { self.head.subject.0 = method; }
|
||||||
self.message.set_write_timeout(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the read timeout.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
self.message.set_read_timeout(dur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Request<Fresh> {
|
|
||||||
/// Create a new `Request<Fresh>` that will use the given `HttpMessage` for its communication
|
|
||||||
/// with the server. This implies that the given `HttpMessage` instance has already been
|
|
||||||
/// properly initialized by the caller (e.g. a TCP connection's already established).
|
|
||||||
pub fn with_message(method: Method, url: Url, message: Box<HttpMessage>)
|
|
||||||
-> ::Result<Request<Fresh>> {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
{
|
|
||||||
let (host, port) = try!(get_host_and_port(&url));
|
|
||||||
headers.set(Host {
|
|
||||||
hostname: host.to_owned(),
|
|
||||||
port: Some(port),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Request::with_headers_and_message(method, url, headers, message))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn with_headers_and_message(method: Method, url: Url, headers: Headers, message: Box<HttpMessage>)
|
|
||||||
-> Request<Fresh> {
|
|
||||||
Request {
|
|
||||||
method: method,
|
|
||||||
headers: headers,
|
|
||||||
url: url,
|
|
||||||
version: version::HttpVersion::Http11,
|
|
||||||
message: message,
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new client request.
|
|
||||||
pub fn new(method: Method, url: Url) -> ::Result<Request<Fresh>> {
|
|
||||||
let conn = DefaultConnector::default();
|
|
||||||
Request::with_connector(method, url, &conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new client request with a specific underlying NetworkStream.
|
|
||||||
pub fn with_connector<C, S>(method: Method, url: Url, connector: &C)
|
|
||||||
-> ::Result<Request<Fresh>> where
|
|
||||||
C: NetworkConnector<Stream=S>,
|
|
||||||
S: Into<Box<NetworkStream + Send>> {
|
|
||||||
let stream = {
|
|
||||||
let (host, port) = try!(get_host_and_port(&url));
|
|
||||||
try!(connector.connect(host, port, url.scheme())).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
Request::with_message(method, url, Box::new(Http11Message::with_stream(stream)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume a Fresh Request, writing the headers and method,
|
|
||||||
/// returning a Streaming Request.
|
|
||||||
pub fn start(mut self) -> ::Result<Request<Streaming>> {
|
|
||||||
let head = match self.message.set_outgoing(RequestHead {
|
|
||||||
headers: self.headers,
|
|
||||||
method: self.method,
|
|
||||||
url: self.url,
|
|
||||||
}) {
|
|
||||||
Ok(head) => head,
|
|
||||||
Err(e) => {
|
|
||||||
let _ = self.message.close_connection();
|
|
||||||
return Err(From::from(e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Request {
|
|
||||||
method: head.method,
|
|
||||||
headers: head.headers,
|
|
||||||
url: head.url,
|
|
||||||
version: self.version,
|
|
||||||
message: self.message,
|
|
||||||
_marker: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a mutable reference to the Request headers.
|
/// Get a mutable reference to the Request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
|
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.head.headers }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(head: &mut RequestHead) -> Request {
|
||||||
|
Request { head: head }
|
||||||
impl Request<Streaming> {
|
|
||||||
/// Completes writing the request, and returns a response to read from.
|
|
||||||
///
|
|
||||||
/// Consumes the Request.
|
|
||||||
pub fn send(self) -> ::Result<Response> {
|
|
||||||
Response::with_message(self.url, self.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for Request<Streaming> {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
|
||||||
match self.message.write(msg) {
|
|
||||||
Ok(n) => Ok(n),
|
|
||||||
Err(e) => {
|
|
||||||
let _ = self.message.close_connection();
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
match self.message.flush() {
|
|
||||||
Ok(r) => Ok(r),
|
|
||||||
Err(e) => {
|
|
||||||
let _ = self.message.close_connection();
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
/*
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -311,4 +186,5 @@ mod tests {
|
|||||||
.get_ref().downcast_ref::<MockStream>().unwrap()
|
.get_ref().downcast_ref::<MockStream>().unwrap()
|
||||||
.is_closed);
|
.is_closed);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +1,57 @@
|
|||||||
//! Client Responses
|
//! Client Responses
|
||||||
use std::io::{self, Read};
|
|
||||||
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
use net::NetworkStream;
|
//use net::NetworkStream;
|
||||||
use http::{self, RawStatus, ResponseHead, HttpMessage};
|
use http::{self, RawStatus};
|
||||||
use http::h1::Http11Message;
|
|
||||||
use status;
|
use status;
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
|
pub fn new(incoming: http::ResponseHead) -> Response {
|
||||||
|
trace!("Response::new");
|
||||||
|
let status = status::StatusCode::from_u16(incoming.subject.0);
|
||||||
|
debug!("version={:?}, status={:?}", incoming.version, status);
|
||||||
|
debug!("headers={:?}", incoming.headers);
|
||||||
|
|
||||||
|
Response {
|
||||||
|
status: status,
|
||||||
|
version: incoming.version,
|
||||||
|
headers: incoming.headers,
|
||||||
|
status_raw: incoming.subject,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// A response for a client request to a remote server.
|
/// A response for a client request to a remote server.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
/// The status from the server.
|
status: status::StatusCode,
|
||||||
pub status: status::StatusCode,
|
headers: header::Headers,
|
||||||
/// The headers from the server.
|
version: version::HttpVersion,
|
||||||
pub headers: header::Headers,
|
|
||||||
/// The HTTP version of this response from the server.
|
|
||||||
pub version: version::HttpVersion,
|
|
||||||
/// The final URL of this response.
|
|
||||||
pub url: Url,
|
|
||||||
status_raw: RawStatus,
|
status_raw: RawStatus,
|
||||||
message: Box<HttpMessage>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
/// Get the headers from the server.
|
||||||
|
#[inline]
|
||||||
|
pub fn headers(&self) -> &header::Headers { &self.headers }
|
||||||
|
|
||||||
/// Creates a new response from a server.
|
/// Get the status from the server.
|
||||||
pub fn new(url: Url, stream: Box<NetworkStream + Send>) -> ::Result<Response> {
|
#[inline]
|
||||||
trace!("Response::new");
|
pub fn status(&self) -> &status::StatusCode { &self.status }
|
||||||
Response::with_message(url, Box::new(Http11Message::with_stream(stream)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new response received from the server on the given `HttpMessage`.
|
|
||||||
pub fn with_message(url: Url, mut message: Box<HttpMessage>) -> ::Result<Response> {
|
|
||||||
trace!("Response::with_message");
|
|
||||||
let ResponseHead { headers, raw_status, version } = match message.get_incoming() {
|
|
||||||
Ok(head) => head,
|
|
||||||
Err(e) => {
|
|
||||||
let _ = message.close_connection();
|
|
||||||
return Err(From::from(e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let status = status::StatusCode::from_u16(raw_status.0);
|
|
||||||
debug!("version={:?}, status={:?}", version, status);
|
|
||||||
debug!("headers={:?}", headers);
|
|
||||||
|
|
||||||
Ok(Response {
|
|
||||||
status: status,
|
|
||||||
version: version,
|
|
||||||
headers: headers,
|
|
||||||
url: url,
|
|
||||||
status_raw: raw_status,
|
|
||||||
message: message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the raw status code and reason.
|
/// Get the raw status code and reason.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn status_raw(&self) -> &RawStatus {
|
pub fn status_raw(&self) -> &RawStatus { &self.status_raw }
|
||||||
&self.status_raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for Response {
|
/// Get the final URL of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
//pub fn url(&self) -> &Url { &self.url }
|
||||||
match self.message.read(buf) {
|
|
||||||
Err(e) => {
|
/// Get the HTTP version of this response from the server.
|
||||||
let _ = self.message.close_connection();
|
#[inline]
|
||||||
Err(e)
|
pub fn version(&self) -> &version::HttpVersion { &self.version }
|
||||||
}
|
|
||||||
r => r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
impl Drop for Response {
|
impl Drop for Response {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// if not drained, theres old bits in the Reader. we can't reuse this,
|
// if not drained, theres old bits in the Reader. we can't reuse this,
|
||||||
@@ -94,9 +69,11 @@ impl Drop for Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
/*
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -230,4 +207,5 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Response::new(url, Box::new(stream)).is_err());
|
assert!(Response::new(url, Box::new(stream)).is_err());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/error.rs
22
src/error.rs
@@ -7,7 +7,6 @@ use std::string::FromUtf8Error;
|
|||||||
|
|
||||||
use httparse;
|
use httparse;
|
||||||
use url;
|
use url;
|
||||||
use solicit::http::HttpError as Http2Error;
|
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use openssl::ssl::error::SslError;
|
use openssl::ssl::error::SslError;
|
||||||
@@ -18,10 +17,11 @@ use self::Error::{
|
|||||||
Version,
|
Version,
|
||||||
Header,
|
Header,
|
||||||
Status,
|
Status,
|
||||||
|
Timeout,
|
||||||
Io,
|
Io,
|
||||||
Ssl,
|
Ssl,
|
||||||
TooLarge,
|
TooLarge,
|
||||||
Http2,
|
Incomplete,
|
||||||
Utf8
|
Utf8
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,14 +42,16 @@ pub enum Error {
|
|||||||
Header,
|
Header,
|
||||||
/// A message head is too large to be reasonable.
|
/// A message head is too large to be reasonable.
|
||||||
TooLarge,
|
TooLarge,
|
||||||
|
/// A message reached EOF before being a complete message.
|
||||||
|
Incomplete,
|
||||||
/// An invalid `Status`, such as `1337 ELITE`.
|
/// An invalid `Status`, such as `1337 ELITE`.
|
||||||
Status,
|
Status,
|
||||||
|
/// A timeout occurred waiting for an IO event.
|
||||||
|
Timeout,
|
||||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
/// An error from a SSL library.
|
/// An error from a SSL library.
|
||||||
Ssl(Box<StdError + Send + Sync>),
|
Ssl(Box<StdError + Send + Sync>),
|
||||||
/// An HTTP/2-specific error, coming from the `solicit` library.
|
|
||||||
Http2(Http2Error),
|
|
||||||
/// Parsing a field as string failed
|
/// Parsing a field as string failed
|
||||||
Utf8(Utf8Error),
|
Utf8(Utf8Error),
|
||||||
|
|
||||||
@@ -80,10 +82,11 @@ impl StdError for Error {
|
|||||||
Header => "Invalid Header provided",
|
Header => "Invalid Header provided",
|
||||||
TooLarge => "Message head is too large",
|
TooLarge => "Message head is too large",
|
||||||
Status => "Invalid Status provided",
|
Status => "Invalid Status provided",
|
||||||
|
Incomplete => "Message is incomplete",
|
||||||
|
Timeout => "Timeout",
|
||||||
Uri(ref e) => e.description(),
|
Uri(ref e) => e.description(),
|
||||||
Io(ref e) => e.description(),
|
Io(ref e) => e.description(),
|
||||||
Ssl(ref e) => e.description(),
|
Ssl(ref e) => e.description(),
|
||||||
Http2(ref e) => e.description(),
|
|
||||||
Utf8(ref e) => e.description(),
|
Utf8(ref e) => e.description(),
|
||||||
Error::__Nonexhaustive(ref void) => match *void {}
|
Error::__Nonexhaustive(ref void) => match *void {}
|
||||||
}
|
}
|
||||||
@@ -94,7 +97,6 @@ impl StdError for Error {
|
|||||||
Io(ref error) => Some(error),
|
Io(ref error) => Some(error),
|
||||||
Ssl(ref error) => Some(&**error),
|
Ssl(ref error) => Some(&**error),
|
||||||
Uri(ref error) => Some(error),
|
Uri(ref error) => Some(error),
|
||||||
Http2(ref error) => Some(error),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,18 +150,11 @@ impl From<httparse::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Http2Error> for Error {
|
|
||||||
fn from(err: Http2Error) -> Error {
|
|
||||||
Error::Http2(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::io;
|
use std::io;
|
||||||
use httparse;
|
use httparse;
|
||||||
use solicit::http::HttpError as Http2Error;
|
|
||||||
use url;
|
use url;
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use super::Error::*;
|
use super::Error::*;
|
||||||
@@ -201,7 +196,6 @@ mod tests {
|
|||||||
|
|
||||||
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..));
|
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..));
|
||||||
from_and_cause!(url::ParseError::EmptyHost => Uri(..));
|
from_and_cause!(url::ParseError::EmptyHost => Uri(..));
|
||||||
from_and_cause!(Http2Error::UnknownStreamId => Http2(..));
|
|
||||||
|
|
||||||
from!(httparse::Error::HeaderName => Header);
|
from!(httparse::Error::HeaderName => Header);
|
||||||
from!(httparse::Error::HeaderName => Header);
|
from!(httparse::Error::HeaderName => Header);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::str;
|
use std::str;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
|
|
||||||
/// `Access-Control-Allow-Credentials` header, part of
|
/// `Access-Control-Allow-Credentials` header, part of
|
||||||
/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header)
|
/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header)
|
||||||
@@ -62,9 +62,7 @@ impl Header for AccessControlAllowCredentials {
|
|||||||
}
|
}
|
||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for AccessControlAllowCredentials {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str("true")
|
f.write_str("true")
|
||||||
}
|
}
|
||||||
@@ -86,4 +84,4 @@ mod test_access_control_allow_credentials {
|
|||||||
test_header!(not_bool, vec![b"false"], None);
|
test_header!(not_bool, vec![b"false"], None);
|
||||||
test_header!(only_single, vec![b"true", b"true"], None);
|
test_header!(only_single, vec![b"true", b"true"], None);
|
||||||
test_header!(no_gibberish, vec!["\u{645}\u{631}\u{62d}\u{628}\u{627}".as_bytes()], None);
|
test_header!(no_gibberish, vec!["\u{645}\u{631}\u{62d}\u{628}\u{627}".as_bytes()], None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
|
|
||||||
/// The `Access-Control-Allow-Origin` response header,
|
/// The `Access-Control-Allow-Origin` response header,
|
||||||
/// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
|
/// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
|
||||||
@@ -70,9 +70,7 @@ impl Header for AccessControlAllowOrigin {
|
|||||||
_ => AccessControlAllowOrigin::Value(try!(String::from_utf8(value.clone())))
|
_ => AccessControlAllowOrigin::Value(try!(String::from_utf8(value.clone())))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for AccessControlAllowOrigin {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
AccessControlAllowOrigin::Any => f.write_str("*"),
|
AccessControlAllowOrigin::Any => f.write_str("*"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::{self, Display};
|
|||||||
use std::str::{FromStr, from_utf8};
|
use std::str::{FromStr, from_utf8};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use serialize::base64::{ToBase64, FromBase64, Standard, Config, Newline};
|
use serialize::base64::{ToBase64, FromBase64, Standard, Config, Newline};
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
|
|
||||||
/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2)
|
/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2)
|
||||||
///
|
///
|
||||||
@@ -97,9 +97,7 @@ impl<S: Scheme + Any> Header for Authorization<S> where <S as FromStr>::Err: 'st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Scheme + Any> HeaderFormat for Authorization<S> where <S as FromStr>::Err: 'static {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if let Some(scheme) = <S as Scheme>::scheme() {
|
if let Some(scheme) = <S as Scheme>::scheme() {
|
||||||
try!(write!(f, "{} ", scheme))
|
try!(write!(f, "{} ", scheme))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use header::{Header, HeaderFormat};
|
use header::Header;
|
||||||
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||||
|
|
||||||
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
|
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
|
||||||
@@ -62,9 +62,7 @@ impl Header for CacheControl {
|
|||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for CacheControl {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt_comma_delimited(f, &self[..])
|
fmt_comma_delimited(f, &self[..])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::fmt;
|
|||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use url::percent_encoding;
|
use url::percent_encoding;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat, parsing};
|
use header::{Header, parsing};
|
||||||
use header::parsing::{parse_extended_value, HTTP_VALUE};
|
use header::parsing::{parse_extended_value, HTTP_VALUE};
|
||||||
use header::shared::Charset;
|
use header::shared::Charset;
|
||||||
|
|
||||||
@@ -144,9 +144,7 @@ impl Header for ContentDisposition {
|
|||||||
Ok(cd)
|
Ok(cd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for ContentDisposition {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self, f)
|
fmt::Display::fmt(&self, f)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use header::{HeaderFormat, Header, parsing};
|
use header::{Header, parsing};
|
||||||
|
|
||||||
/// `Content-Length` header, defined in
|
/// `Content-Length` header, defined in
|
||||||
/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)
|
/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)
|
||||||
@@ -55,9 +55,7 @@ impl Header for ContentLength {
|
|||||||
.unwrap_or(Err(::Error::Header))
|
.unwrap_or(Err(::Error::Header))
|
||||||
.map(ContentLength)
|
.map(ContentLength)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for ContentLength {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self.0, f)
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use header::{Header, HeaderFormat, CookiePair, CookieJar};
|
use header::{Header, CookiePair, CookieJar};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
||||||
@@ -61,9 +61,7 @@ impl Header for Cookie {
|
|||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Cookie {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let cookies = &self.0;
|
let cookies = &self.0;
|
||||||
for (i, cookie) in cookies.iter().enumerate() {
|
for (i, cookie) in cookies.iter().enumerate() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::str;
|
|||||||
|
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
|
|
||||||
/// The `Expect` header.
|
/// The `Expect` header.
|
||||||
///
|
///
|
||||||
@@ -53,9 +53,7 @@ impl Header for Expect {
|
|||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Expect {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str("100-continue")
|
f.write_str("100-continue")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use header::parsing::from_one_raw_str;
|
use header::parsing::from_one_raw_str;
|
||||||
|
|
||||||
@@ -52,9 +52,7 @@ impl Header for Host {
|
|||||||
// https://github.com/servo/rust-url/issues/42
|
// https://github.com/servo/rust-url/issues/42
|
||||||
let idx = {
|
let idx = {
|
||||||
let slice = &s[..];
|
let slice = &s[..];
|
||||||
let mut chars = slice.chars();
|
if slice.starts_with('[') {
|
||||||
chars.next();
|
|
||||||
if chars.next().unwrap() == '[' {
|
|
||||||
match slice.rfind(']') {
|
match slice.rfind(']') {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
if slice.len() > idx + 2 {
|
if slice.len() > idx + 2 {
|
||||||
@@ -86,9 +84,7 @@ impl Header for Host {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Host {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.port {
|
match self.port {
|
||||||
None | Some(80) | Some(443) => f.write_str(&self.hostname[..]),
|
None | Some(80) | Some(443) => f.write_str(&self.hostname[..]),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use header::{self, Header, HeaderFormat, EntityTag, HttpDate};
|
use header::{self, Header, EntityTag, HttpDate};
|
||||||
|
|
||||||
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
|
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
|
||||||
///
|
///
|
||||||
@@ -59,18 +59,16 @@ impl Header for IfRange {
|
|||||||
}
|
}
|
||||||
fn parse_header(raw: &[Vec<u8>]) -> ::Result<IfRange> {
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<IfRange> {
|
||||||
let etag: ::Result<EntityTag> = header::parsing::from_one_raw_str(raw);
|
let etag: ::Result<EntityTag> = header::parsing::from_one_raw_str(raw);
|
||||||
if etag.is_ok() {
|
if let Ok(etag) = etag {
|
||||||
return Ok(IfRange::EntityTag(etag.unwrap()));
|
return Ok(IfRange::EntityTag(etag));
|
||||||
}
|
}
|
||||||
let date: ::Result<HttpDate> = header::parsing::from_one_raw_str(raw);
|
let date: ::Result<HttpDate> = header::parsing::from_one_raw_str(raw);
|
||||||
if date.is_ok() {
|
if let Ok(date) = date {
|
||||||
return Ok(IfRange::Date(date.unwrap()));
|
return Ok(IfRange::Date(date));
|
||||||
}
|
}
|
||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for IfRange {
|
|
||||||
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
IfRange::EntityTag(ref x) => Display::fmt(x, f),
|
IfRange::EntityTag(ref x) => Display::fmt(x, f),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ macro_rules! bench_header(
|
|||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use header::{Header, HeaderFormatter};
|
use header::{Header};
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_parse(b: &mut Bencher) {
|
fn bench_parse(b: &mut Bencher) {
|
||||||
@@ -79,7 +79,7 @@ macro_rules! bench_header(
|
|||||||
#[bench]
|
#[bench]
|
||||||
fn bench_format(b: &mut Bencher) {
|
fn bench_format(b: &mut Bencher) {
|
||||||
let val: $ty = Header::parse_header(&$value[..]).unwrap();
|
let val: $ty = Header::parse_header(&$value[..]).unwrap();
|
||||||
let fmt = HeaderFormatter(&val);
|
let fmt = ::header::HeaderFormatter(&val);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
format!("{}", fmt);
|
format!("{}", fmt);
|
||||||
});
|
});
|
||||||
@@ -222,15 +222,13 @@ macro_rules! header {
|
|||||||
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
||||||
$crate::header::parsing::from_comma_delimited(raw).map($id)
|
$crate::header::parsing::from_comma_delimited(raw).map($id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl $crate::header::HeaderFormat for $id {
|
|
||||||
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
|
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ::std::fmt::Display for $id {
|
impl ::std::fmt::Display for $id {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
use $crate::header::HeaderFormat;
|
use $crate::header::Header;
|
||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,15 +248,13 @@ macro_rules! header {
|
|||||||
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
||||||
$crate::header::parsing::from_comma_delimited(raw).map($id)
|
$crate::header::parsing::from_comma_delimited(raw).map($id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl $crate::header::HeaderFormat for $id {
|
|
||||||
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
|
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ::std::fmt::Display for $id {
|
impl ::std::fmt::Display for $id {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
use $crate::header::HeaderFormat;
|
use $crate::header::Header;
|
||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,8 +273,6 @@ macro_rules! header {
|
|||||||
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
fn parse_header(raw: &[Vec<u8>]) -> $crate::Result<Self> {
|
||||||
$crate::header::parsing::from_one_raw_str(raw).map($id)
|
$crate::header::parsing::from_one_raw_str(raw).map($id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl $crate::header::HeaderFormat for $id {
|
|
||||||
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
::std::fmt::Display::fmt(&**self, f)
|
::std::fmt::Display::fmt(&**self, f)
|
||||||
}
|
}
|
||||||
@@ -313,8 +307,6 @@ macro_rules! header {
|
|||||||
}
|
}
|
||||||
$crate::header::parsing::from_comma_delimited(raw).map($id::Items)
|
$crate::header::parsing::from_comma_delimited(raw).map($id::Items)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl $crate::header::HeaderFormat for $id {
|
|
||||||
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
$id::Any => f.write_str("*"),
|
$id::Any => f.write_str("*"),
|
||||||
@@ -325,7 +317,7 @@ macro_rules! header {
|
|||||||
}
|
}
|
||||||
impl ::std::fmt::Display for $id {
|
impl ::std::fmt::Display for $id {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
use $crate::header::HeaderFormat;
|
use $crate::header::Header;
|
||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat, parsing};
|
use header::{Header, parsing};
|
||||||
|
|
||||||
/// The `Pragma` header defined by HTTP/1.0.
|
/// The `Pragma` header defined by HTTP/1.0.
|
||||||
///
|
///
|
||||||
@@ -52,9 +52,7 @@ impl Header for Pragma {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Pragma {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str(match *self {
|
f.write_str(match *self {
|
||||||
Pragma::NoCache => "no-cache",
|
Pragma::NoCache => "no-cache",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||||
|
|
||||||
/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
||||||
@@ -64,9 +64,7 @@ impl Header for Prefer {
|
|||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Prefer {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt_comma_delimited(f, &self[..])
|
fmt_comma_delimited(f, &self[..])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use header::{Header, HeaderFormat, Preference};
|
use header::{Header, Preference};
|
||||||
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||||
|
|
||||||
/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
||||||
@@ -61,9 +61,7 @@ impl Header for PreferenceApplied {
|
|||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for PreferenceApplied {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let preferences: Vec<_> = self.0.iter().map(|pref| match pref {
|
let preferences: Vec<_> = self.0.iter().map(|pref| match pref {
|
||||||
// The spec ignores parameters in `Preferences-Applied`
|
// The spec ignores parameters in `Preferences-Applied`
|
||||||
@@ -80,7 +78,7 @@ impl HeaderFormat for PreferenceApplied {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use header::{HeaderFormat, Preference};
|
use header::{Header, Preference};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -90,7 +88,7 @@ mod tests {
|
|||||||
"foo".to_owned(),
|
"foo".to_owned(),
|
||||||
"bar".to_owned(),
|
"bar".to_owned(),
|
||||||
vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())]
|
vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())]
|
||||||
)]) as &(HeaderFormat + Send + Sync)),
|
)]) as &(Header + Send + Sync)),
|
||||||
"foo=bar".to_owned()
|
"foo=bar".to_owned()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat};
|
use header::Header;
|
||||||
use header::parsing::{from_one_raw_str, from_comma_delimited};
|
use header::parsing::{from_one_raw_str, from_comma_delimited};
|
||||||
|
|
||||||
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
|
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
|
||||||
@@ -182,9 +182,6 @@ impl Header for Range {
|
|||||||
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Range> {
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Range> {
|
||||||
from_one_raw_str(raw)
|
from_one_raw_str(raw)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for Range {
|
|
||||||
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
Display::fmt(self, f)
|
Display::fmt(self, f)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use header::{Header, HeaderFormat, CookiePair, CookieJar};
|
use header::{Header, CookiePair, CookieJar};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
||||||
@@ -104,10 +104,6 @@ impl Header for SetCookie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for SetCookie {
|
|
||||||
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
for (i, cookie) in self.0.iter().enumerate() {
|
for (i, cookie) in self.0.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::str::{self, FromStr};
|
|||||||
|
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat, parsing};
|
use header::{Header, parsing};
|
||||||
|
|
||||||
/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797)
|
/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797)
|
||||||
///
|
///
|
||||||
@@ -127,9 +127,7 @@ impl Header for StrictTransportSecurity {
|
|||||||
fn parse_header(raw: &[Vec<u8>]) -> ::Result<StrictTransportSecurity> {
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<StrictTransportSecurity> {
|
||||||
parsing::from_one_raw_str(raw)
|
parsing::from_one_raw_str(raw)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for StrictTransportSecurity {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if self.include_subdomains {
|
if self.include_subdomains {
|
||||||
write!(f, "max-age={}; includeSubdomains", self.max_age)
|
write!(f, "max-age={}; includeSubdomains", self.max_age)
|
||||||
|
|||||||
@@ -49,5 +49,12 @@ header! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TransferEncoding {
|
||||||
|
/// Constructor for the most common Transfer-Encoding, `chunked`.
|
||||||
|
pub fn chunked() -> TransferEncoding {
|
||||||
|
TransferEncoding(vec![Encoding::Chunked])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bench_header!(normal, TransferEncoding, { vec![b"chunked, gzip".to_vec()] });
|
bench_header!(normal, TransferEncoding, { vec![b"chunked, gzip".to_vec()] });
|
||||||
bench_header!(ext, TransferEncoding, { vec![b"ext".to_vec()] });
|
bench_header!(ext, TransferEncoding, { vec![b"ext".to_vec()] });
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ enum PtrMap<T> {
|
|||||||
Many(HashMap<TypeId, T>)
|
Many(HashMap<TypeId, T>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: ?Sized + fmt::Debug + Any + 'static> PtrMapCell<V> {
|
impl<V: ?Sized + Any + 'static> PtrMapCell<V> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> PtrMapCell<V> {
|
pub fn new() -> PtrMapCell<V> {
|
||||||
PtrMapCell(UnsafeCell::new(PtrMap::Empty))
|
PtrMapCell(UnsafeCell::new(PtrMap::Empty))
|
||||||
@@ -114,12 +113,12 @@ impl<V: ?Sized + fmt::Debug + Any + 'static> PtrMapCell<V> {
|
|||||||
let map = &*self.0.get();
|
let map = &*self.0.get();
|
||||||
match *map {
|
match *map {
|
||||||
PtrMap::One(_, ref one) => one,
|
PtrMap::One(_, ref one) => one,
|
||||||
_ => panic!("not PtrMap::One value, {:?}", *map)
|
_ => panic!("not PtrMap::One value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: ?Sized + fmt::Debug + Any + 'static> Clone for PtrMapCell<V> where Box<V>: Clone {
|
impl<V: ?Sized + Any + 'static> Clone for PtrMapCell<V> where Box<V>: Clone {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn clone(&self) -> PtrMapCell<V> {
|
fn clone(&self) -> PtrMapCell<V> {
|
||||||
let cell = PtrMapCell::new();
|
let cell = PtrMapCell::new();
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use std::fmt;
|
|||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
||||||
use super::cell::{OptCell, PtrMapCell};
|
use super::cell::{OptCell, PtrMapCell};
|
||||||
use header::{Header, HeaderFormat};
|
use header::{Header};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
raw: OptCell<Vec<Vec<u8>>>,
|
raw: OptCell<Vec<Vec<u8>>>,
|
||||||
typed: PtrMapCell<HeaderFormat + Send + Sync>
|
typed: PtrMapCell<Header + Send + Sync>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
@@ -23,7 +23,7 @@ impl Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_typed(ty: Box<HeaderFormat + Send + Sync>) -> Item {
|
pub fn new_typed(ty: Box<Header + Send + Sync>) -> Item {
|
||||||
let map = PtrMapCell::new();
|
let map = PtrMapCell::new();
|
||||||
unsafe { map.insert((*ty).get_type(), ty); }
|
unsafe { map.insert((*ty).get_type(), ty); }
|
||||||
Item {
|
Item {
|
||||||
@@ -52,7 +52,7 @@ impl Item {
|
|||||||
&raw[..]
|
&raw[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typed<H: Header + HeaderFormat + Any>(&self) -> Option<&H> {
|
pub fn typed<H: Header + Any>(&self) -> Option<&H> {
|
||||||
let tid = TypeId::of::<H>();
|
let tid = TypeId::of::<H>();
|
||||||
match self.typed.get(tid) {
|
match self.typed.get(tid) {
|
||||||
Some(val) => Some(val),
|
Some(val) => Some(val),
|
||||||
@@ -68,7 +68,7 @@ impl Item {
|
|||||||
}.map(|typed| unsafe { typed.downcast_ref_unchecked() })
|
}.map(|typed| unsafe { typed.downcast_ref_unchecked() })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typed_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut H> {
|
pub fn typed_mut<H: Header>(&mut self) -> Option<&mut H> {
|
||||||
let tid = TypeId::of::<H>();
|
let tid = TypeId::of::<H>();
|
||||||
if self.typed.get_mut(tid).is_none() {
|
if self.typed.get_mut(tid).is_none() {
|
||||||
match parse::<H>(self.raw.as_ref().expect("item.raw must exist")) {
|
match parse::<H>(self.raw.as_ref().expect("item.raw must exist")) {
|
||||||
@@ -83,11 +83,11 @@ impl Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse<H: Header + HeaderFormat>(raw: &Vec<Vec<u8>>) ->
|
fn parse<H: Header>(raw: &Vec<Vec<u8>>) ->
|
||||||
::Result<Box<HeaderFormat + Send + Sync>> {
|
::Result<Box<Header + Send + Sync>> {
|
||||||
Header::parse_header(&raw[..]).map(|h: H| {
|
Header::parse_header(&raw[..]).map(|h: H| {
|
||||||
// FIXME: Use Type ascription
|
// FIXME: Use Type ascription
|
||||||
let h: Box<HeaderFormat + Send + Sync> = Box::new(h);
|
let h: Box<Header + Send + Sync> = Box::new(h);
|
||||||
h
|
h
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,18 +31,17 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! This works well for simple "string" headers. But the header system
|
//! This works well for simple "string" headers. If you need more control,
|
||||||
//! actually involves 2 parts: parsing, and formatting. If you need to
|
//! you can implement the trait directly.
|
||||||
//! customize either part, you can do so.
|
|
||||||
//!
|
//!
|
||||||
//! ## `Header` and `HeaderFormat`
|
//! ## Implementing the `Header` trait
|
||||||
//!
|
//!
|
||||||
//! Consider a Do Not Track header. It can be true or false, but it represents
|
//! Consider a Do Not Track header. It can be true or false, but it represents
|
||||||
//! that via the numerals `1` and `0`.
|
//! that via the numerals `1` and `0`.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use std::fmt;
|
//! use std::fmt;
|
||||||
//! use hyper::header::{Header, HeaderFormat};
|
//! use hyper::header::Header;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Debug, Clone, Copy)]
|
//! #[derive(Debug, Clone, Copy)]
|
||||||
//! struct Dnt(bool);
|
//! struct Dnt(bool);
|
||||||
@@ -66,9 +65,7 @@
|
|||||||
//! }
|
//! }
|
||||||
//! Err(hyper::Error::Header)
|
//! Err(hyper::Error::Header)
|
||||||
//! }
|
//! }
|
||||||
//! }
|
|
||||||
//!
|
//!
|
||||||
//! impl HeaderFormat for Dnt {
|
|
||||||
//! fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
//! fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
//! if self.0 {
|
//! if self.0 {
|
||||||
//! f.write_str("1")
|
//! f.write_str("1")
|
||||||
@@ -113,11 +110,11 @@ type HeaderName = UniCase<CowStr>;
|
|||||||
///
|
///
|
||||||
/// This trait represents the construction and identification of headers,
|
/// This trait represents the construction and identification of headers,
|
||||||
/// and contains trait-object unsafe methods.
|
/// and contains trait-object unsafe methods.
|
||||||
pub trait Header: Clone + Any + Send + Sync {
|
pub trait Header: HeaderClone + Any + Typeable + Send + Sync {
|
||||||
/// Returns the name of the header field this belongs to.
|
/// Returns the name of the header field this belongs to.
|
||||||
///
|
///
|
||||||
/// This will become an associated constant once available.
|
/// This will become an associated constant once available.
|
||||||
fn header_name() -> &'static str;
|
fn header_name() -> &'static str where Self: Sized;
|
||||||
/// Parse a header from a raw stream of bytes.
|
/// Parse a header from a raw stream of bytes.
|
||||||
///
|
///
|
||||||
/// It's possible that a request can include a header field more than once,
|
/// It's possible that a request can include a header field more than once,
|
||||||
@@ -125,35 +122,27 @@ pub trait Header: Clone + Any + Send + Sync {
|
|||||||
/// it's not necessarily the case that a Header is *allowed* to have more
|
/// it's not necessarily the case that a Header is *allowed* to have more
|
||||||
/// than one field value. If that's the case, you **should** return `None`
|
/// than one field value. If that's the case, you **should** return `None`
|
||||||
/// if `raw.len() > 1`.
|
/// if `raw.len() > 1`.
|
||||||
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Self>;
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Self> where Self: Sized;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait for any object that will represent a header field and value.
|
|
||||||
///
|
|
||||||
/// This trait represents the formatting of a `Header` for output to a TcpStream.
|
|
||||||
pub trait HeaderFormat: fmt::Debug + HeaderClone + Any + Typeable + Send + Sync {
|
|
||||||
/// Format a header to be output into a TcpStream.
|
/// Format a header to be output into a TcpStream.
|
||||||
///
|
///
|
||||||
/// This method is not allowed to introduce an Err not produced
|
/// This method is not allowed to introduce an Err not produced
|
||||||
/// by the passed-in Formatter.
|
/// by the passed-in Formatter.
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result;
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait HeaderClone {
|
pub trait HeaderClone {
|
||||||
fn clone_box(&self) -> Box<HeaderFormat + Send + Sync>;
|
fn clone_box(&self) -> Box<Header + Send + Sync>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HeaderFormat + Clone> HeaderClone for T {
|
impl<T: Header + Clone> HeaderClone for T {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn clone_box(&self) -> Box<HeaderFormat + Send + Sync> {
|
fn clone_box(&self) -> Box<Header + Send + Sync> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderFormat + Send + Sync {
|
impl Header + Send + Sync {
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
|
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
|
||||||
mem::transmute(traitobject::data(self))
|
mem::transmute(traitobject::data(self))
|
||||||
@@ -165,9 +154,9 @@ impl HeaderFormat + Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Box<HeaderFormat + Send + Sync> {
|
impl Clone for Box<Header + Send + Sync> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn clone(&self) -> Box<HeaderFormat + Send + Sync> {
|
fn clone(&self) -> Box<Header + Send + Sync> {
|
||||||
self.clone_box()
|
self.clone_box()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,6 +172,12 @@ pub struct Headers {
|
|||||||
data: HashMap<HeaderName, Item>
|
data: HashMap<HeaderName, Item>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Headers {
|
||||||
|
fn default() -> Headers {
|
||||||
|
Headers::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Headers {
|
impl Headers {
|
||||||
|
|
||||||
/// Creates a new, empty headers map.
|
/// Creates a new, empty headers map.
|
||||||
@@ -212,8 +207,8 @@ impl Headers {
|
|||||||
/// Set a header field to the corresponding value.
|
/// Set a header field to the corresponding value.
|
||||||
///
|
///
|
||||||
/// The field is determined by the type of the value being set.
|
/// The field is determined by the type of the value being set.
|
||||||
pub fn set<H: Header + HeaderFormat>(&mut self, value: H) {
|
pub fn set<H: Header>(&mut self, value: H) {
|
||||||
trace!("Headers.set( {:?}, {:?} )", header_name::<H>(), value);
|
trace!("Headers.set( {:?}, {:?} )", header_name::<H>(), HeaderFormatter(&value));
|
||||||
self.data.insert(UniCase(CowStr(Cow::Borrowed(header_name::<H>()))),
|
self.data.insert(UniCase(CowStr(Cow::Borrowed(header_name::<H>()))),
|
||||||
Item::new_typed(Box::new(value)));
|
Item::new_typed(Box::new(value)));
|
||||||
}
|
}
|
||||||
@@ -259,13 +254,13 @@ impl Headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the header field's value, if it exists.
|
/// Get a reference to the header field's value, if it exists.
|
||||||
pub fn get<H: Header + HeaderFormat>(&self) -> Option<&H> {
|
pub fn get<H: Header>(&self) -> Option<&H> {
|
||||||
self.data.get(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
self.data.get(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
||||||
.and_then(Item::typed::<H>)
|
.and_then(Item::typed::<H>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the header field's value, if it exists.
|
/// Get a mutable reference to the header field's value, if it exists.
|
||||||
pub fn get_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut H> {
|
pub fn get_mut<H: Header>(&mut self) -> Option<&mut H> {
|
||||||
self.data.get_mut(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
self.data.get_mut(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
||||||
.and_then(Item::typed_mut::<H>)
|
.and_then(Item::typed_mut::<H>)
|
||||||
}
|
}
|
||||||
@@ -280,13 +275,13 @@ impl Headers {
|
|||||||
/// # let mut headers = Headers::new();
|
/// # let mut headers = Headers::new();
|
||||||
/// let has_type = headers.has::<ContentType>();
|
/// let has_type = headers.has::<ContentType>();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn has<H: Header + HeaderFormat>(&self) -> bool {
|
pub fn has<H: Header>(&self) -> bool {
|
||||||
self.data.contains_key(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
self.data.contains_key(&UniCase(CowStr(Cow::Borrowed(header_name::<H>()))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a header from the map, if one existed.
|
/// Removes a header from the map, if one existed.
|
||||||
/// Returns true if a header has been removed.
|
/// Returns true if a header has been removed.
|
||||||
pub fn remove<H: Header + HeaderFormat>(&mut self) -> bool {
|
pub fn remove<H: Header>(&mut self) -> bool {
|
||||||
trace!("Headers.remove( {:?} )", header_name::<H>());
|
trace!("Headers.remove( {:?} )", header_name::<H>());
|
||||||
self.data.remove(&UniCase(CowStr(Cow::Borrowed(header_name::<H>())))).is_some()
|
self.data.remove(&UniCase(CowStr(Cow::Borrowed(header_name::<H>())))).is_some()
|
||||||
}
|
}
|
||||||
@@ -380,6 +375,7 @@ impl Deserialize for Headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An `Iterator` over the fields in a `Headers` map.
|
/// An `Iterator` over the fields in a `Headers` map.
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct HeadersItems<'a> {
|
pub struct HeadersItems<'a> {
|
||||||
inner: Iter<'a, HeaderName, Item>
|
inner: Iter<'a, HeaderName, Item>
|
||||||
}
|
}
|
||||||
@@ -410,7 +406,7 @@ impl<'a> HeaderView<'a> {
|
|||||||
|
|
||||||
/// Cast the value to a certain Header type.
|
/// Cast the value to a certain Header type.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn value<H: Header + HeaderFormat>(&self) -> Option<&'a H> {
|
pub fn value<H: Header>(&self) -> Option<&'a H> {
|
||||||
self.1.typed::<H>()
|
self.1.typed::<H>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +445,7 @@ impl<'a> FromIterator<HeaderView<'a>> for Headers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for &'a (HeaderFormat + Send + Sync) {
|
impl<'a> fmt::Display for &'a (Header + Send + Sync) {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
(**self).fmt_header(f)
|
(**self).fmt_header(f)
|
||||||
@@ -461,16 +457,16 @@ impl<'a> fmt::Display for &'a (HeaderFormat + Send + Sync) {
|
|||||||
/// This can be used like so: `format!("{}", HeaderFormatter(&header))` to
|
/// This can be used like so: `format!("{}", HeaderFormatter(&header))` to
|
||||||
/// get the representation of a Header which will be written to an
|
/// get the representation of a Header which will be written to an
|
||||||
/// outgoing `TcpStream`.
|
/// outgoing `TcpStream`.
|
||||||
pub struct HeaderFormatter<'a, H: HeaderFormat>(pub &'a H);
|
pub struct HeaderFormatter<'a, H: Header>(pub &'a H);
|
||||||
|
|
||||||
impl<'a, H: HeaderFormat> fmt::Display for HeaderFormatter<'a, H> {
|
impl<'a, H: Header> fmt::Display for HeaderFormatter<'a, H> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.0.fmt_header(f)
|
self.0.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, H: HeaderFormat> fmt::Debug for HeaderFormatter<'a, H> {
|
impl<'a, H: Header> fmt::Debug for HeaderFormatter<'a, H> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.0.fmt_header(f)
|
self.0.fmt_header(f)
|
||||||
@@ -519,7 +515,7 @@ mod tests {
|
|||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use mime::TopLevel::Text;
|
use mime::TopLevel::Text;
|
||||||
use mime::SubLevel::Plain;
|
use mime::SubLevel::Plain;
|
||||||
use super::{Headers, Header, HeaderFormat, ContentLength, ContentType,
|
use super::{Headers, Header, ContentLength, ContentType,
|
||||||
Accept, Host, qitem};
|
Accept, Host, qitem};
|
||||||
use httparse;
|
use httparse;
|
||||||
|
|
||||||
@@ -597,9 +593,7 @@ mod tests {
|
|||||||
None => Err(::Error::Header),
|
None => Err(::Error::Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderFormat for CrazyLength {
|
|
||||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let CrazyLength(ref opt, ref value) = *self;
|
let CrazyLength(ref opt, ref value) = *self;
|
||||||
write!(f, "{:?}, {:?}", opt, value)
|
write!(f, "{:?}, {:?}", opt, value)
|
||||||
|
|||||||
@@ -137,6 +137,12 @@ define_encode_set! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HTTP_VALUE {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad("HTTP_VALUE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ExtendedValue {
|
impl Display for ExtendedValue {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let encoded_value =
|
let encoded_value =
|
||||||
|
|||||||
120
src/http/buffer.rs
Normal file
120
src/http/buffer.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use std::cmp;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
|
||||||
|
const INIT_BUFFER_SIZE: usize = 4096;
|
||||||
|
const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Buffer {
|
||||||
|
vec: Vec<u8>,
|
||||||
|
read_pos: usize,
|
||||||
|
write_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
pub fn new() -> Buffer {
|
||||||
|
Buffer {
|
||||||
|
vec: Vec::new(),
|
||||||
|
read_pos: 0,
|
||||||
|
write_pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
*self = Buffer::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.read_pos - self.write_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
&self.vec[self.write_pos..self.read_pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn consume(&mut self, pos: usize) {
|
||||||
|
debug_assert!(self.read_pos >= self.write_pos + pos);
|
||||||
|
self.write_pos += pos;
|
||||||
|
if self.write_pos == self.read_pos {
|
||||||
|
self.write_pos = 0;
|
||||||
|
self.read_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from<R: Read>(&mut self, r: &mut R) -> io::Result<usize> {
|
||||||
|
self.maybe_reserve();
|
||||||
|
let n = try!(r.read(&mut self.vec[self.read_pos..]));
|
||||||
|
self.read_pos += n;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn maybe_reserve(&mut self) {
|
||||||
|
let cap = self.vec.len();
|
||||||
|
if cap == 0 {
|
||||||
|
trace!("reserving initial {}", INIT_BUFFER_SIZE);
|
||||||
|
self.vec = vec![0; INIT_BUFFER_SIZE];
|
||||||
|
} else if self.write_pos > 0 && self.read_pos == cap {
|
||||||
|
let count = self.read_pos - self.write_pos;
|
||||||
|
trace!("moving buffer bytes over by {}", count);
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(
|
||||||
|
self.vec.as_ptr().offset(self.write_pos as isize),
|
||||||
|
self.vec.as_mut_ptr(),
|
||||||
|
count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.read_pos -= count;
|
||||||
|
self.write_pos = 0;
|
||||||
|
} else if self.read_pos == cap && cap < MAX_BUFFER_SIZE {
|
||||||
|
self.vec.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap);
|
||||||
|
let new = self.vec.capacity() - cap;
|
||||||
|
trace!("reserved {}", new);
|
||||||
|
unsafe { grow_zerofill(&mut self.vec, new) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap<'a, 'b: 'a, R: io::Read>(&'a mut self, reader: &'b mut R) -> BufReader<'a, R> {
|
||||||
|
BufReader {
|
||||||
|
buf: self,
|
||||||
|
reader: reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BufReader<'a, R: io::Read + 'a> {
|
||||||
|
buf: &'a mut Buffer,
|
||||||
|
reader: &'a mut R
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: io::Read> Read for BufReader<'a, R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
trace!("BufReader.read self={}, buf={}", self.buf.len(), buf.len());
|
||||||
|
let n = try!(self.buf.bytes().read(buf));
|
||||||
|
self.buf.consume(n);
|
||||||
|
if n == 0 {
|
||||||
|
self.buf.reset();
|
||||||
|
self.reader.read(&mut buf[n..])
|
||||||
|
} else {
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn grow_zerofill(buf: &mut Vec<u8>, additional: usize) {
|
||||||
|
let len = buf.len();
|
||||||
|
buf.set_len(len + additional);
|
||||||
|
ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());
|
||||||
|
}
|
||||||
96
src/http/channel.rs
Normal file
96
src/http/channel.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::sync::{Arc, mpsc};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use ::rotor;
|
||||||
|
|
||||||
|
pub use std::sync::mpsc::TryRecvError;
|
||||||
|
|
||||||
|
pub fn new<T>(notify: rotor::Notifier) -> (Sender<T>, Receiver<T>) {
|
||||||
|
let b = Arc::new(AtomicBool::new(false));
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
(Sender {
|
||||||
|
awake: b.clone(),
|
||||||
|
notify: notify,
|
||||||
|
tx: tx,
|
||||||
|
},
|
||||||
|
Receiver {
|
||||||
|
awake: b,
|
||||||
|
rx: rx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn share<T, U>(other: &Sender<U>) -> (Sender<T>, Receiver<T>) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let notify = other.notify.clone();
|
||||||
|
let b = other.awake.clone();
|
||||||
|
(Sender {
|
||||||
|
awake: b.clone(),
|
||||||
|
notify: notify,
|
||||||
|
tx: tx,
|
||||||
|
},
|
||||||
|
Receiver {
|
||||||
|
awake: b,
|
||||||
|
rx: rx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sender<T> {
|
||||||
|
awake: Arc<AtomicBool>,
|
||||||
|
notify: rotor::Notifier,
|
||||||
|
tx: mpsc::Sender<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send> Sender<T> {
|
||||||
|
pub fn send(&self, val: T) -> Result<(), SendError<T>> {
|
||||||
|
try!(self.tx.send(val));
|
||||||
|
if !self.awake.swap(true, Ordering::SeqCst) {
|
||||||
|
try!(self.notify.wakeup());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for Sender<T> {
|
||||||
|
fn clone(&self) -> Sender<T> {
|
||||||
|
Sender {
|
||||||
|
awake: self.awake.clone(),
|
||||||
|
notify: self.notify.clone(),
|
||||||
|
tx: self.tx.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Sender<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Sender")
|
||||||
|
.field("notify", &self.notify)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SendError<T>(pub Option<T>);
|
||||||
|
|
||||||
|
impl<T> From<mpsc::SendError<T>> for SendError<T> {
|
||||||
|
fn from(e: mpsc::SendError<T>) -> SendError<T> {
|
||||||
|
SendError(Some(e.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<rotor::WakeupError> for SendError<T> {
|
||||||
|
fn from(_e: rotor::WakeupError) -> SendError<T> {
|
||||||
|
SendError(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Receiver<T> {
|
||||||
|
awake: Arc<AtomicBool>,
|
||||||
|
rx: mpsc::Receiver<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send> Receiver<T> {
|
||||||
|
pub fn try_recv(&self) -> Result<T, mpsc::TryRecvError> {
|
||||||
|
self.awake.store(false, Ordering::Relaxed);
|
||||||
|
self.rx.try_recv()
|
||||||
|
}
|
||||||
|
}
|
||||||
915
src/http/conn.rs
Normal file
915
src/http/conn.rs
Normal file
@@ -0,0 +1,915 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use rotor::{self, EventSet, PollOpt, Scope};
|
||||||
|
|
||||||
|
use http::{self, h1, Http1Message, Encoder, Decoder, Next, Next_, Reg, Control};
|
||||||
|
use http::channel;
|
||||||
|
use http::internal::WriteBuf;
|
||||||
|
use http::buffer::Buffer;
|
||||||
|
use net::{Transport, Blocked};
|
||||||
|
use version::HttpVersion;
|
||||||
|
|
||||||
|
const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100;
|
||||||
|
|
||||||
|
/// This handles a connection, which will have been established over a
|
||||||
|
/// Transport (like a socket), and will likely include multiple
|
||||||
|
/// `Message`s over HTTP.
|
||||||
|
///
|
||||||
|
/// The connection will determine when a message begins and ends, creating
|
||||||
|
/// a new message `MessageHandler` for each one, as well as determine if this
|
||||||
|
/// connection can be kept alive after the message, or if it is complete.
|
||||||
|
pub struct Conn<K: Key, T: Transport, H: MessageHandler<T>> {
|
||||||
|
buf: Buffer,
|
||||||
|
ctrl: (channel::Sender<Next>, channel::Receiver<Next>),
|
||||||
|
keep_alive_enabled: bool,
|
||||||
|
key: K,
|
||||||
|
state: State<H, T>,
|
||||||
|
transport: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Key, T: Transport, H: MessageHandler<T>> fmt::Debug for Conn<K, T, H> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Conn")
|
||||||
|
.field("keep_alive_enabled", &self.keep_alive_enabled)
|
||||||
|
.field("state", &self.state)
|
||||||
|
.field("buf", &self.buf)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> {
|
||||||
|
pub fn new(key: K, transport: T, notify: rotor::Notifier) -> Conn<K, T, H> {
|
||||||
|
Conn {
|
||||||
|
buf: Buffer::new(),
|
||||||
|
ctrl: channel::new(notify),
|
||||||
|
keep_alive_enabled: true,
|
||||||
|
key: key,
|
||||||
|
state: State::Init,
|
||||||
|
transport: transport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keep_alive(mut self, val: bool) -> Conn<K, T, H> {
|
||||||
|
self.keep_alive_enabled = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Desired Register interest based on state of current connection.
|
||||||
|
///
|
||||||
|
/// This includes the user interest, such as when they return `Next::read()`.
|
||||||
|
fn interest(&self) -> Reg {
|
||||||
|
match self.state {
|
||||||
|
State::Closed => Reg::Remove,
|
||||||
|
State::Init => {
|
||||||
|
<H as MessageHandler>::Message::initial_interest().interest()
|
||||||
|
}
|
||||||
|
State::Http1(Http1 { reading: Reading::Closed, writing: Writing::Closed, .. }) => {
|
||||||
|
Reg::Remove
|
||||||
|
}
|
||||||
|
State::Http1(Http1 { ref reading, ref writing, .. }) => {
|
||||||
|
let read = match *reading {
|
||||||
|
Reading::Parse |
|
||||||
|
Reading::Body(..) => Reg::Read,
|
||||||
|
Reading::Init |
|
||||||
|
Reading::Wait(..) |
|
||||||
|
Reading::KeepAlive |
|
||||||
|
Reading::Closed => Reg::Wait
|
||||||
|
};
|
||||||
|
|
||||||
|
let write = match *writing {
|
||||||
|
Writing::Head |
|
||||||
|
Writing::Chunk(..) |
|
||||||
|
Writing::Ready(..) => Reg::Write,
|
||||||
|
Writing::Init |
|
||||||
|
Writing::Wait(..) |
|
||||||
|
Writing::KeepAlive => Reg::Wait,
|
||||||
|
Writing::Closed => Reg::Wait,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (read, write) {
|
||||||
|
(Reg::Read, Reg::Write) => Reg::ReadWrite,
|
||||||
|
(Reg::Read, Reg::Wait) => Reg::Read,
|
||||||
|
(Reg::Wait, Reg::Write) => Reg::Write,
|
||||||
|
(Reg::Wait, Reg::Wait) => Reg::Wait,
|
||||||
|
_ => unreachable!("bad read/write reg combo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actual register action.
|
||||||
|
///
|
||||||
|
/// Considers the user interest(), but also compares if the underlying
|
||||||
|
/// transport is blocked(), and adjusts accordingly.
|
||||||
|
fn register(&self) -> Reg {
|
||||||
|
let reg = self.interest();
|
||||||
|
match (reg, self.transport.blocked()) {
|
||||||
|
(Reg::Remove, _) |
|
||||||
|
(Reg::Wait, _) |
|
||||||
|
(_, None) => reg,
|
||||||
|
|
||||||
|
(_, Some(Blocked::Read)) => Reg::Read,
|
||||||
|
(_, Some(Blocked::Write)) => Reg::Write,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self) -> ::Result<http::MessageHead<<<H as MessageHandler<T>>::Message as Http1Message>::Incoming>> {
|
||||||
|
let n = try!(self.buf.read_from(&mut self.transport));
|
||||||
|
if n == 0 {
|
||||||
|
trace!("parse eof");
|
||||||
|
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "parse eof").into());
|
||||||
|
}
|
||||||
|
match try!(http::parse::<<H as MessageHandler<T>>::Message, _>(self.buf.bytes())) {
|
||||||
|
Some((head, len)) => {
|
||||||
|
trace!("parsed {} bytes out of {}", len, self.buf.len());
|
||||||
|
self.buf.consume(len);
|
||||||
|
Ok(head)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if self.buf.len() >= MAX_BUFFER_SIZE {
|
||||||
|
//TODO: Handler.on_too_large_error()
|
||||||
|
debug!("MAX_BUFFER_SIZE reached, closing");
|
||||||
|
Err(::Error::TooLarge)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "incomplete parse").into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read<F: MessageHandlerFactory<K, T, Output=H>>(&mut self, scope: &mut Scope<F>, state: State<H, T>) -> State<H, T> {
|
||||||
|
match state {
|
||||||
|
State::Init => {
|
||||||
|
let head = match self.parse() {
|
||||||
|
Ok(head) => head,
|
||||||
|
Err(::Error::Io(e)) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock |
|
||||||
|
io::ErrorKind::Interrupted => return State::Init,
|
||||||
|
_ => {
|
||||||
|
debug!("io error trying to parse {:?}", e);
|
||||||
|
return State::Closed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
//TODO: send proper error codes depending on error
|
||||||
|
trace!("parse eror: {:?}", e);
|
||||||
|
return State::Closed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match <<H as MessageHandler<T>>::Message as Http1Message>::decoder(&head) {
|
||||||
|
Ok(decoder) => {
|
||||||
|
trace!("decoder = {:?}", decoder);
|
||||||
|
let keep_alive = self.keep_alive_enabled && head.should_keep_alive();
|
||||||
|
let mut handler = scope.create(Seed(&self.key, &self.ctrl.0));
|
||||||
|
let next = handler.on_incoming(head);
|
||||||
|
trace!("handler.on_incoming() -> {:?}", next);
|
||||||
|
|
||||||
|
match next.interest {
|
||||||
|
Next_::Read => self.read(scope, State::Http1(Http1 {
|
||||||
|
handler: handler,
|
||||||
|
reading: Reading::Body(decoder),
|
||||||
|
writing: Writing::Init,
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
timeout: next.timeout,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})),
|
||||||
|
Next_::Write => State::Http1(Http1 {
|
||||||
|
handler: handler,
|
||||||
|
reading: if decoder.is_eof() {
|
||||||
|
if keep_alive {
|
||||||
|
Reading::KeepAlive
|
||||||
|
} else {
|
||||||
|
Reading::Closed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Reading::Wait(decoder)
|
||||||
|
},
|
||||||
|
writing: Writing::Head,
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
timeout: next.timeout,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}),
|
||||||
|
Next_::ReadWrite => self.read(scope, State::Http1(Http1 {
|
||||||
|
handler: handler,
|
||||||
|
reading: Reading::Body(decoder),
|
||||||
|
writing: Writing::Head,
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
timeout: next.timeout,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})),
|
||||||
|
Next_::Wait => State::Http1(Http1 {
|
||||||
|
handler: handler,
|
||||||
|
reading: Reading::Wait(decoder),
|
||||||
|
writing: Writing::Init,
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
timeout: next.timeout,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}),
|
||||||
|
Next_::End |
|
||||||
|
Next_::Remove => State::Closed,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
debug!("error creating decoder: {:?}", e);
|
||||||
|
//TODO: respond with 400
|
||||||
|
State::Closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State::Http1(mut http1) => {
|
||||||
|
let next = match http1.reading {
|
||||||
|
Reading::Init => None,
|
||||||
|
Reading::Parse => match self.parse() {
|
||||||
|
Ok(head) => match <<H as MessageHandler<T>>::Message as Http1Message>::decoder(&head) {
|
||||||
|
Ok(decoder) => {
|
||||||
|
trace!("decoder = {:?}", decoder);
|
||||||
|
// if client request asked for keep alive,
|
||||||
|
// then it depends entirely on if the server agreed
|
||||||
|
if http1.keep_alive {
|
||||||
|
http1.keep_alive = head.should_keep_alive();
|
||||||
|
}
|
||||||
|
let next = http1.handler.on_incoming(head);
|
||||||
|
http1.reading = Reading::Wait(decoder);
|
||||||
|
trace!("handler.on_incoming() -> {:?}", next);
|
||||||
|
Some(next)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
debug!("error creating decoder: {:?}", e);
|
||||||
|
//TODO: respond with 400
|
||||||
|
return State::Closed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(::Error::Io(e)) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock |
|
||||||
|
io::ErrorKind::Interrupted => None,
|
||||||
|
_ => {
|
||||||
|
debug!("io error trying to parse {:?}", e);
|
||||||
|
return State::Closed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
//TODO: send proper error codes depending on error
|
||||||
|
trace!("parse eror: {:?}", e);
|
||||||
|
return State::Closed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Reading::Body(ref mut decoder) => {
|
||||||
|
let wrapped = if !self.buf.is_empty() {
|
||||||
|
super::Trans::Buf(self.buf.wrap(&mut self.transport))
|
||||||
|
} else {
|
||||||
|
super::Trans::Port(&mut self.transport)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(http1.handler.on_decode(&mut Decoder::h1(decoder, wrapped)))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
trace!("Conn.on_readable State::Http1(reading = {:?})", http1.reading);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut s = State::Http1(http1);
|
||||||
|
trace!("h1 read completed, next = {:?}", next);
|
||||||
|
if let Some(next) = next {
|
||||||
|
s.update(next);
|
||||||
|
}
|
||||||
|
trace!("h1 read completed, state = {:?}", s);
|
||||||
|
|
||||||
|
let again = match s {
|
||||||
|
State::Http1(Http1 { reading: Reading::Body(ref encoder), .. }) => encoder.is_eof(),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if again {
|
||||||
|
self.read(scope, s)
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State::Closed => {
|
||||||
|
error!("on_readable State::Closed");
|
||||||
|
State::Closed
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<F: MessageHandlerFactory<K, T, Output=H>>(&mut self, scope: &mut Scope<F>, mut state: State<H, T>) -> State<H, T> {
|
||||||
|
let next = match state {
|
||||||
|
State::Init => {
|
||||||
|
// this could be a Client request, which writes first, so pay
|
||||||
|
// attention to the version written here, which will adjust
|
||||||
|
// our internal state to Http1 or Http2
|
||||||
|
let mut handler = scope.create(Seed(&self.key, &self.ctrl.0));
|
||||||
|
let mut head = http::MessageHead::default();
|
||||||
|
let interest = handler.on_outgoing(&mut head);
|
||||||
|
if head.version == HttpVersion::Http11 {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let keep_alive = self.keep_alive_enabled && head.should_keep_alive();
|
||||||
|
let mut encoder = H::Message::encode(head, &mut buf);
|
||||||
|
let writing = match interest.interest {
|
||||||
|
// user wants to write some data right away
|
||||||
|
// try to write the headers and the first chunk
|
||||||
|
// together, so they are in the same packet
|
||||||
|
Next_::Write |
|
||||||
|
Next_::ReadWrite => {
|
||||||
|
encoder.prefix(WriteBuf {
|
||||||
|
bytes: buf,
|
||||||
|
pos: 0
|
||||||
|
});
|
||||||
|
Writing::Ready(encoder)
|
||||||
|
},
|
||||||
|
_ => Writing::Chunk(Chunk {
|
||||||
|
buf: Cow::Owned(buf),
|
||||||
|
pos: 0,
|
||||||
|
next: (encoder, interest.clone())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
state = State::Http1(Http1 {
|
||||||
|
reading: Reading::Init,
|
||||||
|
writing: writing,
|
||||||
|
handler: handler,
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
timeout: interest.timeout,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some(interest)
|
||||||
|
}
|
||||||
|
State::Http1(Http1 { ref mut handler, ref mut writing, ref mut keep_alive, .. }) => {
|
||||||
|
match *writing {
|
||||||
|
Writing::Init => {
|
||||||
|
trace!("Conn.on_writable Http1::Writing::Init");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Writing::Head => {
|
||||||
|
let mut head = http::MessageHead::default();
|
||||||
|
let interest = handler.on_outgoing(&mut head);
|
||||||
|
// if the request wants to close, server cannot stop it
|
||||||
|
if *keep_alive {
|
||||||
|
// if the request wants to stay alive, then it depends
|
||||||
|
// on the server to agree
|
||||||
|
*keep_alive = head.should_keep_alive();
|
||||||
|
}
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut encoder = <<H as MessageHandler<T>>::Message as Http1Message>::encode(head, &mut buf);
|
||||||
|
*writing = match interest.interest {
|
||||||
|
// user wants to write some data right away
|
||||||
|
// try to write the headers and the first chunk
|
||||||
|
// together, so they are in the same packet
|
||||||
|
Next_::Write |
|
||||||
|
Next_::ReadWrite => {
|
||||||
|
encoder.prefix(WriteBuf {
|
||||||
|
bytes: buf,
|
||||||
|
pos: 0
|
||||||
|
});
|
||||||
|
Writing::Ready(encoder)
|
||||||
|
},
|
||||||
|
_ => Writing::Chunk(Chunk {
|
||||||
|
buf: Cow::Owned(buf),
|
||||||
|
pos: 0,
|
||||||
|
next: (encoder, interest.clone())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Some(interest)
|
||||||
|
},
|
||||||
|
Writing::Chunk(ref mut chunk) => {
|
||||||
|
trace!("Http1.Chunk on_writable");
|
||||||
|
match self.transport.write(&chunk.buf.as_ref()[chunk.pos..]) {
|
||||||
|
Ok(n) => {
|
||||||
|
chunk.pos += n;
|
||||||
|
trace!("Http1.Chunk wrote={}, done={}", n, chunk.is_written());
|
||||||
|
if chunk.is_written() {
|
||||||
|
Some(chunk.next.1.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock |
|
||||||
|
io::ErrorKind::Interrupted => None,
|
||||||
|
_ => {
|
||||||
|
Some(handler.on_error(e.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Writing::Ready(ref mut encoder) => {
|
||||||
|
trace!("Http1.Ready on_writable");
|
||||||
|
Some(handler.on_encode(&mut Encoder::h1(encoder, &mut self.transport)))
|
||||||
|
},
|
||||||
|
Writing::Wait(..) => {
|
||||||
|
trace!("Conn.on_writable Http1::Writing::Wait");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Writing::KeepAlive => {
|
||||||
|
trace!("Conn.on_writable Http1::Writing::KeepAlive");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Writing::Closed => {
|
||||||
|
trace!("on_writable Http1::Writing::Closed");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State::Closed => {
|
||||||
|
trace!("on_writable State::Closed");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(next) = next {
|
||||||
|
state.update(next);
|
||||||
|
}
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_read_more(&self) -> bool {
|
||||||
|
match self.state {
|
||||||
|
State::Init => false,
|
||||||
|
_ => !self.buf.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ready<F>(mut self, events: EventSet, scope: &mut Scope<F>) -> Option<(Self, Option<Duration>)>
|
||||||
|
where F: MessageHandlerFactory<K, T, Output=H> {
|
||||||
|
trace!("Conn::ready events='{:?}', blocked={:?}", events, self.transport.blocked());
|
||||||
|
|
||||||
|
if events.is_error() {
|
||||||
|
match self.transport.take_socket_error() {
|
||||||
|
Ok(_) => {
|
||||||
|
trace!("is_error, but not socket error");
|
||||||
|
// spurious?
|
||||||
|
},
|
||||||
|
Err(e) => self.on_error(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user had an io interest, but the transport was blocked differently,
|
||||||
|
// the event needs to be translated to what the user was actually expecting.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// - User asks for `Next::write().
|
||||||
|
// - But transport is in the middle of renegotiating TLS, and is blocked on reading.
|
||||||
|
// - hyper should not wait on the `write` event, since epoll already
|
||||||
|
// knows it is writable. We would just loop a whole bunch, and slow down.
|
||||||
|
// - So instead, hyper waits on the event needed to unblock the transport, `read`.
|
||||||
|
// - Once epoll detects the transport is readable, it will alert hyper
|
||||||
|
// with a `readable` event.
|
||||||
|
// - hyper needs to translate that `readable` event back into a `write`,
|
||||||
|
// since that is actually what the Handler wants.
|
||||||
|
|
||||||
|
let events = if let Some(blocked) = self.transport.blocked() {
|
||||||
|
let interest = self.interest();
|
||||||
|
trace!("translating blocked={:?}, interest={:?}", blocked, interest);
|
||||||
|
match (blocked, interest) {
|
||||||
|
(Blocked::Read, Reg::Write) => EventSet::writable(),
|
||||||
|
(Blocked::Write, Reg::Read) => EventSet::readable(),
|
||||||
|
// otherwise, the transport was blocked on the same thing the user wanted
|
||||||
|
_ => events
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
events
|
||||||
|
};
|
||||||
|
|
||||||
|
if events.is_readable() {
|
||||||
|
self.on_readable(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
if events.is_writable() {
|
||||||
|
self.on_writable(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
let events = match self.register() {
|
||||||
|
Reg::Read => EventSet::readable(),
|
||||||
|
Reg::Write => EventSet::writable(),
|
||||||
|
Reg::ReadWrite => EventSet::readable() | EventSet::writable(),
|
||||||
|
Reg::Wait => EventSet::none(),
|
||||||
|
Reg::Remove => {
|
||||||
|
trace!("removing transport");
|
||||||
|
let _ = scope.deregister(&self.transport);
|
||||||
|
self.on_remove();
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if events.is_readable() && self.can_read_more() {
|
||||||
|
return self.ready(events, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("scope.reregister({:?})", events);
|
||||||
|
match scope.reregister(&self.transport, events, PollOpt::level()) {
|
||||||
|
Ok(..) => {
|
||||||
|
let timeout = self.state.timeout();
|
||||||
|
Some((self, timeout))
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("error reregistering: {:?}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wakeup<F>(mut self, scope: &mut Scope<F>) -> Option<(Self, Option<Duration>)>
|
||||||
|
where F: MessageHandlerFactory<K, T, Output=H> {
|
||||||
|
loop {
|
||||||
|
match self.ctrl.1.try_recv() {
|
||||||
|
Ok(next) => {
|
||||||
|
trace!("woke up with {:?}", next);
|
||||||
|
self.state.update(next);
|
||||||
|
},
|
||||||
|
Err(_) => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ready(EventSet::readable() | EventSet::writable(), scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timeout<F>(mut self, scope: &mut Scope<F>) -> Option<(Self, Option<Duration>)>
|
||||||
|
where F: MessageHandlerFactory<K, T, Output=H> {
|
||||||
|
//TODO: check if this was a spurious timeout?
|
||||||
|
self.on_error(::Error::Timeout);
|
||||||
|
self.ready(EventSet::none(), scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&mut self, err: ::Error) {
|
||||||
|
debug!("on_error err = {:?}", err);
|
||||||
|
trace!("on_error state = {:?}", self.state);
|
||||||
|
let next = match self.state {
|
||||||
|
State::Init => Next::remove(),
|
||||||
|
State::Http1(ref mut http1) => http1.handler.on_error(err),
|
||||||
|
State::Closed => Next::remove(),
|
||||||
|
};
|
||||||
|
self.state.update(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_remove(self) {
|
||||||
|
debug!("on_remove");
|
||||||
|
match self.state {
|
||||||
|
State::Init | State::Closed => (),
|
||||||
|
State::Http1(http1) => http1.handler.on_remove(self.transport),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_readable<F>(&mut self, scope: &mut Scope<F>)
|
||||||
|
where F: MessageHandlerFactory<K, T, Output=H> {
|
||||||
|
trace!("on_readable -> {:?}", self.state);
|
||||||
|
let state = mem::replace(&mut self.state, State::Closed);
|
||||||
|
self.state = self.read(scope, state);
|
||||||
|
trace!("on_readable <- {:?}", self.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_writable<F>(&mut self, scope: &mut Scope<F>)
|
||||||
|
where F: MessageHandlerFactory<K, T, Output=H> {
|
||||||
|
trace!("on_writable -> {:?}", self.state);
|
||||||
|
let state = mem::replace(&mut self.state, State::Closed);
|
||||||
|
self.state = self.write(scope, state);
|
||||||
|
trace!("on_writable <- {:?}", self.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<H: MessageHandler<T>, T: Transport> {
|
||||||
|
Init,
|
||||||
|
/// Http1 will only ever use a connection to send and receive a single
|
||||||
|
/// message at a time. Once a H1 status has been determined, we will either
|
||||||
|
/// be reading or writing an H1 message, and optionally multiple if
|
||||||
|
/// keep-alive is true.
|
||||||
|
Http1(Http1<H, T>),
|
||||||
|
/// Http2 allows multiplexing streams over a single connection. So even
|
||||||
|
/// when we've identified a certain message, we must always parse frame
|
||||||
|
/// head to determine if the incoming frame is part of a current message,
|
||||||
|
/// or a new one. This also means we could have multiple messages at once.
|
||||||
|
//Http2 {},
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<H: MessageHandler<T>, T: Transport> State<H, T> {
|
||||||
|
fn timeout(&self) -> Option<Duration> {
|
||||||
|
match *self {
|
||||||
|
State::Init => None,
|
||||||
|
State::Http1(ref http1) => http1.timeout,
|
||||||
|
State::Closed => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: MessageHandler<T>, T: Transport> fmt::Debug for State<H, T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
State::Init => f.write_str("Init"),
|
||||||
|
State::Http1(ref h1) => f.debug_tuple("Http1")
|
||||||
|
.field(h1)
|
||||||
|
.finish(),
|
||||||
|
State::Closed => f.write_str("Closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: MessageHandler<T>, T: Transport> State<H, T> {
|
||||||
|
fn update(&mut self, next: Next) {
|
||||||
|
let timeout = next.timeout;
|
||||||
|
let state = mem::replace(self, State::Closed);
|
||||||
|
let new_state = match (state, next.interest) {
|
||||||
|
(_, Next_::Remove) => State::Closed,
|
||||||
|
(State::Closed, _) => State::Closed,
|
||||||
|
(State::Init, _) => State::Init,
|
||||||
|
(State::Http1(http1), Next_::End) => {
|
||||||
|
let reading = match http1.reading {
|
||||||
|
Reading::Body(ref decoder) if decoder.is_eof() => {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Reading::KeepAlive
|
||||||
|
} else {
|
||||||
|
Reading::Closed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Reading::KeepAlive => http1.reading,
|
||||||
|
_ => Reading::Closed,
|
||||||
|
};
|
||||||
|
let writing = match http1.writing {
|
||||||
|
Writing::Ready(ref encoder) if encoder.is_eof() => {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Writing::KeepAlive
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Writing::Ready(encoder) => {
|
||||||
|
if encoder.is_eof() {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Writing::KeepAlive
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
} else if let Some(buf) = encoder.end() {
|
||||||
|
Writing::Chunk(Chunk {
|
||||||
|
buf: buf.bytes,
|
||||||
|
pos: buf.pos,
|
||||||
|
next: (h1::Encoder::length(0), Next::end())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Writing::Chunk(mut chunk) => {
|
||||||
|
if chunk.is_written() {
|
||||||
|
let encoder = chunk.next.0;
|
||||||
|
//TODO: de-dupe this code and from Writing::Ready
|
||||||
|
if encoder.is_eof() {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Writing::KeepAlive
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
} else if let Some(buf) = encoder.end() {
|
||||||
|
Writing::Chunk(Chunk {
|
||||||
|
buf: buf.bytes,
|
||||||
|
pos: buf.pos,
|
||||||
|
next: (h1::Encoder::length(0), Next::end())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chunk.next.1 = next;
|
||||||
|
Writing::Chunk(chunk)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => Writing::Closed,
|
||||||
|
};
|
||||||
|
match (reading, writing) {
|
||||||
|
(Reading::KeepAlive, Writing::KeepAlive) => State::Init,
|
||||||
|
(reading, Writing::Chunk(chunk)) => {
|
||||||
|
State::Http1(Http1 {
|
||||||
|
reading: reading,
|
||||||
|
writing: Writing::Chunk(chunk),
|
||||||
|
.. http1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => State::Closed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(State::Http1(mut http1), Next_::Read) => {
|
||||||
|
http1.reading = match http1.reading {
|
||||||
|
Reading::Init => Reading::Parse,
|
||||||
|
Reading::Wait(decoder) => Reading::Body(decoder),
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
|
||||||
|
http1.writing = match http1.writing {
|
||||||
|
Writing::Ready(encoder) => if encoder.is_eof() {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Writing::KeepAlive
|
||||||
|
} else {
|
||||||
|
Writing::Closed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Writing::Wait(encoder)
|
||||||
|
},
|
||||||
|
Writing::Chunk(chunk) => if chunk.is_written() {
|
||||||
|
Writing::Wait(chunk.next.0)
|
||||||
|
} else {
|
||||||
|
Writing::Chunk(chunk)
|
||||||
|
},
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
|
||||||
|
State::Http1(http1)
|
||||||
|
},
|
||||||
|
(State::Http1(mut http1), Next_::Write) => {
|
||||||
|
http1.writing = match http1.writing {
|
||||||
|
Writing::Wait(encoder) => Writing::Ready(encoder),
|
||||||
|
Writing::Init => Writing::Head,
|
||||||
|
Writing::Chunk(chunk) => if chunk.is_written() {
|
||||||
|
Writing::Ready(chunk.next.0)
|
||||||
|
} else {
|
||||||
|
Writing::Chunk(chunk)
|
||||||
|
},
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
|
||||||
|
http1.reading = match http1.reading {
|
||||||
|
Reading::Body(decoder) => if decoder.is_eof() {
|
||||||
|
if http1.keep_alive {
|
||||||
|
Reading::KeepAlive
|
||||||
|
} else {
|
||||||
|
Reading::Closed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Reading::Wait(decoder)
|
||||||
|
},
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
State::Http1(http1)
|
||||||
|
},
|
||||||
|
(State::Http1(mut http1), Next_::ReadWrite) => {
|
||||||
|
http1.reading = match http1.reading {
|
||||||
|
Reading::Init => Reading::Parse,
|
||||||
|
Reading::Wait(decoder) => Reading::Body(decoder),
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
http1.writing = match http1.writing {
|
||||||
|
Writing::Wait(encoder) => Writing::Ready(encoder),
|
||||||
|
Writing::Init => Writing::Head,
|
||||||
|
Writing::Chunk(chunk) => if chunk.is_written() {
|
||||||
|
Writing::Ready(chunk.next.0)
|
||||||
|
} else {
|
||||||
|
Writing::Chunk(chunk)
|
||||||
|
},
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
State::Http1(http1)
|
||||||
|
},
|
||||||
|
(State::Http1(mut http1), Next_::Wait) => {
|
||||||
|
http1.reading = match http1.reading {
|
||||||
|
Reading::Body(decoder) => Reading::Wait(decoder),
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
|
||||||
|
http1.writing = match http1.writing {
|
||||||
|
Writing::Ready(encoder) => Writing::Wait(encoder),
|
||||||
|
Writing::Chunk(chunk) => if chunk.is_written() {
|
||||||
|
Writing::Wait(chunk.next.0)
|
||||||
|
} else {
|
||||||
|
Writing::Chunk(chunk)
|
||||||
|
},
|
||||||
|
same => same
|
||||||
|
};
|
||||||
|
State::Http1(http1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_state = match new_state {
|
||||||
|
State::Http1(mut http1) => {
|
||||||
|
http1.timeout = timeout;
|
||||||
|
State::Http1(http1)
|
||||||
|
}
|
||||||
|
other => other
|
||||||
|
};
|
||||||
|
mem::replace(self, new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These Reading and Writing stuff should probably get moved into h1/message.rs
|
||||||
|
|
||||||
|
struct Http1<H, T> {
|
||||||
|
handler: H,
|
||||||
|
reading: Reading,
|
||||||
|
writing: Writing,
|
||||||
|
keep_alive: bool,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, T> fmt::Debug for Http1<H, T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Http1")
|
||||||
|
.field("reading", &self.reading)
|
||||||
|
.field("writing", &self.writing)
|
||||||
|
.field("keep_alive", &self.keep_alive)
|
||||||
|
.field("timeout", &self.timeout)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Reading {
|
||||||
|
Init,
|
||||||
|
Parse,
|
||||||
|
Body(h1::Decoder),
|
||||||
|
Wait(h1::Decoder),
|
||||||
|
KeepAlive,
|
||||||
|
Closed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Writing {
|
||||||
|
Init,
|
||||||
|
Head,
|
||||||
|
Chunk(Chunk) ,
|
||||||
|
Ready(h1::Encoder),
|
||||||
|
Wait(h1::Encoder),
|
||||||
|
KeepAlive,
|
||||||
|
Closed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Chunk {
|
||||||
|
buf: Cow<'static, [u8]>,
|
||||||
|
pos: usize,
|
||||||
|
next: (h1::Encoder, Next),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
fn is_written(&self) -> bool {
|
||||||
|
self.pos >= self.buf.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MessageHandler<T: Transport> {
|
||||||
|
type Message: Http1Message;
|
||||||
|
fn on_incoming(&mut self, head: http::MessageHead<<Self::Message as Http1Message>::Incoming>) -> Next;
|
||||||
|
fn on_outgoing(&mut self, head: &mut http::MessageHead<<Self::Message as Http1Message>::Outgoing>) -> Next;
|
||||||
|
fn on_decode(&mut self, &mut http::Decoder<T>) -> Next;
|
||||||
|
fn on_encode(&mut self, &mut http::Encoder<T>) -> Next;
|
||||||
|
fn on_error(&mut self, err: ::Error) -> Next;
|
||||||
|
|
||||||
|
fn on_remove(self, T) where Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Seed<'a, K: Key + 'a>(&'a K, &'a channel::Sender<Next>);
|
||||||
|
|
||||||
|
impl<'a, K: Key + 'a> Seed<'a, K> {
|
||||||
|
pub fn control(&self) -> Control {
|
||||||
|
Control {
|
||||||
|
tx: self.1.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key(&self) -> &K {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait MessageHandlerFactory<K: Key, T: Transport> {
|
||||||
|
type Output: MessageHandler<T>;
|
||||||
|
|
||||||
|
fn create(&mut self, seed: Seed<K>) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, K, H, T> MessageHandlerFactory<K, T> for F
|
||||||
|
where F: FnMut(Seed<K>) -> H,
|
||||||
|
K: Key,
|
||||||
|
H: MessageHandler<T>,
|
||||||
|
T: Transport {
|
||||||
|
type Output = H;
|
||||||
|
fn create(&mut self, seed: Seed<K>) -> H {
|
||||||
|
self(seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Key: Eq + Hash + Clone {}
|
||||||
|
impl<T: Eq + Hash + Clone> Key for T {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
/* TODO:
|
||||||
|
test when the underlying Transport of a Conn is blocked on an action that
|
||||||
|
differs from the desired interest().
|
||||||
|
|
||||||
|
Ex:
|
||||||
|
transport.blocked() == Some(Blocked::Read)
|
||||||
|
self.interest() == Reg::Write
|
||||||
|
|
||||||
|
Should call `scope.register(EventSet::read())`, not with write
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conn_register_when_transport_blocked() {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
1137
src/http/h1.rs
1137
src/http/h1.rs
File diff suppressed because it is too large
Load Diff
293
src/http/h1/decode.rs
Normal file
293
src/http/h1/decode.rs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
use std::cmp;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
use self::Kind::{Length, Chunked, Eof};
|
||||||
|
|
||||||
|
/// Decoders to handle different Transfer-Encodings.
|
||||||
|
///
|
||||||
|
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||||
|
/// include a Content-Length header.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Decoder {
|
||||||
|
kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
pub fn length(x: u64) -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Length(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chunked() -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Chunked(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eof() -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Eof(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Kind {
|
||||||
|
/// A Reader used when a Content-Length header is passed with a positive integer.
|
||||||
|
Length(u64),
|
||||||
|
/// A Reader used when Transfer-Encoding is `chunked`.
|
||||||
|
Chunked(Option<u64>),
|
||||||
|
/// A Reader used for responses that don't indicate a length or chunked.
|
||||||
|
///
|
||||||
|
/// Note: This should only used for `Response`s. It is illegal for a
|
||||||
|
/// `Request` to be made with both `Content-Length` and
|
||||||
|
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
||||||
|
///
|
||||||
|
/// > If a Transfer-Encoding header field is present in a response and
|
||||||
|
/// > the chunked transfer coding is not the final encoding, the
|
||||||
|
/// > message body length is determined by reading the connection until
|
||||||
|
/// > it is closed by the server. If a Transfer-Encoding header field
|
||||||
|
/// > is present in a request and the chunked transfer coding is not
|
||||||
|
/// > the final encoding, the message body length cannot be determined
|
||||||
|
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
||||||
|
/// > status code and then close the connection.
|
||||||
|
Eof(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
pub fn is_eof(&self) -> bool {
|
||||||
|
trace!("is_eof? {:?}", self);
|
||||||
|
match self.kind {
|
||||||
|
Length(0) |
|
||||||
|
Chunked(Some(0)) |
|
||||||
|
Eof(true) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
pub fn decode<R: Read>(&mut self, body: &mut R, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.kind {
|
||||||
|
Length(ref mut remaining) => {
|
||||||
|
trace!("Sized read, remaining={:?}", remaining);
|
||||||
|
if *remaining == 0 {
|
||||||
|
Ok(0)
|
||||||
|
} else {
|
||||||
|
let to_read = cmp::min(*remaining as usize, buf.len());
|
||||||
|
let num = try!(body.read(&mut buf[..to_read])) as u64;
|
||||||
|
trace!("Length read: {}", num);
|
||||||
|
if num > *remaining {
|
||||||
|
*remaining = 0;
|
||||||
|
} else if num == 0 {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "early eof"));
|
||||||
|
} else {
|
||||||
|
*remaining -= num;
|
||||||
|
}
|
||||||
|
Ok(num as usize)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Chunked(ref mut opt_remaining) => {
|
||||||
|
let mut rem = match *opt_remaining {
|
||||||
|
Some(ref rem) => *rem,
|
||||||
|
// None means we don't know the size of the next chunk
|
||||||
|
None => try!(read_chunk_size(body))
|
||||||
|
};
|
||||||
|
trace!("Chunked read, remaining={:?}", rem);
|
||||||
|
|
||||||
|
if rem == 0 {
|
||||||
|
*opt_remaining = Some(0);
|
||||||
|
|
||||||
|
// chunk of size 0 signals the end of the chunked stream
|
||||||
|
// if the 0 digit was missing from the stream, it would
|
||||||
|
// be an InvalidInput error instead.
|
||||||
|
trace!("end of chunked");
|
||||||
|
return Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let to_read = cmp::min(rem as usize, buf.len());
|
||||||
|
let count = try!(body.read(&mut buf[..to_read])) as u64;
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
*opt_remaining = Some(0);
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "early eof"));
|
||||||
|
}
|
||||||
|
|
||||||
|
rem -= count;
|
||||||
|
*opt_remaining = if rem > 0 {
|
||||||
|
Some(rem)
|
||||||
|
} else {
|
||||||
|
try!(eat(body, b"\r\n"));
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(count as usize)
|
||||||
|
},
|
||||||
|
Eof(ref mut is_eof) => {
|
||||||
|
match body.read(buf) {
|
||||||
|
Ok(0) => {
|
||||||
|
*is_eof = true;
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
other => other
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eat<R: Read>(rdr: &mut R, bytes: &[u8]) -> io::Result<()> {
|
||||||
|
let mut buf = [0];
|
||||||
|
for &b in bytes.iter() {
|
||||||
|
match try!(rdr.read(&mut buf)) {
|
||||||
|
1 if buf[0] == b => (),
|
||||||
|
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid characters found")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chunked chunks start with 1*HEXDIGIT, indicating the size of the chunk.
|
||||||
|
fn read_chunk_size<R: Read>(rdr: &mut R) -> io::Result<u64> {
|
||||||
|
macro_rules! byte (
|
||||||
|
($rdr:ident) => ({
|
||||||
|
let mut buf = [0];
|
||||||
|
match try!($rdr.read(&mut buf)) {
|
||||||
|
1 => buf[0],
|
||||||
|
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size line")),
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let mut size = 0u64;
|
||||||
|
let radix = 16;
|
||||||
|
let mut in_ext = false;
|
||||||
|
let mut in_chunk_size = true;
|
||||||
|
loop {
|
||||||
|
match byte!(rdr) {
|
||||||
|
b@b'0'...b'9' if in_chunk_size => {
|
||||||
|
size *= radix;
|
||||||
|
size += (b - b'0') as u64;
|
||||||
|
},
|
||||||
|
b@b'a'...b'f' if in_chunk_size => {
|
||||||
|
size *= radix;
|
||||||
|
size += (b + 10 - b'a') as u64;
|
||||||
|
},
|
||||||
|
b@b'A'...b'F' if in_chunk_size => {
|
||||||
|
size *= radix;
|
||||||
|
size += (b + 10 - b'A') as u64;
|
||||||
|
},
|
||||||
|
b'\r' => {
|
||||||
|
match byte!(rdr) {
|
||||||
|
b'\n' => break,
|
||||||
|
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size line"))
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// If we weren't in the extension yet, the ";" signals its start
|
||||||
|
b';' if !in_ext => {
|
||||||
|
in_ext = true;
|
||||||
|
in_chunk_size = false;
|
||||||
|
},
|
||||||
|
// "Linear white space" is ignored between the chunk size and the
|
||||||
|
// extension separator token (";") due to the "implied *LWS rule".
|
||||||
|
b'\t' | b' ' if !in_ext & !in_chunk_size => {},
|
||||||
|
// LWS can follow the chunk size, but no more digits can come
|
||||||
|
b'\t' | b' ' if in_chunk_size => in_chunk_size = false,
|
||||||
|
// We allow any arbitrary octet once we are in the extension, since
|
||||||
|
// they all get ignored anyway. According to the HTTP spec, valid
|
||||||
|
// extensions would have a more strict syntax:
|
||||||
|
// (token ["=" (token | quoted-string)])
|
||||||
|
// but we gain nothing by rejecting an otherwise valid chunk size.
|
||||||
|
_ext if in_ext => {
|
||||||
|
//TODO: chunk extension byte;
|
||||||
|
},
|
||||||
|
// Finally, if we aren't in the extension and we're reading any
|
||||||
|
// other octet, the chunk size line is invalid!
|
||||||
|
_ => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size line"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace!("chunk size={:?}", size);
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use super::{Decoder, read_chunk_size};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_chunk_size() {
|
||||||
|
fn read(s: &str, result: u64) {
|
||||||
|
assert_eq!(read_chunk_size(&mut s.as_bytes()).unwrap(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_err(s: &str) {
|
||||||
|
assert_eq!(read_chunk_size(&mut s.as_bytes()).unwrap_err().kind(),
|
||||||
|
io::ErrorKind::InvalidInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
read("1\r\n", 1);
|
||||||
|
read("01\r\n", 1);
|
||||||
|
read("0\r\n", 0);
|
||||||
|
read("00\r\n", 0);
|
||||||
|
read("A\r\n", 10);
|
||||||
|
read("a\r\n", 10);
|
||||||
|
read("Ff\r\n", 255);
|
||||||
|
read("Ff \r\n", 255);
|
||||||
|
// Missing LF or CRLF
|
||||||
|
read_err("F\rF");
|
||||||
|
read_err("F");
|
||||||
|
// Invalid hex digit
|
||||||
|
read_err("X\r\n");
|
||||||
|
read_err("1X\r\n");
|
||||||
|
read_err("-\r\n");
|
||||||
|
read_err("-1\r\n");
|
||||||
|
// Acceptable (if not fully valid) extensions do not influence the size
|
||||||
|
read("1;extension\r\n", 1);
|
||||||
|
read("a;ext name=value\r\n", 10);
|
||||||
|
read("1;extension;extension2\r\n", 1);
|
||||||
|
read("1;;; ;\r\n", 1);
|
||||||
|
read("2; extension...\r\n", 2);
|
||||||
|
read("3 ; extension=123\r\n", 3);
|
||||||
|
read("3 ;\r\n", 3);
|
||||||
|
read("3 ; \r\n", 3);
|
||||||
|
// Invalid extensions cause an error
|
||||||
|
read_err("1 invalid extension\r\n");
|
||||||
|
read_err("1 A\r\n");
|
||||||
|
read_err("1;no CRLF");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_sized_early_eof() {
|
||||||
|
let mut bytes = &b"foo bar"[..];
|
||||||
|
let mut decoder = Decoder::length(10);
|
||||||
|
let mut buf = [0u8; 10];
|
||||||
|
assert_eq!(decoder.decode(&mut bytes, &mut buf).unwrap(), 7);
|
||||||
|
let e = decoder.decode(&mut bytes, &mut buf).unwrap_err();
|
||||||
|
assert_eq!(e.kind(), io::ErrorKind::Other);
|
||||||
|
assert_eq!(e.description(), "early eof");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_chunked_early_eof() {
|
||||||
|
let mut bytes = &b"\
|
||||||
|
9\r\n\
|
||||||
|
foo bar\
|
||||||
|
"[..];
|
||||||
|
let mut decoder = Decoder::chunked();
|
||||||
|
let mut buf = [0u8; 10];
|
||||||
|
assert_eq!(decoder.decode(&mut bytes, &mut buf).unwrap(), 7);
|
||||||
|
let e = decoder.decode(&mut bytes, &mut buf).unwrap_err();
|
||||||
|
assert_eq!(e.kind(), io::ErrorKind::Other);
|
||||||
|
assert_eq!(e.description(), "early eof");
|
||||||
|
}
|
||||||
|
}
|
||||||
371
src/http/h1/encode.rs
Normal file
371
src/http/h1/encode.rs
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::cmp;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use http::internal::{AtomicWrite, WriteBuf};
|
||||||
|
|
||||||
|
/// Encoders to handle different Transfer-Encodings.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Encoder {
|
||||||
|
kind: Kind,
|
||||||
|
prefix: Prefix, //Option<WriteBuf<Vec<u8>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
enum Kind {
|
||||||
|
/// An Encoder for when Transfer-Encoding includes `chunked`.
|
||||||
|
Chunked(Chunked),
|
||||||
|
/// An Encoder for when Content-Length is set.
|
||||||
|
///
|
||||||
|
/// Enforces that the body is not longer than the Content-Length header.
|
||||||
|
Length(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder {
|
||||||
|
pub fn chunked() -> Encoder {
|
||||||
|
Encoder {
|
||||||
|
kind: Kind::Chunked(Chunked::Init),
|
||||||
|
prefix: Prefix(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(len: u64) -> Encoder {
|
||||||
|
Encoder {
|
||||||
|
kind: Kind::Length(len),
|
||||||
|
prefix: Prefix(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefix(&mut self, prefix: WriteBuf<Vec<u8>>) {
|
||||||
|
self.prefix.0 = Some(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_eof(&self) -> bool {
|
||||||
|
if self.prefix.0.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
Kind::Length(0) |
|
||||||
|
Kind::Chunked(Chunked::End) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end(self) -> Option<WriteBuf<Cow<'static, [u8]>>> {
|
||||||
|
let trailer = self.trailer();
|
||||||
|
let buf = self.prefix.0;
|
||||||
|
|
||||||
|
match (buf, trailer) {
|
||||||
|
(Some(mut buf), Some(trailer)) => {
|
||||||
|
buf.bytes.extend_from_slice(trailer);
|
||||||
|
Some(WriteBuf {
|
||||||
|
bytes: Cow::Owned(buf.bytes),
|
||||||
|
pos: buf.pos,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(Some(buf), None) => Some(WriteBuf {
|
||||||
|
bytes: Cow::Owned(buf.bytes),
|
||||||
|
pos: buf.pos
|
||||||
|
}),
|
||||||
|
(None, Some(trailer)) => {
|
||||||
|
Some(WriteBuf {
|
||||||
|
bytes: Cow::Borrowed(trailer),
|
||||||
|
pos: 0,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(None, None) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trailer(&self) -> Option<&'static [u8]> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Chunked(Chunked::Init) => {
|
||||||
|
Some(b"0\r\n\r\n")
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode<W: AtomicWrite>(&mut self, w: &mut W, msg: &[u8]) -> io::Result<usize> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Chunked(ref mut chunked) => {
|
||||||
|
chunked.encode(w, &mut self.prefix, msg)
|
||||||
|
},
|
||||||
|
Kind::Length(ref mut remaining) => {
|
||||||
|
let mut n = {
|
||||||
|
let max = cmp::min(*remaining as usize, msg.len());
|
||||||
|
let slice = &msg[..max];
|
||||||
|
|
||||||
|
let prefix = self.prefix.0.as_ref().map(|buf| &buf.bytes[buf.pos..]).unwrap_or(b"");
|
||||||
|
|
||||||
|
try!(w.write_atomic(&[prefix, slice]))
|
||||||
|
};
|
||||||
|
|
||||||
|
n = self.prefix.update(n);
|
||||||
|
if n == 0 {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::WouldBlock, "would block"));
|
||||||
|
}
|
||||||
|
|
||||||
|
*remaining -= n as u64;
|
||||||
|
Ok(n)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
enum Chunked {
|
||||||
|
Init,
|
||||||
|
Size(ChunkSize),
|
||||||
|
SizeCr,
|
||||||
|
SizeLf,
|
||||||
|
Body(usize),
|
||||||
|
BodyCr,
|
||||||
|
BodyLf,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunked {
|
||||||
|
fn encode<W: AtomicWrite>(&mut self, w: &mut W, prefix: &mut Prefix, msg: &[u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
Chunked::Init => {
|
||||||
|
let mut size = ChunkSize {
|
||||||
|
bytes: [0; CHUNK_SIZE_MAX_BYTES],
|
||||||
|
pos: 0,
|
||||||
|
len: 0,
|
||||||
|
};
|
||||||
|
trace!("chunked write, size = {:?}", msg.len());
|
||||||
|
write!(&mut size, "{:X}", msg.len())
|
||||||
|
.expect("CHUNK_SIZE_MAX_BYTES should fit any usize");
|
||||||
|
*self = Chunked::Size(size);
|
||||||
|
}
|
||||||
|
Chunked::End => return Ok(0),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let mut n = {
|
||||||
|
let pieces = match *self {
|
||||||
|
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
||||||
|
Chunked::Size(ref size) => [
|
||||||
|
prefix.0.as_ref().map(|buf| &buf.bytes[buf.pos..]).unwrap_or(b""),
|
||||||
|
&size.bytes[size.pos.into() .. size.len.into()],
|
||||||
|
&b"\r\n"[..],
|
||||||
|
msg,
|
||||||
|
&b"\r\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::SizeCr => [
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b"\r\n"[..],
|
||||||
|
msg,
|
||||||
|
&b"\r\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::SizeLf => [
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b"\n"[..],
|
||||||
|
msg,
|
||||||
|
&b"\r\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::Body(pos) => [
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&msg[pos..],
|
||||||
|
&b"\r\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::BodyCr => [
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b"\r\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::BodyLf => [
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b""[..],
|
||||||
|
&b"\n"[..],
|
||||||
|
],
|
||||||
|
Chunked::End => unreachable!("Chunked::End shouldn't write more")
|
||||||
|
};
|
||||||
|
try!(w.write_atomic(&pieces))
|
||||||
|
};
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
n = prefix.update(n);
|
||||||
|
}
|
||||||
|
while n > 0 {
|
||||||
|
match *self {
|
||||||
|
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
||||||
|
Chunked::Size(mut size) => {
|
||||||
|
n = size.update(n);
|
||||||
|
if size.len == 0 {
|
||||||
|
*self = Chunked::SizeCr;
|
||||||
|
} else {
|
||||||
|
*self = Chunked::Size(size);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Chunked::SizeCr => {
|
||||||
|
*self = Chunked::SizeLf;
|
||||||
|
n -= 1;
|
||||||
|
}
|
||||||
|
Chunked::SizeLf => {
|
||||||
|
*self = Chunked::Body(0);
|
||||||
|
n -= 1;
|
||||||
|
}
|
||||||
|
Chunked::Body(pos) => {
|
||||||
|
let left = msg.len() - pos;
|
||||||
|
if n >= left {
|
||||||
|
*self = Chunked::BodyCr;
|
||||||
|
n -= left;
|
||||||
|
} else {
|
||||||
|
*self = Chunked::Body(pos + n);
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chunked::BodyCr => {
|
||||||
|
*self = Chunked::BodyLf;
|
||||||
|
n -= 1;
|
||||||
|
}
|
||||||
|
Chunked::BodyLf => {
|
||||||
|
assert!(n == 1);
|
||||||
|
*self = if msg.len() == 0 {
|
||||||
|
Chunked::End
|
||||||
|
} else {
|
||||||
|
Chunked::Init
|
||||||
|
};
|
||||||
|
n = 0;
|
||||||
|
},
|
||||||
|
Chunked::End => unreachable!("Chunked::End shouldn't have any to write")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
Chunked::Init |
|
||||||
|
Chunked::End => Ok(msg.len()),
|
||||||
|
_ => Err(io::Error::new(io::ErrorKind::WouldBlock, "chunked incomplete"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
const USIZE_BYTES: usize = 4;
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
const USIZE_BYTES: usize = 8;
|
||||||
|
|
||||||
|
// each byte will become 2 hex
|
||||||
|
const CHUNK_SIZE_MAX_BYTES: usize = USIZE_BYTES * 2;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ChunkSize {
|
||||||
|
bytes: [u8; CHUNK_SIZE_MAX_BYTES],
|
||||||
|
pos: u8,
|
||||||
|
len: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkSize {
|
||||||
|
fn update(&mut self, n: usize) -> usize {
|
||||||
|
let diff = (self.len - self.pos).into();
|
||||||
|
if n >= diff {
|
||||||
|
self.pos = 0;
|
||||||
|
self.len = 0;
|
||||||
|
n - diff
|
||||||
|
} else {
|
||||||
|
self.pos += n as u8; // just verified it was a small usize
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for ChunkSize {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.debug_struct("ChunkSize")
|
||||||
|
.field("bytes", &&self.bytes[..self.len.into()])
|
||||||
|
.field("pos", &self.pos)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::cmp::PartialEq for ChunkSize {
|
||||||
|
fn eq(&self, other: &ChunkSize) -> bool {
|
||||||
|
self.len == other.len &&
|
||||||
|
self.pos == other.pos &&
|
||||||
|
(&self.bytes[..]) == (&other.bytes[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for ChunkSize {
|
||||||
|
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
||||||
|
let n = (&mut self.bytes[self.len.into() ..]).write(msg)
|
||||||
|
.expect("&mut [u8].write() cannot error");
|
||||||
|
self.len += n as u8; // safe because bytes is never bigger than 256
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Prefix(Option<WriteBuf<Vec<u8>>>);
|
||||||
|
|
||||||
|
impl Prefix {
|
||||||
|
fn update(&mut self, n: usize) -> usize {
|
||||||
|
if let Some(mut buf) = self.0.take() {
|
||||||
|
if buf.bytes.len() - buf.pos > n {
|
||||||
|
buf.pos += n;
|
||||||
|
self.0 = Some(buf);
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let nbuf = buf.bytes.len() - buf.pos;
|
||||||
|
n - nbuf
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Encoder;
|
||||||
|
use mock::{Async, Buf};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_chunked_sync() {
|
||||||
|
let mut dst = Buf::new();
|
||||||
|
let mut encoder = Encoder::chunked();
|
||||||
|
|
||||||
|
encoder.encode(&mut dst, b"foo bar").unwrap();
|
||||||
|
encoder.encode(&mut dst, b"baz quux herp").unwrap();
|
||||||
|
encoder.encode(&mut dst, b"").unwrap();
|
||||||
|
assert_eq!(&dst[..], &b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n"[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_chunked_async() {
|
||||||
|
let mut dst = Async::new(Buf::new(), 7);
|
||||||
|
let mut encoder = Encoder::chunked();
|
||||||
|
|
||||||
|
assert!(encoder.encode(&mut dst, b"foo bar").is_err());
|
||||||
|
dst.block_in(6);
|
||||||
|
assert_eq!(7, encoder.encode(&mut dst, b"foo bar").unwrap());
|
||||||
|
dst.block_in(30);
|
||||||
|
assert_eq!(13, encoder.encode(&mut dst, b"baz quux herp").unwrap());
|
||||||
|
encoder.encode(&mut dst, b"").unwrap();
|
||||||
|
assert_eq!(&dst[..], &b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n"[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_sized() {
|
||||||
|
let mut dst = Buf::new();
|
||||||
|
let mut encoder = Encoder::length(8);
|
||||||
|
encoder.encode(&mut dst, b"foo bar").unwrap();
|
||||||
|
assert_eq!(encoder.encode(&mut dst, b"baz").unwrap(), 1);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"foo barb");
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/http/h1/mod.rs
Normal file
136
src/http/h1/mod.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use tick;
|
||||||
|
use time::now_utc;
|
||||||
|
|
||||||
|
use header::{self, Headers};
|
||||||
|
use http::{self, conn};
|
||||||
|
use method::Method;
|
||||||
|
use net::{Fresh, Streaming};
|
||||||
|
use status::StatusCode;
|
||||||
|
use version::HttpVersion;
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use self::decode::Decoder;
|
||||||
|
pub use self::encode::Encoder;
|
||||||
|
|
||||||
|
pub use self::parse::parse;
|
||||||
|
|
||||||
|
mod decode;
|
||||||
|
mod encode;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn should_have_response_body(method: &Method, status: u16) -> bool {
|
||||||
|
trace!("should_have_response_body({:?}, {})", method, status);
|
||||||
|
match (method, status) {
|
||||||
|
(&Method::Head, _) |
|
||||||
|
(_, 100...199) |
|
||||||
|
(_, 204) |
|
||||||
|
(_, 304) |
|
||||||
|
(&Method::Connect, 200...299) => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
const MAX_INVALID_RESPONSE_BYTES: usize = 1024 * 128;
|
||||||
|
impl HttpMessage for Http11Message {
|
||||||
|
|
||||||
|
fn get_incoming(&mut self) -> ::Result<ResponseHead> {
|
||||||
|
unimplemented!();
|
||||||
|
/*
|
||||||
|
try!(self.flush_outgoing());
|
||||||
|
let stream = match self.stream.take() {
|
||||||
|
Some(stream) => stream,
|
||||||
|
None => {
|
||||||
|
// The message was already in the reading state...
|
||||||
|
// TODO Decide what happens in case we try to get a new incoming at that point
|
||||||
|
return Err(From::from(
|
||||||
|
io::Error::new(io::ErrorKind::Other,
|
||||||
|
"Read already in progress")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let expected_no_content = stream.previous_response_expected_no_content();
|
||||||
|
trace!("previous_response_expected_no_content = {}", expected_no_content);
|
||||||
|
|
||||||
|
let mut stream = BufReader::new(stream);
|
||||||
|
|
||||||
|
let mut invalid_bytes_read = 0;
|
||||||
|
let head;
|
||||||
|
loop {
|
||||||
|
head = match parse_response(&mut stream) {
|
||||||
|
Ok(head) => head,
|
||||||
|
Err(::Error::Version)
|
||||||
|
if expected_no_content && invalid_bytes_read < MAX_INVALID_RESPONSE_BYTES => {
|
||||||
|
trace!("expected_no_content, found content");
|
||||||
|
invalid_bytes_read += 1;
|
||||||
|
stream.consume(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.stream = Some(stream.into_inner());
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_status = head.subject;
|
||||||
|
let headers = head.headers;
|
||||||
|
|
||||||
|
let method = self.method.take().unwrap_or(Method::Get);
|
||||||
|
|
||||||
|
let is_empty = !should_have_response_body(&method, raw_status.0);
|
||||||
|
stream.get_mut().set_previous_response_expected_no_content(is_empty);
|
||||||
|
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||||
|
// 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body.
|
||||||
|
// 2. Status 2xx to a CONNECT cannot have a body.
|
||||||
|
// 3. Transfer-Encoding: chunked has a chunked body.
|
||||||
|
// 4. If multiple differing Content-Length headers or invalid, close connection.
|
||||||
|
// 5. Content-Length header has a sized body.
|
||||||
|
// 6. Not Client.
|
||||||
|
// 7. Read till EOF.
|
||||||
|
self.reader = Some(if is_empty {
|
||||||
|
SizedReader(stream, 0)
|
||||||
|
} else {
|
||||||
|
if let Some(&TransferEncoding(ref codings)) = headers.get() {
|
||||||
|
if codings.last() == Some(&Chunked) {
|
||||||
|
ChunkedReader(stream, None)
|
||||||
|
} else {
|
||||||
|
trace!("not chuncked. read till eof");
|
||||||
|
EofReader(stream)
|
||||||
|
}
|
||||||
|
} else if let Some(&ContentLength(len)) = headers.get() {
|
||||||
|
SizedReader(stream, len)
|
||||||
|
} else if headers.has::<ContentLength>() {
|
||||||
|
trace!("illegal Content-Length: {:?}", headers.get_raw("Content-Length"));
|
||||||
|
return Err(Error::Header);
|
||||||
|
} else {
|
||||||
|
trace!("neither Transfer-Encoding nor Content-Length");
|
||||||
|
EofReader(stream)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
trace!("Http11Message.reader = {:?}", self.reader);
|
||||||
|
|
||||||
|
|
||||||
|
Ok(ResponseHead {
|
||||||
|
headers: headers,
|
||||||
|
raw_status: raw_status,
|
||||||
|
version: head.version,
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
246
src/http/h1/parse.rs
Normal file
246
src/http/h1/parse.rs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use httparse;
|
||||||
|
|
||||||
|
use header::{self, Headers, ContentLength, TransferEncoding};
|
||||||
|
use http::{MessageHead, RawStatus, Http1Message, ParseResult, Next, ServerMessage, ClientMessage, Next_, RequestLine};
|
||||||
|
use http::h1::{Encoder, Decoder};
|
||||||
|
use method::Method;
|
||||||
|
use status::StatusCode;
|
||||||
|
use version::HttpVersion::{Http10, Http11};
|
||||||
|
|
||||||
|
const MAX_HEADERS: usize = 100;
|
||||||
|
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||||
|
|
||||||
|
pub fn parse<T: Http1Message<Incoming=I>, I>(buf: &[u8]) -> ParseResult<I> {
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
trace!("parse({:?})", buf);
|
||||||
|
<T as Http1Message>::parse(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl Http1Message for ServerMessage {
|
||||||
|
type Incoming = RequestLine;
|
||||||
|
type Outgoing = StatusCode;
|
||||||
|
|
||||||
|
fn initial_interest() -> Next {
|
||||||
|
Next::new(Next_::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(buf: &[u8]) -> ParseResult<RequestLine> {
|
||||||
|
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||||
|
trace!("Request.parse([Header; {}], [u8; {}])", headers.len(), buf.len());
|
||||||
|
let mut req = httparse::Request::new(&mut headers);
|
||||||
|
Ok(match try!(req.parse(buf)) {
|
||||||
|
httparse::Status::Complete(len) => {
|
||||||
|
trace!("Request.parse Complete({})", len);
|
||||||
|
Some((MessageHead {
|
||||||
|
version: if req.version.unwrap() == 1 { Http11 } else { Http10 },
|
||||||
|
subject: RequestLine(
|
||||||
|
try!(req.method.unwrap().parse()),
|
||||||
|
try!(req.path.unwrap().parse())
|
||||||
|
),
|
||||||
|
headers: try!(Headers::from_raw(req.headers))
|
||||||
|
}, len))
|
||||||
|
},
|
||||||
|
httparse::Status::Partial => None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<Decoder> {
|
||||||
|
use ::header;
|
||||||
|
if let Some(&header::ContentLength(len)) = head.headers.get() {
|
||||||
|
Ok(Decoder::length(len))
|
||||||
|
} else if head.headers.has::<header::TransferEncoding>() {
|
||||||
|
//TODO: check for Transfer-Encoding: chunked
|
||||||
|
Ok(Decoder::chunked())
|
||||||
|
} else {
|
||||||
|
Ok(Decoder::length(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||||
|
use ::header;
|
||||||
|
trace!("writing head: {:?}", head);
|
||||||
|
|
||||||
|
if !head.headers.has::<header::Date>() {
|
||||||
|
head.headers.set(header::Date(header::HttpDate(::time::now_utc())));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut is_chunked = true;
|
||||||
|
let mut body = Encoder::chunked();
|
||||||
|
if let Some(cl) = head.headers.get::<header::ContentLength>() {
|
||||||
|
body = Encoder::length(**cl);
|
||||||
|
is_chunked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_chunked {
|
||||||
|
let encodings = match head.headers.get_mut::<header::TransferEncoding>() {
|
||||||
|
Some(&mut header::TransferEncoding(ref mut encodings)) => {
|
||||||
|
if encodings.last() != Some(&header::Encoding::Chunked) {
|
||||||
|
encodings.push(header::Encoding::Chunked);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
None => true
|
||||||
|
};
|
||||||
|
|
||||||
|
if encodings {
|
||||||
|
head.headers.set(header::TransferEncoding(vec![header::Encoding::Chunked]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
|
||||||
|
dst.reserve(init_cap);
|
||||||
|
debug!("writing {:#?}", head.headers);
|
||||||
|
let _ = write!(dst, "{} {}\r\n{}\r\n", head.version, head.subject, head.headers);
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Http1Message for ClientMessage {
|
||||||
|
type Incoming = RawStatus;
|
||||||
|
type Outgoing = RequestLine;
|
||||||
|
|
||||||
|
|
||||||
|
fn initial_interest() -> Next {
|
||||||
|
Next::new(Next_::Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(buf: &[u8]) -> ParseResult<RawStatus> {
|
||||||
|
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||||
|
trace!("Response.parse([Header; {}], [u8; {}])", headers.len(), buf.len());
|
||||||
|
let mut res = httparse::Response::new(&mut headers);
|
||||||
|
Ok(match try!(res.parse(buf)) {
|
||||||
|
httparse::Status::Complete(len) => {
|
||||||
|
trace!("Response.try_parse Complete({})", len);
|
||||||
|
let code = res.code.unwrap();
|
||||||
|
let reason = match StatusCode::from_u16(code).canonical_reason() {
|
||||||
|
Some(reason) if reason == res.reason.unwrap() => Cow::Borrowed(reason),
|
||||||
|
_ => Cow::Owned(res.reason.unwrap().to_owned())
|
||||||
|
};
|
||||||
|
Some((MessageHead {
|
||||||
|
version: if res.version.unwrap() == 1 { Http11 } else { Http10 },
|
||||||
|
subject: RawStatus(code, reason),
|
||||||
|
headers: try!(Headers::from_raw(res.headers))
|
||||||
|
}, len))
|
||||||
|
},
|
||||||
|
httparse::Status::Partial => None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoder(inc: &MessageHead<Self::Incoming>) -> ::Result<Decoder> {
|
||||||
|
use ::header;
|
||||||
|
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||||
|
// 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body.
|
||||||
|
// 2. Status 2xx to a CONNECT cannot have a body.
|
||||||
|
//
|
||||||
|
// First two steps taken care of before this method.
|
||||||
|
//
|
||||||
|
// 3. Transfer-Encoding: chunked has a chunked body.
|
||||||
|
// 4. If multiple differing Content-Length headers or invalid, close connection.
|
||||||
|
// 5. Content-Length header has a sized body.
|
||||||
|
// 6. Not Client.
|
||||||
|
// 7. Read till EOF.
|
||||||
|
if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() {
|
||||||
|
if codings.last() == Some(&header::Encoding::Chunked) {
|
||||||
|
Ok(Decoder::chunked())
|
||||||
|
} else {
|
||||||
|
trace!("not chuncked. read till eof");
|
||||||
|
Ok(Decoder::eof())
|
||||||
|
}
|
||||||
|
} else if let Some(&header::ContentLength(len)) = inc.headers.get() {
|
||||||
|
Ok(Decoder::length(len))
|
||||||
|
} else if inc.headers.has::<header::ContentLength>() {
|
||||||
|
trace!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length"));
|
||||||
|
Err(::Error::Header)
|
||||||
|
} else {
|
||||||
|
trace!("neither Transfer-Encoding nor Content-Length");
|
||||||
|
Ok(Decoder::eof())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||||
|
trace!("writing head: {:?}", head);
|
||||||
|
|
||||||
|
|
||||||
|
let mut body = Encoder::length(0);
|
||||||
|
let expects_no_body = match head.subject.0 {
|
||||||
|
Method::Head | Method::Get | Method::Connect => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
let mut chunked = false;
|
||||||
|
|
||||||
|
if let Some(con_len) = head.headers.get::<ContentLength>() {
|
||||||
|
body = Encoder::length(**con_len);
|
||||||
|
} else {
|
||||||
|
chunked = !expects_no_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunked {
|
||||||
|
body = Encoder::chunked();
|
||||||
|
let encodings = match head.headers.get_mut::<TransferEncoding>() {
|
||||||
|
Some(encodings) => {
|
||||||
|
//TODO: check if Chunked already exists
|
||||||
|
encodings.push(header::Encoding::Chunked);
|
||||||
|
true
|
||||||
|
},
|
||||||
|
None => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !encodings {
|
||||||
|
head.headers.set(TransferEncoding(vec![header::Encoding::Chunked]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
|
||||||
|
dst.reserve(init_cap);
|
||||||
|
debug!("writing {:#?}", head.headers);
|
||||||
|
let _ = write!(dst, "{} {}\r\n{}\r\n", head.subject, head.version, head.headers);
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use http;
|
||||||
|
use super::{parse};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_request() {
|
||||||
|
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||||
|
parse::<http::ServerMessage, _>(raw).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_raw_status() {
|
||||||
|
let raw = b"HTTP/1.1 200 OK\r\n\r\n";
|
||||||
|
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
||||||
|
assert_eq!(res.subject.1, "OK");
|
||||||
|
|
||||||
|
let raw = b"HTTP/1.1 200 Howdy\r\n\r\n";
|
||||||
|
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
||||||
|
assert_eq!(res.subject.1, "Howdy");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_parse_incoming(b: &mut Bencher) {
|
||||||
|
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||||
|
b.iter(|| {
|
||||||
|
parse::<http::ServerMessage, _>(raw).unwrap()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
//! Defines the `HttpMessage` trait that serves to encapsulate the operations of a single
|
|
||||||
//! request-response cycle on any HTTP connection.
|
|
||||||
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use typeable::Typeable;
|
|
||||||
|
|
||||||
use header::Headers;
|
|
||||||
use http::RawStatus;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use method;
|
|
||||||
use version;
|
|
||||||
use traitobject;
|
|
||||||
|
|
||||||
/// The trait provides an API for creating new `HttpMessage`s depending on the underlying HTTP
|
|
||||||
/// protocol.
|
|
||||||
pub trait Protocol {
|
|
||||||
/// Creates a fresh `HttpMessage` bound to the given host, based on the given protocol scheme.
|
|
||||||
fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a request.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct RequestHead {
|
|
||||||
/// The headers of the request
|
|
||||||
pub headers: Headers,
|
|
||||||
/// The method of the request
|
|
||||||
pub method: method::Method,
|
|
||||||
/// The URL of the request
|
|
||||||
pub url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a response.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ResponseHead {
|
|
||||||
/// The headers of the reponse
|
|
||||||
pub headers: Headers,
|
|
||||||
/// The raw status line of the response
|
|
||||||
pub raw_status: RawStatus,
|
|
||||||
/// The HTTP/2 version which generated the response
|
|
||||||
pub version: version::HttpVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The trait provides an API for sending an receiving HTTP messages.
|
|
||||||
pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug {
|
|
||||||
/// Initiates a new outgoing request.
|
|
||||||
///
|
|
||||||
/// Only the request's head is provided (in terms of the `RequestHead` struct).
|
|
||||||
///
|
|
||||||
/// After this, the `HttpMessage` instance can be used as an `io::Write` in order to write the
|
|
||||||
/// body of the request.
|
|
||||||
fn set_outgoing(&mut self, head: RequestHead) -> ::Result<RequestHead>;
|
|
||||||
/// Obtains the incoming response and returns its head (i.e. the `ResponseHead` struct)
|
|
||||||
///
|
|
||||||
/// After this, the `HttpMessage` instance can be used as an `io::Read` in order to read out
|
|
||||||
/// the response body.
|
|
||||||
fn get_incoming(&mut self) -> ::Result<ResponseHead>;
|
|
||||||
/// Set the read timeout duration for this message.
|
|
||||||
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()>;
|
|
||||||
/// Set the write timeout duration for this message.
|
|
||||||
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()>;
|
|
||||||
/// Closes the underlying HTTP connection.
|
|
||||||
fn close_connection(&mut self) -> ::Result<()>;
|
|
||||||
/// Returns whether the incoming message has a body.
|
|
||||||
fn has_body(&self) -> bool;
|
|
||||||
/// Called when the Client wishes to use a Proxy.
|
|
||||||
fn set_proxied(&mut self, val: bool) {
|
|
||||||
// default implementation so as to not be a breaking change.
|
|
||||||
warn!("default set_proxied({:?})", val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpMessage {
|
|
||||||
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
|
|
||||||
mem::transmute(traitobject::data(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T {
|
|
||||||
mem::transmute(traitobject::data_mut(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn downcast_unchecked<T: 'static>(self: Box<HttpMessage>) -> Box<T> {
|
|
||||||
let raw: *mut HttpMessage = mem::transmute(self);
|
|
||||||
mem::transmute(traitobject::data_mut(raw))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpMessage {
|
|
||||||
/// Is the underlying type in this trait object a T?
|
|
||||||
#[inline]
|
|
||||||
pub fn is<T: Any>(&self) -> bool {
|
|
||||||
(*self).get_type() == TypeId::of::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the underlying type is T, get a reference to the contained data.
|
|
||||||
#[inline]
|
|
||||||
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
|
||||||
if self.is::<T>() {
|
|
||||||
Some(unsafe { self.downcast_ref_unchecked() })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the underlying type is T, get a mutable reference to the contained
|
|
||||||
/// data.
|
|
||||||
#[inline]
|
|
||||||
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
|
|
||||||
if self.is::<T>() {
|
|
||||||
Some(unsafe { self.downcast_mut_unchecked() })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the underlying type is T, extract it.
|
|
||||||
#[inline]
|
|
||||||
pub fn downcast<T: Any>(self: Box<HttpMessage>)
|
|
||||||
-> Result<Box<T>, Box<HttpMessage>> {
|
|
||||||
if self.is::<T>() {
|
|
||||||
Ok(unsafe { self.downcast_unchecked() })
|
|
||||||
} else {
|
|
||||||
Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
331
src/http/mod.rs
331
src/http/mod.rs
@@ -1,25 +1,196 @@
|
|||||||
//! Pieces pertaining to the HTTP message protocol.
|
//! Pieces pertaining to the HTTP message protocol.
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use header::Connection;
|
use header::Connection;
|
||||||
use header::ConnectionOption::{KeepAlive, Close};
|
use header::ConnectionOption::{KeepAlive, Close};
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
|
use method::Method;
|
||||||
|
use net::Transport;
|
||||||
|
use status::StatusCode;
|
||||||
|
use uri::RequestUri;
|
||||||
use version::HttpVersion;
|
use version::HttpVersion;
|
||||||
use version::HttpVersion::{Http10, Http11};
|
use version::HttpVersion::{Http10, Http11};
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
#[cfg(feature = "serde-serialization")]
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
pub use self::message::{HttpMessage, RequestHead, ResponseHead, Protocol};
|
pub use self::conn::{Conn, MessageHandler, MessageHandlerFactory, Seed, Key};
|
||||||
|
|
||||||
pub mod h1;
|
mod buffer;
|
||||||
pub mod h2;
|
pub mod channel;
|
||||||
pub mod message;
|
mod conn;
|
||||||
|
mod h1;
|
||||||
|
//mod h2;
|
||||||
|
|
||||||
|
/// Wraps a `Transport` to provide HTTP decoding when reading.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Decoder<'a, T: Read + 'a>(DecoderImpl<'a, T>);
|
||||||
|
|
||||||
|
/// Wraps a `Transport` to provide HTTP encoding when writing.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Encoder<'a, T: Transport + 'a>(EncoderImpl<'a, T>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DecoderImpl<'a, T: Read + 'a> {
|
||||||
|
H1(&'a mut h1::Decoder, Trans<'a, T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Trans<'a, T: Read + 'a> {
|
||||||
|
Port(&'a mut T),
|
||||||
|
Buf(self::buffer::BufReader<'a, T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Read + 'a> Read for Trans<'a, T> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
Trans::Port(ref mut t) => t.read(buf),
|
||||||
|
Trans::Buf(ref mut b) => b.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum EncoderImpl<'a, T: Transport + 'a> {
|
||||||
|
H1(&'a mut h1::Encoder, &'a mut T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Read> Decoder<'a, T> {
|
||||||
|
fn h1(decoder: &'a mut h1::Decoder, transport: Trans<'a, T>) -> Decoder<'a, T> {
|
||||||
|
Decoder(DecoderImpl::H1(decoder, transport))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Transport> Encoder<'a, T> {
|
||||||
|
fn h1(encoder: &'a mut h1::Encoder, transport: &'a mut T) -> Encoder<'a, T> {
|
||||||
|
Encoder(EncoderImpl::H1(encoder, transport))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Read> Read for Decoder<'a, T> {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.0 {
|
||||||
|
DecoderImpl::H1(ref mut decoder, ref mut transport) => {
|
||||||
|
decoder.decode(transport, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Transport> Write for Encoder<'a, T> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
if data.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
match self.0 {
|
||||||
|
EncoderImpl::H1(ref mut encoder, ref mut transport) => {
|
||||||
|
encoder.encode(*transport, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match self.0 {
|
||||||
|
EncoderImpl::H1(_, ref mut transport) => {
|
||||||
|
transport.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Because privacy rules. Reasons.
|
||||||
|
/// https://github.com/rust-lang/rust/issues/30905
|
||||||
|
mod internal {
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WriteBuf<T: AsRef<[u8]>> {
|
||||||
|
pub bytes: T,
|
||||||
|
pub pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AtomicWrite {
|
||||||
|
fn write_atomic(&mut self, data: &[&[u8]]) -> io::Result<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
impl<T: Write + ::vecio::Writev> AtomicWrite for T {
|
||||||
|
|
||||||
|
fn write_atomic(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
||||||
|
self.writev(bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
impl<T: Write> AtomicWrite for T {
|
||||||
|
fn write_atomic(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
||||||
|
let vec = bufs.concat();
|
||||||
|
self.write(&vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An Incoming Message head. Includes request/status line, and headers.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct MessageHead<S> {
|
||||||
|
/// HTTP version of the message.
|
||||||
|
pub version: HttpVersion,
|
||||||
|
/// Subject (request line or status line) of Incoming message.
|
||||||
|
pub subject: S,
|
||||||
|
/// Headers of the Incoming message.
|
||||||
|
pub headers: Headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An incoming request message.
|
||||||
|
pub type RequestHead = MessageHead<RequestLine>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct RequestLine(pub Method, pub RequestUri);
|
||||||
|
|
||||||
|
impl fmt::Display for RequestLine {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An incoming response message.
|
||||||
|
pub type ResponseHead = MessageHead<RawStatus>;
|
||||||
|
|
||||||
|
impl<S> MessageHead<S> {
|
||||||
|
pub fn should_keep_alive(&self) -> bool {
|
||||||
|
should_keep_alive(self.version, &self.headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The raw status code and reason-phrase.
|
/// The raw status code and reason-phrase.
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
||||||
|
|
||||||
|
impl fmt::Display for RawStatus {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StatusCode> for RawStatus {
|
||||||
|
fn from(status: StatusCode) -> RawStatus {
|
||||||
|
RawStatus(status.to_u16(), Cow::Borrowed(status.canonical_reason().unwrap_or("")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RawStatus {
|
||||||
|
fn default() -> RawStatus {
|
||||||
|
RawStatus(200, Cow::Borrowed("OK"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
#[cfg(feature = "serde-serialization")]
|
||||||
impl Serialize for RawStatus {
|
impl Serialize for RawStatus {
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
||||||
@@ -46,6 +217,158 @@ pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool {
|
|||||||
_ => true
|
_ => true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>;
|
||||||
|
|
||||||
|
pub fn parse<T: Http1Message<Incoming=I>, I>(rdr: &[u8]) -> ParseResult<I> {
|
||||||
|
h1::parse::<T, I>(rdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These 2 enums are not actually dead_code. They are used in the server and
|
||||||
|
// and client modules, respectively. However, their being used as associated
|
||||||
|
// types doesn't mark them as used, so the dead_code linter complains.
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ServerMessage {}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ClientMessage {}
|
||||||
|
|
||||||
|
pub trait Http1Message {
|
||||||
|
type Incoming;
|
||||||
|
type Outgoing: Default;
|
||||||
|
//TODO: replace with associated const when stable
|
||||||
|
fn initial_interest() -> Next;
|
||||||
|
fn parse(bytes: &[u8]) -> ParseResult<Self::Incoming>;
|
||||||
|
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<h1::Decoder>;
|
||||||
|
fn encode(head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> h1::Encoder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to signal desired events when working with asynchronous IO.
|
||||||
|
#[must_use]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Next {
|
||||||
|
interest: Next_,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Next {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
try!(write!(f, "Next::{:?}", &self.interest));
|
||||||
|
match self.timeout {
|
||||||
|
Some(ref d) => write!(f, "({:?})", d),
|
||||||
|
None => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Next_ {
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
ReadWrite,
|
||||||
|
Wait,
|
||||||
|
End,
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Reg {
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
ReadWrite,
|
||||||
|
Wait,
|
||||||
|
Remove
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A notifier to wakeup a socket after having used `Next::wait()`
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Control {
|
||||||
|
tx: self::channel::Sender<Next>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Control {
|
||||||
|
/// Wakeup a waiting socket to listen for a certain event.
|
||||||
|
pub fn ready(&self, next: Next) -> Result<(), ControlError> {
|
||||||
|
//TODO: assert!( next.interest != Next_::Wait ) ?
|
||||||
|
self.tx.send(next).map_err(|_| ControlError(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error occured trying to tell a Control it is ready.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ControlError(());
|
||||||
|
|
||||||
|
impl ::std::error::Error for ControlError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Cannot wakeup event loop: loop is closed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ControlError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str(::std::error::Error::description(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Next {
|
||||||
|
fn new(interest: Next_) -> Next {
|
||||||
|
Next {
|
||||||
|
interest: interest,
|
||||||
|
timeout: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interest(&self) -> Reg {
|
||||||
|
match self.interest {
|
||||||
|
Next_::Read => Reg::Read,
|
||||||
|
Next_::Write => Reg::Write,
|
||||||
|
Next_::ReadWrite => Reg::ReadWrite,
|
||||||
|
Next_::Wait => Reg::Wait,
|
||||||
|
Next_::End => Reg::Remove,
|
||||||
|
Next_::Remove => Reg::Remove,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to read from the transport.
|
||||||
|
pub fn read() -> Next {
|
||||||
|
Next::new(Next_::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to write to the transport.
|
||||||
|
pub fn write() -> Next {
|
||||||
|
Next::new(Next_::Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to read and write to the transport.
|
||||||
|
pub fn read_and_write() -> Next {
|
||||||
|
Next::new(Next_::ReadWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to end the current HTTP message.
|
||||||
|
pub fn end() -> Next {
|
||||||
|
Next::new(Next_::End)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to abruptly remove the current transport from the
|
||||||
|
/// event loop.
|
||||||
|
pub fn remove() -> Next {
|
||||||
|
Next::new(Next_::Remove)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals the desire to wait until some future time before acting again.
|
||||||
|
pub fn wait() -> Next {
|
||||||
|
Next::new(Next_::Wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals a maximum duration to be waited for the desired event.
|
||||||
|
pub fn timeout(mut self, dur: Duration) -> Next {
|
||||||
|
self.timeout = Some(dur);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_keep_alive() {
|
fn test_should_keep_alive() {
|
||||||
|
|||||||
168
src/lib.rs
168
src/lib.rs
@@ -1,6 +1,7 @@
|
|||||||
#![doc(html_root_url = "https://hyperium.github.io/hyper/")]
|
#![doc(html_root_url = "https://hyperium.github.io/hyper/")]
|
||||||
#![cfg_attr(test, deny(missing_docs))]
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(test, deny(warnings))]
|
#![deny(warnings)]
|
||||||
|
#![deny(missing_debug_implementations)]
|
||||||
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
||||||
|
|
||||||
//! # Hyper
|
//! # Hyper
|
||||||
@@ -9,125 +10,9 @@
|
|||||||
//! is a low-level typesafe abstraction over raw HTTP, providing an elegant
|
//! is a low-level typesafe abstraction over raw HTTP, providing an elegant
|
||||||
//! layer over "stringly-typed" HTTP.
|
//! layer over "stringly-typed" HTTP.
|
||||||
//!
|
//!
|
||||||
//! Hyper offers both a [Client](client/index.html) and a
|
//! Hyper provides both a [Client](client/index.html) and a
|
||||||
//! [Server](server/index.html) which can be used to drive complex web
|
//! [Server](server/index.html), along with a
|
||||||
//! applications written entirely in Rust.
|
//! [typed Headers system](header/index.html).
|
||||||
//!
|
|
||||||
//! ## Internal Design
|
|
||||||
//!
|
|
||||||
//! Hyper is designed as a relatively low-level wrapper over raw HTTP. It should
|
|
||||||
//! allow the implementation of higher-level abstractions with as little pain as
|
|
||||||
//! possible, and should not irrevocably hide any information from its users.
|
|
||||||
//!
|
|
||||||
//! ### Common Functionality
|
|
||||||
//!
|
|
||||||
//! Functionality and code shared between the Server and Client implementations
|
|
||||||
//! can be found in `src` directly - this includes `NetworkStream`s, `Method`s,
|
|
||||||
//! `StatusCode`, and so on.
|
|
||||||
//!
|
|
||||||
//! #### Methods
|
|
||||||
//!
|
|
||||||
//! Methods are represented as a single `enum` to remain as simple as possible.
|
|
||||||
//! Extension Methods are represented as raw `String`s. A method's safety and
|
|
||||||
//! idempotence can be accessed using the `safe` and `idempotent` methods.
|
|
||||||
//!
|
|
||||||
//! #### StatusCode
|
|
||||||
//!
|
|
||||||
//! Status codes are also represented as a single, exhaustive, `enum`. This
|
|
||||||
//! representation is efficient, typesafe, and ergonomic as it allows the use of
|
|
||||||
//! `match` to disambiguate known status codes.
|
|
||||||
//!
|
|
||||||
//! #### Headers
|
|
||||||
//!
|
|
||||||
//! Hyper's [header](header/index.html) representation is likely the most
|
|
||||||
//! complex API exposed by Hyper.
|
|
||||||
//!
|
|
||||||
//! Hyper's headers are an abstraction over an internal `HashMap` and provides a
|
|
||||||
//! typesafe API for interacting with headers that does not rely on the use of
|
|
||||||
//! "string-typing."
|
|
||||||
//!
|
|
||||||
//! Each HTTP header in Hyper has an associated type and implementation of the
|
|
||||||
//! `Header` trait, which defines an HTTP headers name as a string, how to parse
|
|
||||||
//! that header, and how to format that header.
|
|
||||||
//!
|
|
||||||
//! Headers are then parsed from the string representation lazily when the typed
|
|
||||||
//! representation of a header is requested and formatted back into their string
|
|
||||||
//! representation when headers are written back to the client.
|
|
||||||
//!
|
|
||||||
//! #### NetworkStream and NetworkAcceptor
|
|
||||||
//!
|
|
||||||
//! These are found in `src/net.rs` and define the interface that acceptors and
|
|
||||||
//! streams must fulfill for them to be used within Hyper. They are by and large
|
|
||||||
//! internal tools and you should only need to mess around with them if you want to
|
|
||||||
//! mock or replace `TcpStream` and `TcpAcceptor`.
|
|
||||||
//!
|
|
||||||
//! ### Server
|
|
||||||
//!
|
|
||||||
//! Server-specific functionality, such as `Request` and `Response`
|
|
||||||
//! representations, are found in in `src/server`.
|
|
||||||
//!
|
|
||||||
//! #### Handler + Server
|
|
||||||
//!
|
|
||||||
//! A `Handler` in Hyper accepts a `Request` and `Response`. This is where
|
|
||||||
//! user-code can handle each connection. The server accepts connections in a
|
|
||||||
//! task pool with a customizable number of threads, and passes the Request /
|
|
||||||
//! Response to the handler.
|
|
||||||
//!
|
|
||||||
//! #### Request
|
|
||||||
//!
|
|
||||||
//! An incoming HTTP Request is represented as a struct containing
|
|
||||||
//! a `Reader` over a `NetworkStream`, which represents the body, headers, a remote
|
|
||||||
//! address, an HTTP version, and a `Method` - relatively standard stuff.
|
|
||||||
//!
|
|
||||||
//! `Request` implements `Reader` itself, meaning that you can ergonomically get
|
|
||||||
//! the body out of a `Request` using standard `Reader` methods and helpers.
|
|
||||||
//!
|
|
||||||
//! #### Response
|
|
||||||
//!
|
|
||||||
//! An outgoing HTTP Response is also represented as a struct containing a `Writer`
|
|
||||||
//! over a `NetworkStream` which represents the Response body in addition to
|
|
||||||
//! standard items such as the `StatusCode` and HTTP version. `Response`'s `Writer`
|
|
||||||
//! implementation provides a streaming interface for sending data over to the
|
|
||||||
//! client.
|
|
||||||
//!
|
|
||||||
//! One of the traditional problems with representing outgoing HTTP Responses is
|
|
||||||
//! tracking the write-status of the Response - have we written the status-line,
|
|
||||||
//! the headers, the body, etc.? Hyper tracks this information statically using the
|
|
||||||
//! type system and prevents you, using the type system, from writing headers after
|
|
||||||
//! you have started writing to the body or vice versa.
|
|
||||||
//!
|
|
||||||
//! Hyper does this through a phantom type parameter in the definition of Response,
|
|
||||||
//! which tracks whether you are allowed to write to the headers or the body. This
|
|
||||||
//! phantom type can have two values `Fresh` or `Streaming`, with `Fresh`
|
|
||||||
//! indicating that you can write the headers and `Streaming` indicating that you
|
|
||||||
//! may write to the body, but not the headers.
|
|
||||||
//!
|
|
||||||
//! ### Client
|
|
||||||
//!
|
|
||||||
//! Client-specific functionality, such as `Request` and `Response`
|
|
||||||
//! representations, are found in `src/client`.
|
|
||||||
//!
|
|
||||||
//! #### Request
|
|
||||||
//!
|
|
||||||
//! An outgoing HTTP Request is represented as a struct containing a `Writer` over
|
|
||||||
//! a `NetworkStream` which represents the Request body in addition to the standard
|
|
||||||
//! information such as headers and the request method.
|
|
||||||
//!
|
|
||||||
//! Outgoing Requests track their write-status in almost exactly the same way as
|
|
||||||
//! outgoing HTTP Responses do on the Server, so we will defer to the explanation
|
|
||||||
//! in the documentation for server Response.
|
|
||||||
//!
|
|
||||||
//! Requests expose an efficient streaming interface instead of a builder pattern,
|
|
||||||
//! but they also provide the needed interface for creating a builder pattern over
|
|
||||||
//! the API exposed by core Hyper.
|
|
||||||
//!
|
|
||||||
//! #### Response
|
|
||||||
//!
|
|
||||||
//! Incoming HTTP Responses are represented as a struct containing a `Reader` over
|
|
||||||
//! a `NetworkStream` and contain headers, a status, and an http version. They
|
|
||||||
//! implement `Reader` and can be read to get the data out of a `Response`.
|
|
||||||
//!
|
|
||||||
|
|
||||||
extern crate rustc_serialize as serialize;
|
extern crate rustc_serialize as serialize;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
#[macro_use] extern crate url;
|
#[macro_use] extern crate url;
|
||||||
@@ -142,10 +27,11 @@ extern crate serde;
|
|||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
extern crate unicase;
|
extern crate unicase;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
extern crate num_cpus;
|
extern crate rotor;
|
||||||
|
extern crate spmc;
|
||||||
extern crate traitobject;
|
extern crate traitobject;
|
||||||
extern crate typeable;
|
extern crate typeable;
|
||||||
extern crate solicit;
|
extern crate vecio;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate language_tags;
|
extern crate language_tags;
|
||||||
@@ -163,35 +49,31 @@ extern crate test;
|
|||||||
pub use url::Url;
|
pub use url::Url;
|
||||||
pub use client::Client;
|
pub use client::Client;
|
||||||
pub use error::{Result, Error};
|
pub use error::{Result, Error};
|
||||||
pub use method::Method::{Get, Head, Post, Delete};
|
pub use http::{Next, Encoder, Decoder, Control};
|
||||||
pub use status::StatusCode::{Ok, BadRequest, NotFound};
|
pub use header::Headers;
|
||||||
|
pub use method::Method::{self, Get, Head, Post, Delete};
|
||||||
|
pub use status::StatusCode::{self, Ok, BadRequest, NotFound};
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
|
pub use uri::RequestUri;
|
||||||
|
pub use version::HttpVersion;
|
||||||
pub use language_tags::LanguageTag;
|
pub use language_tags::LanguageTag;
|
||||||
|
|
||||||
macro_rules! todo(
|
macro_rules! rotor_try {
|
||||||
($($arg:tt)*) => (if cfg!(not(ndebug)) {
|
($e:expr) => ({
|
||||||
trace!("TODO: {:?}", format_args!($($arg)*))
|
match $e {
|
||||||
})
|
Ok(v) => v,
|
||||||
);
|
Err(e) => return ::rotor::Response::error(e.into())
|
||||||
|
}
|
||||||
macro_rules! inspect(
|
});
|
||||||
($name:expr, $value:expr) => ({
|
}
|
||||||
let v = $value;
|
|
||||||
trace!("inspect: {:?} = {:?}", $name, v);
|
|
||||||
v
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
|
||||||
mod mock;
|
mod mock;
|
||||||
#[doc(hidden)]
|
|
||||||
pub mod buffer;
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod method;
|
pub mod method;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod http;
|
mod http;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
@@ -203,6 +85,7 @@ pub mod mime {
|
|||||||
pub use mime_crate::*;
|
pub use mime_crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[allow(unconditional_recursion)]
|
#[allow(unconditional_recursion)]
|
||||||
fn _assert_send<T: Send>() {
|
fn _assert_send<T: Send>() {
|
||||||
_assert_send::<Client>();
|
_assert_send::<Client>();
|
||||||
@@ -216,3 +99,4 @@ fn _assert_sync<T: Sync>() {
|
|||||||
_assert_sync::<Client>();
|
_assert_sync::<Client>();
|
||||||
_assert_sync::<error::Error>();
|
_assert_sync::<error::Error>();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ impl fmt::Display for Method {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Method {
|
||||||
|
fn default() -> Method {
|
||||||
|
Method::Get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
#[cfg(feature = "serde-serialization")]
|
||||||
impl Serialize for Method {
|
impl Serialize for Method {
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
||||||
|
|||||||
384
src/mock.rs
384
src/mock.rs
@@ -1,87 +1,37 @@
|
|||||||
use std::ascii::AsciiExt;
|
use std::cmp;
|
||||||
use std::io::{self, Read, Write, Cursor};
|
use std::io::{self, Read, Write};
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::net::{SocketAddr, Shutdown};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
use solicit::http::HttpScheme;
|
#[derive(Debug)]
|
||||||
use solicit::http::transport::TransportStream;
|
pub struct Buf {
|
||||||
use solicit::http::frame::{SettingsFrame, Frame};
|
vec: Vec<u8>
|
||||||
use solicit::http::connection::{HttpConnection, EndStream, DataChunk};
|
|
||||||
|
|
||||||
use header::Headers;
|
|
||||||
use net::{NetworkStream, NetworkConnector, SslClient};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MockStream {
|
|
||||||
pub read: Cursor<Vec<u8>>,
|
|
||||||
next_reads: Vec<Vec<u8>>,
|
|
||||||
pub write: Vec<u8>,
|
|
||||||
pub is_closed: bool,
|
|
||||||
pub error_on_write: bool,
|
|
||||||
pub error_on_read: bool,
|
|
||||||
pub read_timeout: Cell<Option<Duration>>,
|
|
||||||
pub write_timeout: Cell<Option<Duration>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for MockStream {
|
impl Buf {
|
||||||
fn eq(&self, other: &MockStream) -> bool {
|
pub fn new() -> Buf {
|
||||||
self.read.get_ref() == other.read.get_ref() && self.write == other.write
|
Buf {
|
||||||
}
|
vec: vec![]
|
||||||
}
|
|
||||||
|
|
||||||
impl MockStream {
|
|
||||||
pub fn new() -> MockStream {
|
|
||||||
MockStream::with_input(b"")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_input(input: &[u8]) -> MockStream {
|
|
||||||
MockStream::with_responses(vec![input])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_responses(mut responses: Vec<&[u8]>) -> MockStream {
|
|
||||||
MockStream {
|
|
||||||
read: Cursor::new(responses.remove(0).to_vec()),
|
|
||||||
next_reads: responses.into_iter().map(|arr| arr.to_vec()).collect(),
|
|
||||||
write: vec![],
|
|
||||||
is_closed: false,
|
|
||||||
error_on_write: false,
|
|
||||||
error_on_read: false,
|
|
||||||
read_timeout: Cell::new(None),
|
|
||||||
write_timeout: Cell::new(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for MockStream {
|
impl ::std::ops::Deref for Buf {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
type Target = [u8];
|
||||||
if self.error_on_read {
|
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "mock error"))
|
fn deref(&self) -> &[u8] {
|
||||||
} else {
|
&self.vec
|
||||||
match self.read.read(buf) {
|
|
||||||
Ok(n) => {
|
|
||||||
if self.read.position() as usize == self.read.get_ref().len() {
|
|
||||||
if self.next_reads.len() > 0 {
|
|
||||||
self.read = Cursor::new(self.next_reads.remove(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(n)
|
|
||||||
},
|
|
||||||
r => r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for MockStream {
|
impl<S: AsRef<[u8]>> PartialEq<S> for Buf {
|
||||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
fn eq(&self, other: &S) -> bool {
|
||||||
if self.error_on_write {
|
self.vec == other.as_ref()
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "mock error"))
|
}
|
||||||
} else {
|
}
|
||||||
Write::write(&mut self.write, msg)
|
|
||||||
}
|
impl Write for Buf {
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
self.vec.extend(data);
|
||||||
|
Ok(data.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
@@ -89,239 +39,89 @@ impl Write for MockStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkStream for MockStream {
|
impl Read for Buf {
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
Ok("127.0.0.1:1337".parse().unwrap())
|
(&*self.vec).read(buf)
|
||||||
}
|
|
||||||
|
|
||||||
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
self.read_timeout.set(dur);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
self.write_timeout.set(dur);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self, _how: Shutdown) -> io::Result<()> {
|
|
||||||
self.is_closed = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around a `MockStream` that allows one to clone it and keep an independent copy to the
|
impl ::vecio::Writev for Buf {
|
||||||
/// same underlying stream.
|
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
||||||
#[derive(Clone)]
|
let cap = bufs.iter().map(|buf| buf.len()).fold(0, |total, next| total + next);
|
||||||
pub struct CloneableMockStream {
|
let mut vec = Vec::with_capacity(cap);
|
||||||
pub inner: Arc<Mutex<MockStream>>,
|
for &buf in bufs {
|
||||||
|
vec.extend(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write(&vec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for CloneableMockStream {
|
#[derive(Debug)]
|
||||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
pub struct Async<T> {
|
||||||
self.inner.lock().unwrap().write(msg)
|
inner: T,
|
||||||
|
bytes_until_block: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Async<T> {
|
||||||
|
pub fn new(inner: T, bytes: usize) -> Async<T> {
|
||||||
|
Async {
|
||||||
|
inner: inner,
|
||||||
|
bytes_until_block: bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block_in(&mut self, bytes: usize) {
|
||||||
|
self.bytes_until_block = bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Read> Read for Async<T> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
if self.bytes_until_block == 0 {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
||||||
|
} else {
|
||||||
|
let n = cmp::min(self.bytes_until_block, buf.len());
|
||||||
|
let n = try!(self.inner.read(&mut buf[..n]));
|
||||||
|
self.bytes_until_block -= n;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Write> Write for Async<T> {
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
if self.bytes_until_block == 0 {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
||||||
|
} else {
|
||||||
|
let n = cmp::min(self.bytes_until_block, data.len());
|
||||||
|
let n = try!(self.inner.write(&data[..n]));
|
||||||
|
self.bytes_until_block -= n;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
self.inner.lock().unwrap().flush()
|
self.inner.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for CloneableMockStream {
|
impl<T: Write> ::vecio::Writev for Async<T> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
||||||
self.inner.lock().unwrap().read(buf)
|
let cap = bufs.iter().map(|buf| buf.len()).fold(0, |total, next| total + next);
|
||||||
}
|
let mut vec = Vec::with_capacity(cap);
|
||||||
}
|
for &buf in bufs {
|
||||||
|
vec.extend(buf);
|
||||||
impl TransportStream for CloneableMockStream {
|
|
||||||
fn try_split(&self) -> Result<CloneableMockStream, io::Error> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self) -> Result<(), io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkStream for CloneableMockStream {
|
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
|
||||||
self.inner.lock().unwrap().peer_addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
self.inner.lock().unwrap().set_read_timeout(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
self.inner.lock().unwrap().set_write_timeout(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
|
||||||
NetworkStream::close(&mut *self.inner.lock().unwrap(), how)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CloneableMockStream {
|
|
||||||
pub fn with_stream(stream: MockStream) -> CloneableMockStream {
|
|
||||||
CloneableMockStream {
|
|
||||||
inner: Arc::new(Mutex::new(stream)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MockConnector;
|
|
||||||
|
|
||||||
impl NetworkConnector for MockConnector {
|
|
||||||
type Stream = MockStream;
|
|
||||||
|
|
||||||
fn connect(&self, _host: &str, _port: u16, _scheme: &str) -> ::Result<MockStream> {
|
|
||||||
Ok(MockStream::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// new connectors must be created if you wish to intercept requests.
|
|
||||||
macro_rules! mock_connector (
|
|
||||||
($name:ident {
|
|
||||||
$($url:expr => $res:expr)*
|
|
||||||
}) => (
|
|
||||||
|
|
||||||
struct $name;
|
|
||||||
|
|
||||||
impl $crate::net::NetworkConnector for $name {
|
|
||||||
type Stream = ::mock::MockStream;
|
|
||||||
fn connect(&self, host: &str, port: u16, scheme: &str)
|
|
||||||
-> $crate::Result<::mock::MockStream> {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
debug!("MockStream::connect({:?}, {:?}, {:?})", host, port, scheme);
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
$(map.insert($url, $res);)*
|
|
||||||
|
|
||||||
|
|
||||||
let key = format!("{}://{}", scheme, host);
|
|
||||||
// ignore port for now
|
|
||||||
match map.get(&*key) {
|
|
||||||
Some(&res) => Ok($crate::mock::MockStream::with_input(res.as_bytes())),
|
|
||||||
None => panic!("{:?} doesn't know url {}", stringify!($name), key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
self.write(&vec)
|
||||||
|
|
||||||
($name:ident { $($response:expr),+ }) => (
|
|
||||||
struct $name;
|
|
||||||
|
|
||||||
impl $crate::net::NetworkConnector for $name {
|
|
||||||
type Stream = $crate::mock::MockStream;
|
|
||||||
fn connect(&self, _: &str, _: u16, _: &str)
|
|
||||||
-> $crate::Result<$crate::mock::MockStream> {
|
|
||||||
Ok($crate::mock::MockStream::with_responses(vec![
|
|
||||||
$($response),+
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
);
|
|
||||||
|
|
||||||
impl TransportStream for MockStream {
|
|
||||||
fn try_split(&self) -> Result<MockStream, io::Error> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self) -> Result<(), io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockStream {
|
impl ::std::ops::Deref for Async<Buf> {
|
||||||
/// Creates a new `MockStream` that will return the response described by the parameters as an
|
type Target = [u8];
|
||||||
/// HTTP/2 response. This will also include the correct server preface.
|
|
||||||
pub fn new_http2_response(status: &[u8], headers: &Headers, body: Option<Vec<u8>>)
|
fn deref(&self) -> &[u8] {
|
||||||
-> MockStream {
|
&self.inner
|
||||||
let resp_bytes = build_http2_response(status, headers, body);
|
|
||||||
MockStream::with_input(&resp_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds up a sequence of bytes that represent a server's response based on the given parameters.
|
|
||||||
pub fn build_http2_response(status: &[u8], headers: &Headers, body: Option<Vec<u8>>) -> Vec<u8> {
|
|
||||||
let mut conn = HttpConnection::new(MockStream::new(), MockStream::new(), HttpScheme::Http);
|
|
||||||
// Server preface first
|
|
||||||
conn.sender.write(&SettingsFrame::new().serialize()).unwrap();
|
|
||||||
|
|
||||||
let mut resp_headers: Vec<_> = headers.iter().map(|h| {
|
|
||||||
(h.name().to_ascii_lowercase().into_bytes(), h.value_string().into_bytes())
|
|
||||||
}).collect();
|
|
||||||
resp_headers.insert(0, (b":status".to_vec(), status.into()));
|
|
||||||
|
|
||||||
let end = if body.is_none() {
|
|
||||||
EndStream::Yes
|
|
||||||
} else {
|
|
||||||
EndStream::No
|
|
||||||
};
|
|
||||||
conn.send_headers(resp_headers, 1, end).unwrap();
|
|
||||||
if body.is_some() {
|
|
||||||
let chunk = DataChunk::new_borrowed(&body.as_ref().unwrap()[..], 1, EndStream::Yes);
|
|
||||||
conn.send_data(chunk).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.sender.write
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mock connector that produces `MockStream`s that are set to return HTTP/2 responses.
|
|
||||||
///
|
|
||||||
/// This means that the streams' payloads are fairly opaque byte sequences (as HTTP/2 is a binary
|
|
||||||
/// protocol), which can be understood only be HTTP/2 clients.
|
|
||||||
pub struct MockHttp2Connector {
|
|
||||||
/// The list of streams that the connector returns, in the given order.
|
|
||||||
pub streams: RefCell<Vec<CloneableMockStream>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MockHttp2Connector {
|
|
||||||
/// Creates a new `MockHttp2Connector` with no streams.
|
|
||||||
pub fn new() -> MockHttp2Connector {
|
|
||||||
MockHttp2Connector {
|
|
||||||
streams: RefCell::new(Vec::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new `CloneableMockStream` to the end of the connector's stream queue.
|
|
||||||
///
|
|
||||||
/// Streams are returned in a FIFO manner.
|
|
||||||
pub fn add_stream(&mut self, stream: CloneableMockStream) {
|
|
||||||
self.streams.borrow_mut().push(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new response stream that will be placed to the end of the connector's stream queue.
|
|
||||||
///
|
|
||||||
/// Returns a separate `CloneableMockStream` that allows the user to inspect what is written
|
|
||||||
/// into the original stream.
|
|
||||||
pub fn new_response_stream(&mut self, status: &[u8], headers: &Headers, body: Option<Vec<u8>>)
|
|
||||||
-> CloneableMockStream {
|
|
||||||
let stream = MockStream::new_http2_response(status, headers, body);
|
|
||||||
let stream = CloneableMockStream::with_stream(stream);
|
|
||||||
let ret = stream.clone();
|
|
||||||
self.add_stream(stream);
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkConnector for MockHttp2Connector {
|
|
||||||
type Stream = CloneableMockStream;
|
|
||||||
#[inline]
|
|
||||||
fn connect(&self, _host: &str, _port: u16, _scheme: &str)
|
|
||||||
-> ::Result<CloneableMockStream> {
|
|
||||||
Ok(self.streams.borrow_mut().remove(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct MockSsl;
|
|
||||||
|
|
||||||
impl<T: NetworkStream + Send + Clone> SslClient<T> for MockSsl {
|
|
||||||
type Stream = T;
|
|
||||||
fn wrap_client(&self, stream: T, _host: &str) -> ::Result<T> {
|
|
||||||
Ok(stream)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
984
src/net.rs
984
src/net.rs
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
|||||||
use std::sync::{Arc, mpsc};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use net::NetworkListener;
|
|
||||||
|
|
||||||
pub struct ListenerPool<A: NetworkListener> {
|
|
||||||
acceptor: A
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: NetworkListener + Send + 'static> ListenerPool<A> {
|
|
||||||
/// Create a thread pool to manage the acceptor.
|
|
||||||
pub fn new(acceptor: A) -> ListenerPool<A> {
|
|
||||||
ListenerPool { acceptor: acceptor }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the acceptor pool. Blocks until the acceptors are closed.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if threads == 0.
|
|
||||||
pub fn accept<F>(self, work: F, threads: usize)
|
|
||||||
where F: Fn(A::Stream) + Send + Sync + 'static {
|
|
||||||
assert!(threads != 0, "Can't accept on 0 threads.");
|
|
||||||
|
|
||||||
let (super_tx, supervisor_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
let work = Arc::new(work);
|
|
||||||
|
|
||||||
// Begin work.
|
|
||||||
for _ in 0..threads {
|
|
||||||
spawn_with(super_tx.clone(), work.clone(), self.acceptor.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monitor for panics.
|
|
||||||
// FIXME(reem): This won't ever exit since we still have a super_tx handle.
|
|
||||||
for _ in supervisor_rx.iter() {
|
|
||||||
spawn_with(super_tx.clone(), work.clone(), self.acceptor.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_with<A, F>(supervisor: mpsc::Sender<()>, work: Arc<F>, mut acceptor: A)
|
|
||||||
where A: NetworkListener + Send + 'static,
|
|
||||||
F: Fn(<A as NetworkListener>::Stream) + Send + Sync + 'static {
|
|
||||||
thread::spawn(move || {
|
|
||||||
let _sentinel = Sentinel::new(supervisor, ());
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match acceptor.accept() {
|
|
||||||
Ok(stream) => work(stream),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Connection failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Sentinel<T: Send + 'static> {
|
|
||||||
value: Option<T>,
|
|
||||||
supervisor: mpsc::Sender<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Send + 'static> Sentinel<T> {
|
|
||||||
fn new(channel: mpsc::Sender<T>, data: T) -> Sentinel<T> {
|
|
||||||
Sentinel {
|
|
||||||
value: Some(data),
|
|
||||||
supervisor: channel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Send + 'static> Drop for Sentinel<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Respawn ourselves
|
|
||||||
let _ = self.supervisor.send(self.value.take().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
58
src/server/message.rs
Normal file
58
src/server/message.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
|
||||||
|
use http::{self, Next};
|
||||||
|
use net::Transport;
|
||||||
|
|
||||||
|
use super::{Handler, request, response};
|
||||||
|
|
||||||
|
/// A MessageHandler for a Server.
|
||||||
|
///
|
||||||
|
/// This should be really thin glue between http::MessageHandler and
|
||||||
|
/// server::Handler, but largely just providing the proper types one
|
||||||
|
/// would expect in a Server Handler.
|
||||||
|
pub struct Message<H: Handler<T>, T: Transport> {
|
||||||
|
handler: H,
|
||||||
|
_marker: PhantomData<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Handler<T>, T: Transport> Message<H, T> {
|
||||||
|
pub fn new(handler: H) -> Message<H, T> {
|
||||||
|
Message {
|
||||||
|
handler: handler,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Handler<T>, T: Transport> http::MessageHandler<T> for Message<H, T> {
|
||||||
|
type Message = http::ServerMessage;
|
||||||
|
|
||||||
|
fn on_incoming(&mut self, head: http::RequestHead) -> Next {
|
||||||
|
trace!("on_incoming {:?}", head);
|
||||||
|
let req = request::new(head);
|
||||||
|
self.handler.on_request(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_decode(&mut self, transport: &mut http::Decoder<T>) -> Next {
|
||||||
|
self.handler.on_request_readable(transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_outgoing(&mut self, head: &mut http::MessageHead<::status::StatusCode>) -> Next {
|
||||||
|
let mut res = response::new(head);
|
||||||
|
self.handler.on_response(&mut res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_encode(&mut self, transport: &mut http::Encoder<T>) -> Next {
|
||||||
|
self.handler.on_response_writable(transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&mut self, error: ::Error) -> Next {
|
||||||
|
self.handler.on_error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_remove(self, transport: T) {
|
||||||
|
self.handler.on_remove(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,510 +1,369 @@
|
|||||||
//! HTTP Server
|
//! HTTP Server
|
||||||
//!
|
//!
|
||||||
//! # Server
|
|
||||||
//!
|
|
||||||
//! A `Server` is created to listen on port, parse HTTP requests, and hand
|
//! A `Server` is created to listen on port, parse HTTP requests, and hand
|
||||||
//! them off to a `Handler`. By default, the Server will listen across multiple
|
//! them off to a `Handler`.
|
||||||
//! threads, but that can be configured to a single thread if preferred.
|
|
||||||
//!
|
|
||||||
//! # Handling requests
|
|
||||||
//!
|
|
||||||
//! You must pass a `Handler` to the Server that will handle requests. There is
|
|
||||||
//! a default implementation for `fn`s and closures, allowing you pass one of
|
|
||||||
//! those easily.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use hyper::server::{Server, Request, Response};
|
|
||||||
//!
|
|
||||||
//! fn hello(req: Request, res: Response) {
|
|
||||||
//! // handle things here
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! Server::http("0.0.0.0:0").unwrap().handle(hello).unwrap();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! As with any trait, you can also define a struct and implement `Handler`
|
|
||||||
//! directly on your own type, and pass that to the `Server` instead.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use std::sync::Mutex;
|
|
||||||
//! use std::sync::mpsc::{channel, Sender};
|
|
||||||
//! use hyper::server::{Handler, Server, Request, Response};
|
|
||||||
//!
|
|
||||||
//! struct SenderHandler {
|
|
||||||
//! sender: Mutex<Sender<&'static str>>
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl Handler for SenderHandler {
|
|
||||||
//! fn handle(&self, req: Request, res: Response) {
|
|
||||||
//! self.sender.lock().unwrap().send("start").unwrap();
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! let (tx, rx) = channel();
|
|
||||||
//! Server::http("0.0.0.0:0").unwrap().handle(SenderHandler {
|
|
||||||
//! sender: Mutex::new(tx)
|
|
||||||
//! }).unwrap();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Since the `Server` will be listening on multiple threads, the `Handler`
|
|
||||||
//! must implement `Sync`: any mutable state must be synchronized.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
//! use hyper::server::{Server, Request, Response};
|
|
||||||
//!
|
|
||||||
//! let counter = AtomicUsize::new(0);
|
|
||||||
//! Server::http("0.0.0.0:0").unwrap().handle(move |req: Request, res: Response| {
|
|
||||||
//! counter.fetch_add(1, Ordering::Relaxed);
|
|
||||||
//! }).unwrap();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # The `Request` and `Response` pair
|
|
||||||
//!
|
|
||||||
//! A `Handler` receives a pair of arguments, a `Request` and a `Response`. The
|
|
||||||
//! `Request` includes access to the `method`, `uri`, and `headers` of the
|
|
||||||
//! incoming HTTP request. It also implements `std::io::Read`, in order to
|
|
||||||
//! read any body, such as with `POST` or `PUT` messages.
|
|
||||||
//!
|
|
||||||
//! Likewise, the `Response` includes ways to set the `status` and `headers`,
|
|
||||||
//! and implements `std::io::Write` to allow writing the response body.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use std::io;
|
|
||||||
//! use hyper::server::{Server, Request, Response};
|
|
||||||
//! use hyper::status::StatusCode;
|
|
||||||
//!
|
|
||||||
//! Server::http("0.0.0.0:0").unwrap().handle(|mut req: Request, mut res: Response| {
|
|
||||||
//! match req.method {
|
|
||||||
//! hyper::Post => {
|
|
||||||
//! io::copy(&mut req, &mut res.start().unwrap()).unwrap();
|
|
||||||
//! },
|
|
||||||
//! _ => *res.status_mut() = StatusCode::MethodNotAllowed
|
|
||||||
//! }
|
|
||||||
//! }).unwrap();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## An aside: Write Status
|
|
||||||
//!
|
|
||||||
//! The `Response` uses a phantom type parameter to determine its write status.
|
|
||||||
//! What does that mean? In short, it ensures you never write a body before
|
|
||||||
//! adding all headers, and never add a header after writing some of the body.
|
|
||||||
//!
|
|
||||||
//! This is often done in most implementations by include a boolean property
|
|
||||||
//! on the response, such as `headers_written`, checking that each time the
|
|
||||||
//! body has something to write, so as to make sure the headers are sent once,
|
|
||||||
//! and only once. But this has 2 downsides:
|
|
||||||
//!
|
|
||||||
//! 1. You are typically never notified that your late header is doing nothing.
|
|
||||||
//! 2. There's a runtime cost to checking on every write.
|
|
||||||
//!
|
|
||||||
//! Instead, hyper handles this statically, or at compile-time. A
|
|
||||||
//! `Response<Fresh>` includes a `headers_mut()` method, allowing you add more
|
|
||||||
//! headers. It also does not implement `Write`, so you can't accidentally
|
|
||||||
//! write early. Once the "head" of the response is correct, you can "send" it
|
|
||||||
//! out by calling `start` on the `Response<Fresh>`. This will return a new
|
|
||||||
//! `Response<Streaming>` object, that no longer has `headers_mut()`, but does
|
|
||||||
//! implement `Write`.
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, ErrorKind, BufWriter, Write};
|
use std::net::SocketAddr;
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::sync::Arc;
|
||||||
use std::thread::{self, JoinHandle};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use num_cpus;
|
use rotor::mio::{EventSet, PollOpt};
|
||||||
|
use rotor::{self, Scope};
|
||||||
|
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
||||||
pub use net::{Fresh, Streaming};
|
use http::{self, Next};
|
||||||
|
use net::{Accept, HttpListener, HttpsListener, SslServer, Transport};
|
||||||
|
|
||||||
use Error;
|
|
||||||
use buffer::BufReader;
|
|
||||||
use header::{Headers, Expect, Connection};
|
|
||||||
use http;
|
|
||||||
use method::Method;
|
|
||||||
use net::{NetworkListener, NetworkStream, HttpListener, HttpsListener, Ssl};
|
|
||||||
use status::StatusCode;
|
|
||||||
use uri::RequestUri;
|
|
||||||
use version::HttpVersion::Http11;
|
|
||||||
|
|
||||||
use self::listener::ListenerPool;
|
mod request;
|
||||||
|
mod response;
|
||||||
|
mod message;
|
||||||
|
|
||||||
pub mod request;
|
/// A configured `Server` ready to run.
|
||||||
pub mod response;
|
pub struct ServerLoop<A, H> where A: Accept, H: HandlerFactory<A::Output> {
|
||||||
|
inner: Option<(rotor::Loop<ServerFsm<A, H>>, Context<H>)>,
|
||||||
mod listener;
|
|
||||||
|
|
||||||
/// A server can listen on a TCP socket.
|
|
||||||
///
|
|
||||||
/// Once listening, it will create a `Request`/`Response` pair for each
|
|
||||||
/// incoming connection, and hand them to the provided handler.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Server<L = HttpListener> {
|
|
||||||
listener: L,
|
|
||||||
timeouts: Timeouts,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
impl<A: Accept, H: HandlerFactory<A::Output>> fmt::Debug for ServerLoop<A, H> {
|
||||||
struct Timeouts {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
read: Option<Duration>,
|
f.pad("ServerLoop")
|
||||||
write: Option<Duration>,
|
|
||||||
keep_alive: Option<Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Timeouts {
|
|
||||||
fn default() -> Timeouts {
|
|
||||||
Timeouts {
|
|
||||||
read: None,
|
|
||||||
write: None,
|
|
||||||
keep_alive: Some(Duration::from_secs(5))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_option(
|
/// A Server that can accept incoming network requests.
|
||||||
($e:expr) => {{
|
#[derive(Debug)]
|
||||||
match $e {
|
pub struct Server<T: Accept> {
|
||||||
Some(v) => v,
|
listener: T,
|
||||||
None => return None
|
keep_alive: bool,
|
||||||
}
|
idle_timeout: Duration,
|
||||||
}}
|
max_sockets: usize,
|
||||||
);
|
}
|
||||||
|
|
||||||
impl<L: NetworkListener> Server<L> {
|
impl<T> Server<T> where T: Accept, T::Output: Transport {
|
||||||
/// Creates a new server with the provided handler.
|
/// Creates a new server with the provided Listener.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(listener: L) -> Server<L> {
|
pub fn new(listener: T) -> Server<T> {
|
||||||
Server {
|
Server {
|
||||||
listener: listener,
|
listener: listener,
|
||||||
timeouts: Timeouts::default()
|
keep_alive: true,
|
||||||
|
idle_timeout: Duration::from_secs(10),
|
||||||
|
max_sockets: 4096,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls keep-alive for this server.
|
/// Enables or disables HTTP keep-alive.
|
||||||
///
|
///
|
||||||
/// The timeout duration passed will be used to determine how long
|
/// Default is true.
|
||||||
/// to keep the connection alive before dropping it.
|
pub fn keep_alive(mut self, val: bool) -> Server<T> {
|
||||||
///
|
self.keep_alive = val;
|
||||||
/// Passing `None` will disable keep-alive.
|
self
|
||||||
///
|
|
||||||
/// Default is enabled with a 5 second timeout.
|
|
||||||
#[inline]
|
|
||||||
pub fn keep_alive(&mut self, timeout: Option<Duration>) {
|
|
||||||
self.timeouts.keep_alive = timeout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the read timeout for all Request reads.
|
/// Sets how long an idle connection will be kept before closing.
|
||||||
pub fn set_read_timeout(&mut self, dur: Option<Duration>) {
|
///
|
||||||
self.timeouts.read = dur;
|
/// Default is 10 seconds.
|
||||||
|
pub fn idle_timeout(mut self, val: Duration) -> Server<T> {
|
||||||
|
self.idle_timeout = val;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the write timeout for all Response writes.
|
/// Sets the maximum open sockets for this Server.
|
||||||
pub fn set_write_timeout(&mut self, dur: Option<Duration>) {
|
///
|
||||||
self.timeouts.write = dur;
|
/// Default is 4096, but most servers can handle much more than this.
|
||||||
|
pub fn max_sockets(mut self, val: usize) -> Server<T> {
|
||||||
|
self.max_sockets = val;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server<HttpListener> {
|
impl Server<HttpListener> { //<H: HandlerFactory<<HttpListener as Accept>::Output>> Server<HttpListener, H> {
|
||||||
/// Creates a new server that will handle `HttpStream`s.
|
/// Creates a new HTTP server config listening on the provided address.
|
||||||
pub fn http<To: ToSocketAddrs>(addr: To) -> ::Result<Server<HttpListener>> {
|
pub fn http(addr: &SocketAddr) -> ::Result<Server<HttpListener>> {
|
||||||
HttpListener::new(addr).map(Server::new)
|
use ::rotor::mio::tcp::TcpListener;
|
||||||
|
TcpListener::bind(addr)
|
||||||
|
.map(HttpListener)
|
||||||
|
.map(Server::new)
|
||||||
|
.map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Ssl + Clone + Send> Server<HttpsListener<S>> {
|
|
||||||
/// Creates a new server that will handle `HttpStream`s over SSL.
|
impl<S: SslServer> Server<HttpsListener<S>> {
|
||||||
|
/// Creates a new server config that will handle `HttpStream`s over SSL.
|
||||||
///
|
///
|
||||||
/// You can use any SSL implementation, as long as implements `hyper::net::Ssl`.
|
/// You can use any SSL implementation, as long as implements `hyper::net::Ssl`.
|
||||||
pub fn https<A: ToSocketAddrs>(addr: A, ssl: S) -> ::Result<Server<HttpsListener<S>>> {
|
pub fn https(addr: &SocketAddr, ssl: S) -> ::Result<Server<HttpsListener<S>>> {
|
||||||
HttpsListener::new(addr, ssl).map(Server::new)
|
HttpsListener::new(addr, ssl)
|
||||||
|
.map(Server::new)
|
||||||
|
.map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<L: NetworkListener + Send + 'static> Server<L> {
|
|
||||||
|
impl<A: Accept> Server<A> where A::Output: Transport {
|
||||||
/// Binds to a socket and starts handling connections.
|
/// Binds to a socket and starts handling connections.
|
||||||
pub fn handle<H: Handler + 'static>(self, handler: H) -> ::Result<Listening> {
|
pub fn handle<H>(self, factory: H) -> ::Result<(Listening, ServerLoop<A, H>)>
|
||||||
self.handle_threads(handler, num_cpus::get() * 5 / 4)
|
where H: HandlerFactory<A::Output> {
|
||||||
}
|
let addr = try!(self.listener.local_addr());
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
let shutdown_rx = shutdown.clone();
|
||||||
|
|
||||||
/// Binds to a socket and starts handling connections with the provided
|
let mut config = rotor::Config::new();
|
||||||
/// number of threads.
|
config.slab_capacity(self.max_sockets);
|
||||||
pub fn handle_threads<H: Handler + 'static>(self, handler: H,
|
config.mio().notify_capacity(self.max_sockets);
|
||||||
threads: usize) -> ::Result<Listening> {
|
let keep_alive = self.keep_alive;
|
||||||
handle(self, handler, threads)
|
let mut loop_ = rotor::Loop::new(&config).unwrap();
|
||||||
}
|
let mut notifier = None;
|
||||||
}
|
|
||||||
|
|
||||||
fn handle<H, L>(mut server: Server<L>, handler: H, threads: usize) -> ::Result<Listening>
|
|
||||||
where H: Handler + 'static, L: NetworkListener + Send + 'static {
|
|
||||||
let socket = try!(server.listener.local_addr());
|
|
||||||
|
|
||||||
debug!("threads = {:?}", threads);
|
|
||||||
let pool = ListenerPool::new(server.listener);
|
|
||||||
let worker = Worker::new(handler, server.timeouts);
|
|
||||||
let work = move |mut stream| worker.handle_connection(&mut stream);
|
|
||||||
|
|
||||||
let guard = thread::spawn(move || pool.accept(work, threads));
|
|
||||||
|
|
||||||
Ok(Listening {
|
|
||||||
_guard: Some(guard),
|
|
||||||
socket: socket,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Worker<H: Handler + 'static> {
|
|
||||||
handler: H,
|
|
||||||
timeouts: Timeouts,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H: Handler + 'static> Worker<H> {
|
|
||||||
fn new(handler: H, timeouts: Timeouts) -> Worker<H> {
|
|
||||||
Worker {
|
|
||||||
handler: handler,
|
|
||||||
timeouts: timeouts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_connection<S>(&self, mut stream: &mut S) where S: NetworkStream + Clone {
|
|
||||||
debug!("Incoming stream");
|
|
||||||
|
|
||||||
self.handler.on_connection_start();
|
|
||||||
|
|
||||||
if let Err(e) = self.set_timeouts(&*stream) {
|
|
||||||
error!("set_timeouts error: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let addr = match stream.peer_addr() {
|
|
||||||
Ok(addr) => addr,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Peer Name error: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let stream_clone: &mut NetworkStream = &mut stream.clone();
|
|
||||||
let mut rdr = BufReader::new(stream_clone);
|
|
||||||
let mut wrt = BufWriter::new(stream);
|
|
||||||
|
|
||||||
while self.keep_alive_loop(&mut rdr, &mut wrt, addr) {
|
|
||||||
if let Err(e) = self.set_read_timeout(*rdr.get_ref(), self.timeouts.keep_alive) {
|
|
||||||
error!("set_read_timeout keep_alive {:?}", e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.handler.on_connection_end();
|
|
||||||
|
|
||||||
debug!("keep_alive loop ending for {}", addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_timeouts(&self, s: &NetworkStream) -> io::Result<()> {
|
|
||||||
try!(self.set_read_timeout(s, self.timeouts.read));
|
|
||||||
self.set_write_timeout(s, self.timeouts.write)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_write_timeout(&self, s: &NetworkStream, timeout: Option<Duration>) -> io::Result<()> {
|
|
||||||
s.set_write_timeout(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_read_timeout(&self, s: &NetworkStream, timeout: Option<Duration>) -> io::Result<()> {
|
|
||||||
s.set_read_timeout(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keep_alive_loop<W: Write>(&self, mut rdr: &mut BufReader<&mut NetworkStream>,
|
|
||||||
wrt: &mut W, addr: SocketAddr) -> bool {
|
|
||||||
let req = match Request::new(rdr, addr) {
|
|
||||||
Ok(req) => req,
|
|
||||||
Err(Error::Io(ref e)) if e.kind() == ErrorKind::ConnectionAborted => {
|
|
||||||
trace!("tcp closed, cancelling keep-alive loop");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Err(Error::Io(e)) => {
|
|
||||||
debug!("ioerror in keepalive loop = {:?}", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
//TODO: send a 400 response
|
|
||||||
error!("request error = {:?}", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.handle_expect(&req, wrt) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = req.set_read_timeout(self.timeouts.read) {
|
|
||||||
error!("set_read_timeout {:?}", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut keep_alive = self.timeouts.keep_alive.is_some() &&
|
|
||||||
http::should_keep_alive(req.version, &req.headers);
|
|
||||||
let version = req.version;
|
|
||||||
let mut res_headers = Headers::new();
|
|
||||||
if !keep_alive {
|
|
||||||
res_headers.set(Connection::close());
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
let mut res = Response::new(wrt, &mut res_headers);
|
let notifier = &mut notifier;
|
||||||
res.version = version;
|
loop_.add_machine_with(move |scope| {
|
||||||
self.handler.handle(req, res);
|
*notifier = Some(scope.notifier());
|
||||||
|
rotor_try!(scope.register(&self.listener, EventSet::readable(), PollOpt::level()));
|
||||||
|
rotor::Response::ok(ServerFsm::Listener::<A, H>(self.listener, shutdown_rx))
|
||||||
|
}).unwrap();
|
||||||
}
|
}
|
||||||
|
let notifier = notifier.expect("loop.add_machine failed");
|
||||||
|
|
||||||
// if the request was keep-alive, we need to check that the server agrees
|
let listening = Listening {
|
||||||
// if it wasn't, then the server cannot force it to be true anyways
|
addr: addr,
|
||||||
if keep_alive {
|
shutdown: (shutdown, notifier),
|
||||||
keep_alive = http::should_keep_alive(version, &res_headers);
|
};
|
||||||
}
|
let server = ServerLoop {
|
||||||
|
inner: Some((loop_, Context {
|
||||||
|
keep_alive: keep_alive,
|
||||||
|
factory: factory
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
Ok((listening, server))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!("keep_alive = {:?} for {}", keep_alive, addr);
|
|
||||||
keep_alive
|
impl<A: Accept, H: HandlerFactory<A::Output>> ServerLoop<A, H> {
|
||||||
|
/// Runs the server forever in this loop.
|
||||||
|
///
|
||||||
|
/// This will block the current thread.
|
||||||
|
pub fn run(self) {
|
||||||
|
// drop will take care of it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Accept, H: HandlerFactory<A::Output>> Drop for ServerLoop<A, H> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner.take().map(|(loop_, ctx)| {
|
||||||
|
let _ = loop_.run(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context<F> {
|
||||||
|
keep_alive: bool,
|
||||||
|
factory: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: HandlerFactory<T>, T: Transport> http::MessageHandlerFactory<(), T> for Context<F> {
|
||||||
|
type Output = message::Message<F::Output, T>;
|
||||||
|
|
||||||
|
fn create(&mut self, seed: http::Seed<()>) -> Self::Output {
|
||||||
|
message::Message::new(self.factory.create(seed.control()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ServerFsm<A, H>
|
||||||
|
where A: Accept,
|
||||||
|
A::Output: Transport,
|
||||||
|
H: HandlerFactory<A::Output> {
|
||||||
|
Listener(A, Arc<AtomicBool>),
|
||||||
|
Conn(http::Conn<(), A::Output, message::Message<H::Output, A::Output>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, H> rotor::Machine for ServerFsm<A, H>
|
||||||
|
where A: Accept,
|
||||||
|
A::Output: Transport,
|
||||||
|
H: HandlerFactory<A::Output> {
|
||||||
|
type Context = Context<H>;
|
||||||
|
type Seed = A::Output;
|
||||||
|
|
||||||
|
fn create(seed: Self::Seed, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, rotor::Void> {
|
||||||
|
rotor_try!(scope.register(&seed, EventSet::readable(), PollOpt::level()));
|
||||||
|
rotor::Response::ok(
|
||||||
|
ServerFsm::Conn(
|
||||||
|
http::Conn::new((), seed, scope.notifier())
|
||||||
|
.keep_alive(scope.keep_alive)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_expect<W: Write>(&self, req: &Request, wrt: &mut W) -> bool {
|
fn ready(self, events: EventSet, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
||||||
if req.version == Http11 && req.headers.get() == Some(&Expect::Continue) {
|
match self {
|
||||||
let status = self.handler.check_continue((&req.method, &req.uri, &req.headers));
|
ServerFsm::Listener(listener, rx) => {
|
||||||
match write!(wrt, "{} {}\r\n\r\n", Http11, status).and_then(|_| wrt.flush()) {
|
match listener.accept() {
|
||||||
Ok(..) => (),
|
Ok(Some(conn)) => {
|
||||||
Err(e) => {
|
rotor::Response::spawn(ServerFsm::Listener(listener, rx), conn)
|
||||||
error!("error writing 100-continue: {:?}", e);
|
},
|
||||||
return false;
|
Ok(None) => rotor::Response::ok(ServerFsm::Listener(listener, rx)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("listener accept error {}", e);
|
||||||
|
// usually fine, just keep listening
|
||||||
|
rotor::Response::ok(ServerFsm::Listener(listener, rx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ServerFsm::Conn(conn) => {
|
||||||
|
match conn.ready(events, scope) {
|
||||||
|
Some((conn, None)) => rotor::Response::ok(ServerFsm::Conn(conn)),
|
||||||
|
Some((conn, Some(dur))) => {
|
||||||
|
rotor::Response::ok(ServerFsm::Conn(conn))
|
||||||
|
.deadline(scope.now() + dur)
|
||||||
|
}
|
||||||
|
None => rotor::Response::done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if status != StatusCode::Continue {
|
fn spawned(self, _scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
||||||
debug!("non-100 status ({}) for Expect 100 request", status);
|
match self {
|
||||||
return false;
|
ServerFsm::Listener(listener, rx) => {
|
||||||
}
|
match listener.accept() {
|
||||||
|
Ok(Some(conn)) => {
|
||||||
|
rotor::Response::spawn(ServerFsm::Listener(listener, rx), conn)
|
||||||
|
},
|
||||||
|
Ok(None) => rotor::Response::ok(ServerFsm::Listener(listener, rx)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("listener accept error {}", e);
|
||||||
|
// usually fine, just keep listening
|
||||||
|
rotor::Response::ok(ServerFsm::Listener(listener, rx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sock => rotor::Response::ok(sock)
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
}
|
||||||
|
|
||||||
|
fn timeout(self, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
||||||
|
match self {
|
||||||
|
ServerFsm::Listener(..) => unreachable!("Listener cannot timeout"),
|
||||||
|
ServerFsm::Conn(conn) => {
|
||||||
|
match conn.timeout(scope) {
|
||||||
|
Some((conn, None)) => rotor::Response::ok(ServerFsm::Conn(conn)),
|
||||||
|
Some((conn, Some(dur))) => {
|
||||||
|
rotor::Response::ok(ServerFsm::Conn(conn))
|
||||||
|
.deadline(scope.now() + dur)
|
||||||
|
}
|
||||||
|
None => rotor::Response::done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wakeup(self, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
||||||
|
match self {
|
||||||
|
ServerFsm::Listener(lst, shutdown) => {
|
||||||
|
if shutdown.load(Ordering::Acquire) {
|
||||||
|
let _ = scope.deregister(&lst);
|
||||||
|
scope.shutdown_loop();
|
||||||
|
rotor::Response::done()
|
||||||
|
} else {
|
||||||
|
rotor::Response::ok(ServerFsm::Listener(lst, shutdown))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ServerFsm::Conn(conn) => match conn.wakeup(scope) {
|
||||||
|
Some((conn, None)) => rotor::Response::ok(ServerFsm::Conn(conn)),
|
||||||
|
Some((conn, Some(dur))) => {
|
||||||
|
rotor::Response::ok(ServerFsm::Conn(conn))
|
||||||
|
.deadline(scope.now() + dur)
|
||||||
|
}
|
||||||
|
None => rotor::Response::done()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A listening server, which can later be closed.
|
/// A handle of the running server.
|
||||||
pub struct Listening {
|
pub struct Listening {
|
||||||
_guard: Option<JoinHandle<()>>,
|
addr: SocketAddr,
|
||||||
/// The socket addresses that the server is bound to.
|
shutdown: (Arc<AtomicBool>, rotor::Notifier),
|
||||||
pub socket: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Listening {
|
impl fmt::Debug for Listening {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Listening {{ socket: {:?} }}", self.socket)
|
f.debug_struct("Listening")
|
||||||
|
.field("addr", &self.addr)
|
||||||
|
.field("closed", &self.shutdown.0.load(Ordering::Relaxed))
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Listening {
|
impl fmt::Display for Listening {
|
||||||
fn drop(&mut self) {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let _ = self._guard.take().map(|g| g.join());
|
fmt::Display::fmt(&self.addr, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Listening {
|
impl Listening {
|
||||||
/// Warning: This function doesn't work. The server remains listening after you called
|
/// The address this server is listening on.
|
||||||
/// it. See https://github.com/hyperium/hyper/issues/338 for more details.
|
pub fn addr(&self) -> &SocketAddr {
|
||||||
///
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop the server from listening to its socket address.
|
/// Stop the server from listening to its socket address.
|
||||||
pub fn close(&mut self) -> ::Result<()> {
|
pub fn close(self) {
|
||||||
let _ = self._guard.take();
|
debug!("closing server {}", self);
|
||||||
debug!("closing server");
|
self.shutdown.0.store(true, Ordering::Release);
|
||||||
Ok(())
|
self.shutdown.1.wakeup().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handler that can handle incoming requests for a server.
|
/// A trait to react to server events that happen for each message.
|
||||||
pub trait Handler: Sync + Send {
|
///
|
||||||
/// Receives a `Request`/`Response` pair, and should perform some action on them.
|
/// Each event handler returns it's desired `Next` action.
|
||||||
///
|
pub trait Handler<T: Transport> {
|
||||||
/// This could reading from the request, and writing to the response.
|
/// This event occurs first, triggering when a `Request` has been parsed.
|
||||||
fn handle<'a, 'k>(&'a self, Request<'a, 'k>, Response<'a, Fresh>);
|
fn on_request(&mut self, request: Request) -> Next;
|
||||||
|
/// This event occurs each time the `Request` is ready to be read from.
|
||||||
|
fn on_request_readable(&mut self, request: &mut http::Decoder<T>) -> Next;
|
||||||
|
/// This event occurs after the first time this handled signals `Next::write()`.
|
||||||
|
fn on_response(&mut self, response: &mut Response) -> Next;
|
||||||
|
/// This event occurs each time the `Response` is ready to be written to.
|
||||||
|
fn on_response_writable(&mut self, response: &mut http::Encoder<T>) -> Next;
|
||||||
|
|
||||||
/// Called when a Request includes a `Expect: 100-continue` header.
|
/// This event occurs whenever an `Error` occurs outside of the other events.
|
||||||
///
|
///
|
||||||
/// By default, this will always immediately response with a `StatusCode::Continue`,
|
/// This could IO errors while waiting for events, or a timeout, etc.
|
||||||
/// but can be overridden with custom behavior.
|
fn on_error(&mut self, err: ::Error) -> Next where Self: Sized {
|
||||||
fn check_continue(&self, _: (&Method, &RequestUri, &Headers)) -> StatusCode {
|
debug!("default Handler.on_error({:?})", err);
|
||||||
StatusCode::Continue
|
http::Next::remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is run after a connection is received, on a per-connection basis (not a
|
/// This event occurs when this Handler has requested to remove the Transport.
|
||||||
/// per-request basis, as a connection with keep-alive may handle multiple
|
fn on_remove(self, _transport: T) where Self: Sized {
|
||||||
/// requests)
|
debug!("default Handler.on_remove");
|
||||||
fn on_connection_start(&self) { }
|
}
|
||||||
|
|
||||||
/// This is run before a connection is closed, on a per-connection basis (not a
|
|
||||||
/// per-request basis, as a connection with keep-alive may handle multiple
|
|
||||||
/// requests)
|
|
||||||
fn on_connection_end(&self) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> Handler for F where F: Fn(Request, Response<Fresh>), F: Sync + Send {
|
|
||||||
fn handle<'a, 'k>(&'a self, req: Request<'a, 'k>, res: Response<'a, Fresh>) {
|
/// Used to create a `Handler` when a new message is received by the server.
|
||||||
self(req, res)
|
pub trait HandlerFactory<T: Transport> {
|
||||||
|
/// The `Handler` to use for the incoming message.
|
||||||
|
type Output: Handler<T>;
|
||||||
|
/// Creates the associated `Handler`.
|
||||||
|
fn create(&mut self, ctrl: http::Control) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, H, T> HandlerFactory<T> for F
|
||||||
|
where F: FnMut(http::Control) -> H, H: Handler<T>, T: Transport {
|
||||||
|
type Output = H;
|
||||||
|
fn create(&mut self, ctrl: http::Control) -> H {
|
||||||
|
self(ctrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use header::Headers;
|
|
||||||
use method::Method;
|
|
||||||
use mock::MockStream;
|
|
||||||
use status::StatusCode;
|
|
||||||
use uri::RequestUri;
|
|
||||||
|
|
||||||
use super::{Request, Response, Fresh, Handler, Worker};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_check_continue_default() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST /upload HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Expect: 100-continue\r\n\
|
|
||||||
Content-Length: 10\r\n\
|
|
||||||
\r\n\
|
|
||||||
1234567890\
|
|
||||||
");
|
|
||||||
|
|
||||||
fn handle(_: Request, res: Response<Fresh>) {
|
|
||||||
res.start().unwrap().end().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Worker::new(handle, Default::default()).handle_connection(&mut mock);
|
|
||||||
let cont = b"HTTP/1.1 100 Continue\r\n\r\n";
|
|
||||||
assert_eq!(&mock.write[..cont.len()], cont);
|
|
||||||
let res = b"HTTP/1.1 200 OK\r\n";
|
|
||||||
assert_eq!(&mock.write[cont.len()..cont.len() + res.len()], res);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_check_continue_reject() {
|
|
||||||
struct Reject;
|
|
||||||
impl Handler for Reject {
|
|
||||||
fn handle<'a, 'k>(&'a self, _: Request<'a, 'k>, res: Response<'a, Fresh>) {
|
|
||||||
res.start().unwrap().end().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_continue(&self, _: (&Method, &RequestUri, &Headers)) -> StatusCode {
|
|
||||||
StatusCode::ExpectationFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST /upload HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Expect: 100-continue\r\n\
|
|
||||||
Content-Length: 10\r\n\
|
|
||||||
\r\n\
|
|
||||||
1234567890\
|
|
||||||
");
|
|
||||||
|
|
||||||
Worker::new(Reject, Default::default()).handle_connection(&mut mock);
|
|
||||||
assert_eq!(mock.write, &b"HTTP/1.1 417 Expectation Failed\r\n\r\n"[..]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,324 +2,75 @@
|
|||||||
//!
|
//!
|
||||||
//! These are requests that a `hyper::Server` receives, and include its method,
|
//! These are requests that a `hyper::Server` receives, and include its method,
|
||||||
//! target URI, headers, and message body.
|
//! target URI, headers, and message body.
|
||||||
use std::io::{self, Read};
|
//use std::net::SocketAddr;
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use buffer::BufReader;
|
use version::HttpVersion;
|
||||||
use net::NetworkStream;
|
|
||||||
use version::{HttpVersion};
|
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use header::{Headers, ContentLength, TransferEncoding};
|
use header::Headers;
|
||||||
use http::h1::{self, Incoming, HttpReader};
|
use http::{RequestHead, MessageHead, RequestLine};
|
||||||
use http::h1::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
|
|
||||||
|
pub fn new(incoming: RequestHead) -> Request {
|
||||||
|
let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming;
|
||||||
|
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
||||||
|
debug!("{:#?}", headers);
|
||||||
|
|
||||||
|
Request {
|
||||||
|
//remote_addr: addr,
|
||||||
|
method: method,
|
||||||
|
uri: uri,
|
||||||
|
headers: headers,
|
||||||
|
version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
||||||
pub struct Request<'a, 'b: 'a> {
|
#[derive(Debug)]
|
||||||
/// The IP address of the remote connection.
|
pub struct Request {
|
||||||
pub remote_addr: SocketAddr,
|
// The IP address of the remote connection.
|
||||||
/// The `Method`, such as `Get`, `Post`, etc.
|
//remote_addr: SocketAddr,
|
||||||
pub method: Method,
|
method: Method,
|
||||||
/// The headers of the incoming request.
|
headers: Headers,
|
||||||
pub headers: Headers,
|
uri: RequestUri,
|
||||||
/// The target request-uri for this request.
|
version: HttpVersion,
|
||||||
pub uri: RequestUri,
|
|
||||||
/// The version of HTTP for this request.
|
|
||||||
pub version: HttpVersion,
|
|
||||||
body: HttpReader<&'a mut BufReader<&'b mut NetworkStream>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a> Request<'a, 'b> {
|
impl Request {
|
||||||
/// Create a new Request, reading the StartLine and Headers so they are
|
/// The `Method`, such as `Get`, `Post`, etc.
|
||||||
/// immediately useful.
|
|
||||||
pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr)
|
|
||||||
-> ::Result<Request<'a, 'b>> {
|
|
||||||
|
|
||||||
let Incoming { version, subject: (method, uri), headers } = try!(h1::parse_request(stream));
|
|
||||||
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
|
||||||
debug!("{:?}", headers);
|
|
||||||
|
|
||||||
let body = if headers.has::<ContentLength>() {
|
|
||||||
match headers.get::<ContentLength>() {
|
|
||||||
Some(&ContentLength(len)) => SizedReader(stream, len),
|
|
||||||
None => unreachable!()
|
|
||||||
}
|
|
||||||
} else if headers.has::<TransferEncoding>() {
|
|
||||||
todo!("check for Transfer-Encoding: chunked");
|
|
||||||
ChunkedReader(stream, None)
|
|
||||||
} else {
|
|
||||||
EmptyReader(stream)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Request {
|
|
||||||
remote_addr: addr,
|
|
||||||
method: method,
|
|
||||||
uri: uri,
|
|
||||||
headers: headers,
|
|
||||||
version: version,
|
|
||||||
body: body
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the read timeout of the underlying NetworkStream.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
|
pub fn method(&self) -> &Method { &self.method }
|
||||||
self.body.get_ref().get_ref().set_read_timeout(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the underlying `NetworkStream`.
|
/// The headers of the incoming request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn downcast_ref<T: NetworkStream>(&self) -> Option<&T> {
|
pub fn headers(&self) -> &Headers { &self.headers }
|
||||||
self.body.get_ref().get_ref().downcast_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the underlying Ssl stream, if connected
|
/// The target request-uri for this request.
|
||||||
/// over HTTPS.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate hyper;
|
|
||||||
/// # #[cfg(feature = "openssl")]
|
|
||||||
/// extern crate openssl;
|
|
||||||
/// # #[cfg(feature = "openssl")]
|
|
||||||
/// use openssl::ssl::SslStream;
|
|
||||||
/// use hyper::net::HttpStream;
|
|
||||||
/// # fn main() {}
|
|
||||||
/// # #[cfg(feature = "openssl")]
|
|
||||||
/// # fn doc_ssl(req: hyper::server::Request) {
|
|
||||||
/// let maybe_ssl = req.ssl::<SslStream<HttpStream>>();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ssl<T: NetworkStream>(&self) -> Option<&T> {
|
pub fn uri(&self) -> &RequestUri { &self.uri }
|
||||||
use ::net::HttpsStream;
|
|
||||||
match self.downcast_ref() {
|
/// The version of HTTP for this request.
|
||||||
Some(&HttpsStream::Https(ref s)) => Some(s),
|
#[inline]
|
||||||
|
pub fn version(&self) -> &HttpVersion { &self.version }
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// The target path of this Request.
|
||||||
|
#[inline]
|
||||||
|
pub fn path(&self) -> Option<&str> {
|
||||||
|
match *self.uri {
|
||||||
|
RequestUri::AbsolutePath(ref s) => Some(s),
|
||||||
|
RequestUri::AbsoluteUri(ref url) => Some(&url[::url::Position::BeforePath..]),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/// Deconstruct a Request into its constituent parts.
|
/// Deconstruct this Request into its pieces.
|
||||||
|
///
|
||||||
|
/// Modifying these pieces will have no effect on how hyper behaves.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn deconstruct(self) -> (SocketAddr, Method, Headers,
|
pub fn deconstruct(self) -> (Method, RequestUri, HttpVersion, Headers) {
|
||||||
RequestUri, HttpVersion,
|
(self.method, self.uri, self.version, self.headers)
|
||||||
HttpReader<&'a mut BufReader<&'b mut NetworkStream>>) {
|
|
||||||
(self.remote_addr, self.method, self.headers,
|
|
||||||
self.uri, self.version, self.body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> Read for Request<'a, 'b> {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.body.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use buffer::BufReader;
|
|
||||||
use header::{Host, TransferEncoding, Encoding};
|
|
||||||
use net::NetworkStream;
|
|
||||||
use mock::MockStream;
|
|
||||||
use super::Request;
|
|
||||||
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
fn sock(s: &str) -> SocketAddr {
|
|
||||||
s.parse().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_to_string(mut req: Request) -> io::Result<String> {
|
|
||||||
let mut s = String::new();
|
|
||||||
try!(req.read_to_string(&mut s));
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_empty_body() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
\r\n\
|
|
||||||
I'm a bad request.\r\n\
|
|
||||||
");
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_with_body() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Content-Length: 19\r\n\
|
|
||||||
\r\n\
|
|
||||||
I'm a good request.\r\n\
|
|
||||||
");
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "I'm a good request.".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_head_empty_body() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
HEAD / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
\r\n\
|
|
||||||
I'm a bad request.\r\n\
|
|
||||||
");
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_post_empty_body() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
\r\n\
|
|
||||||
I'm a bad request.\r\n\
|
|
||||||
");
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_chunked_request() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Transfer-Encoding: chunked\r\n\
|
|
||||||
\r\n\
|
|
||||||
1\r\n\
|
|
||||||
q\r\n\
|
|
||||||
2\r\n\
|
|
||||||
we\r\n\
|
|
||||||
2\r\n\
|
|
||||||
rt\r\n\
|
|
||||||
0\r\n\
|
|
||||||
\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
|
|
||||||
// The headers are correct?
|
|
||||||
match req.headers.get::<Host>() {
|
|
||||||
Some(host) => {
|
|
||||||
assert_eq!("example.domain", host.hostname);
|
|
||||||
},
|
|
||||||
None => panic!("Host header expected!"),
|
|
||||||
};
|
|
||||||
match req.headers.get::<TransferEncoding>() {
|
|
||||||
Some(encodings) => {
|
|
||||||
assert_eq!(1, encodings.len());
|
|
||||||
assert_eq!(Encoding::Chunked, encodings[0]);
|
|
||||||
}
|
|
||||||
None => panic!("Transfer-Encoding: chunked expected!"),
|
|
||||||
};
|
|
||||||
// The content is correctly read?
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "qwert".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests that when a chunk size is not a valid radix-16 number, an error
|
|
||||||
/// is returned.
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_chunk_size_not_hex_digit() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Transfer-Encoding: chunked\r\n\
|
|
||||||
\r\n\
|
|
||||||
X\r\n\
|
|
||||||
1\r\n\
|
|
||||||
0\r\n\
|
|
||||||
\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
|
|
||||||
assert!(read_to_string(req).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests that when a chunk size contains an invalid extension, an error is
|
|
||||||
/// returned.
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_chunk_size_extension() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Transfer-Encoding: chunked\r\n\
|
|
||||||
\r\n\
|
|
||||||
1 this is an invalid extension\r\n\
|
|
||||||
1\r\n\
|
|
||||||
0\r\n\
|
|
||||||
\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
|
|
||||||
assert!(read_to_string(req).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests that when a valid extension that contains a digit is appended to
|
|
||||||
/// the chunk size, the chunk is correctly read.
|
|
||||||
#[test]
|
|
||||||
fn test_chunk_size_with_extension() {
|
|
||||||
let mut mock = MockStream::with_input(b"\
|
|
||||||
POST / HTTP/1.1\r\n\
|
|
||||||
Host: example.domain\r\n\
|
|
||||||
Transfer-Encoding: chunked\r\n\
|
|
||||||
\r\n\
|
|
||||||
1;this is an extension with a digit 1\r\n\
|
|
||||||
1\r\n\
|
|
||||||
0\r\n\
|
|
||||||
\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Use Type ascription
|
|
||||||
let mock: &mut NetworkStream = &mut mock;
|
|
||||||
let mut stream = BufReader::new(mock);
|
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(read_to_string(req).unwrap(), "1".to_owned());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,431 +2,49 @@
|
|||||||
//!
|
//!
|
||||||
//! These are responses sent by a `hyper::Server` to clients, after
|
//! These are responses sent by a `hyper::Server` to clients, after
|
||||||
//! receiving a request.
|
//! receiving a request.
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::mem;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::ptr;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use time::now_utc;
|
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
use http::h1::{LINE_ENDING, HttpWriter};
|
use http;
|
||||||
use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
use status::StatusCode;
|
||||||
use status;
|
|
||||||
use net::{Fresh, Streaming};
|
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
|
|
||||||
/// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`.
|
/// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`.
|
||||||
///
|
///
|
||||||
/// The default `StatusCode` for a `Response` is `200 OK`.
|
/// The default `StatusCode` for a `Response` is `200 OK`.
|
||||||
///
|
|
||||||
/// There is a `Drop` implementation for `Response` that will automatically
|
|
||||||
/// write the head and flush the body, if the handler has not already done so,
|
|
||||||
/// so that the server doesn't accidentally leave dangling requests.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Response<'a, W: Any = Fresh> {
|
pub struct Response<'a> {
|
||||||
/// The HTTP version of this response.
|
head: &'a mut http::MessageHead<StatusCode>,
|
||||||
pub version: version::HttpVersion,
|
|
||||||
// Stream the Response is writing to, not accessible through UnwrittenResponse
|
|
||||||
body: HttpWriter<&'a mut (Write + 'a)>,
|
|
||||||
// The status code for the request.
|
|
||||||
status: status::StatusCode,
|
|
||||||
// The outgoing headers on this response.
|
|
||||||
headers: &'a mut header::Headers,
|
|
||||||
|
|
||||||
_writing: PhantomData<W>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W: Any> Response<'a, W> {
|
impl<'a> Response<'a> {
|
||||||
/// The status of this response.
|
|
||||||
#[inline]
|
|
||||||
pub fn status(&self) -> status::StatusCode { self.status }
|
|
||||||
|
|
||||||
/// The headers of this response.
|
/// The headers of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &header::Headers { &*self.headers }
|
pub fn headers(&self) -> &header::Headers { &self.head.headers }
|
||||||
|
|
||||||
/// Construct a Response from its constituent parts.
|
/// The status of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn construct(version: version::HttpVersion,
|
pub fn status(&self) -> &StatusCode {
|
||||||
body: HttpWriter<&'a mut (Write + 'a)>,
|
&self.head.subject
|
||||||
status: status::StatusCode,
|
|
||||||
headers: &'a mut header::Headers) -> Response<'a, Fresh> {
|
|
||||||
Response {
|
|
||||||
status: status,
|
|
||||||
version: version,
|
|
||||||
body: body,
|
|
||||||
headers: headers,
|
|
||||||
_writing: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deconstruct this Response into its constituent parts.
|
/// The HTTP version of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn deconstruct(self) -> (version::HttpVersion, HttpWriter<&'a mut (Write + 'a)>,
|
pub fn version(&self) -> &version::HttpVersion { &self.head.version }
|
||||||
status::StatusCode, &'a mut header::Headers) {
|
|
||||||
unsafe {
|
|
||||||
let parts = (
|
|
||||||
self.version,
|
|
||||||
ptr::read(&self.body),
|
|
||||||
self.status,
|
|
||||||
ptr::read(&self.headers)
|
|
||||||
);
|
|
||||||
mem::forget(self);
|
|
||||||
parts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_head(&mut self) -> io::Result<Body> {
|
|
||||||
debug!("writing head: {:?} {:?}", self.version, self.status);
|
|
||||||
try!(write!(&mut self.body, "{} {}\r\n", self.version, self.status));
|
|
||||||
|
|
||||||
if !self.headers.has::<header::Date>() {
|
|
||||||
self.headers.set(header::Date(header::HttpDate(now_utc())));
|
|
||||||
}
|
|
||||||
|
|
||||||
let body_type = match self.status {
|
|
||||||
status::StatusCode::NoContent | status::StatusCode::NotModified => Body::Empty,
|
|
||||||
c if c.class() == status::StatusClass::Informational => Body::Empty,
|
|
||||||
_ => if let Some(cl) = self.headers.get::<header::ContentLength>() {
|
|
||||||
Body::Sized(**cl)
|
|
||||||
} else {
|
|
||||||
Body::Chunked
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// can't do in match above, thanks borrowck
|
|
||||||
if body_type == Body::Chunked {
|
|
||||||
let encodings = match self.headers.get_mut::<header::TransferEncoding>() {
|
|
||||||
Some(&mut header::TransferEncoding(ref mut encodings)) => {
|
|
||||||
//TODO: check if chunked is already in encodings. use HashSet?
|
|
||||||
encodings.push(header::Encoding::Chunked);
|
|
||||||
false
|
|
||||||
},
|
|
||||||
None => true
|
|
||||||
};
|
|
||||||
|
|
||||||
if encodings {
|
|
||||||
self.headers.set::<header::TransferEncoding>(
|
|
||||||
header::TransferEncoding(vec![header::Encoding::Chunked]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
debug!("headers [\n{:?}]", self.headers);
|
|
||||||
try!(write!(&mut self.body, "{}", self.headers));
|
|
||||||
try!(write!(&mut self.body, "{}", LINE_ENDING));
|
|
||||||
|
|
||||||
Ok(body_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Response<'a, Fresh> {
|
|
||||||
/// Creates a new Response that can be used to write to a network stream.
|
|
||||||
#[inline]
|
|
||||||
pub fn new(stream: &'a mut (Write + 'a), headers: &'a mut header::Headers) ->
|
|
||||||
Response<'a, Fresh> {
|
|
||||||
Response {
|
|
||||||
status: status::StatusCode::Ok,
|
|
||||||
version: version::HttpVersion::Http11,
|
|
||||||
headers: headers,
|
|
||||||
body: ThroughWriter(stream),
|
|
||||||
_writing: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the body and ends the response.
|
|
||||||
///
|
|
||||||
/// This is a shortcut method for when you have a response with a fixed
|
|
||||||
/// size, and would only need a single `write` call normally.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use hyper::server::Response;
|
|
||||||
/// fn handler(res: Response) {
|
|
||||||
/// res.send(b"Hello World!").unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The above is the same, but shorter, than the longer:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use hyper::server::Response;
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use hyper::header::ContentLength;
|
|
||||||
/// fn handler(mut res: Response) {
|
|
||||||
/// let body = b"Hello World!";
|
|
||||||
/// res.headers_mut().set(ContentLength(body.len() as u64));
|
|
||||||
/// let mut res = res.start().unwrap();
|
|
||||||
/// res.write_all(body).unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn send(mut self, body: &[u8]) -> io::Result<()> {
|
|
||||||
self.headers.set(header::ContentLength(body.len() as u64));
|
|
||||||
let mut stream = try!(self.start());
|
|
||||||
try!(stream.write_all(body));
|
|
||||||
stream.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume this Response<Fresh>, writing the Headers and Status and
|
|
||||||
/// creating a Response<Streaming>
|
|
||||||
pub fn start(mut self) -> io::Result<Response<'a, Streaming>> {
|
|
||||||
let body_type = try!(self.write_head());
|
|
||||||
let (version, body, status, headers) = self.deconstruct();
|
|
||||||
let stream = match body_type {
|
|
||||||
Body::Chunked => ChunkedWriter(body.into_inner()),
|
|
||||||
Body::Sized(len) => SizedWriter(body.into_inner(), len),
|
|
||||||
Body::Empty => EmptyWriter(body.into_inner()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// "copy" to change the phantom type
|
|
||||||
Ok(Response {
|
|
||||||
version: version,
|
|
||||||
body: stream,
|
|
||||||
status: status,
|
|
||||||
headers: headers,
|
|
||||||
_writing: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the status.
|
|
||||||
#[inline]
|
|
||||||
pub fn status_mut(&mut self) -> &mut status::StatusCode { &mut self.status }
|
|
||||||
|
|
||||||
/// Get a mutable reference to the Headers.
|
/// Get a mutable reference to the Headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut header::Headers { self.headers }
|
pub fn headers_mut(&mut self) -> &mut header::Headers { &mut self.head.headers }
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Get a mutable reference to the status.
|
||||||
impl<'a> Response<'a, Streaming> {
|
|
||||||
/// Flushes all writing of a response to the client.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn end(self) -> io::Result<()> {
|
pub fn set_status(&mut self, status: StatusCode) {
|
||||||
trace!("ending");
|
self.head.subject = status;
|
||||||
let (_, body, _, _) = self.deconstruct();
|
|
||||||
try!(body.end());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Write for Response<'a, Streaming> {
|
/// Creates a new Response that can be used to write to a network stream.
|
||||||
#[inline]
|
pub fn new<'a>(head: &'a mut http::MessageHead<StatusCode>) -> Response<'a> {
|
||||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
Response {
|
||||||
debug!("write {:?} bytes", msg.len());
|
head: head
|
||||||
self.body.write(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.body.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum Body {
|
|
||||||
Chunked,
|
|
||||||
Sized(u64),
|
|
||||||
Empty,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Any> Drop for Response<'a, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if TypeId::of::<T>() == TypeId::of::<Fresh>() {
|
|
||||||
if thread::panicking() {
|
|
||||||
self.status = status::StatusCode::InternalServerError;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut body = match self.write_head() {
|
|
||||||
Ok(Body::Chunked) => ChunkedWriter(self.body.get_mut()),
|
|
||||||
Ok(Body::Sized(len)) => SizedWriter(self.body.get_mut(), len),
|
|
||||||
Ok(Body::Empty) => EmptyWriter(self.body.get_mut()),
|
|
||||||
Err(e) => {
|
|
||||||
debug!("error dropping request: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
end(&mut body);
|
|
||||||
} else {
|
|
||||||
end(&mut self.body);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn end<W: Write>(w: &mut W) {
|
|
||||||
match w.write(&[]) {
|
|
||||||
Ok(_) => match w.flush() {
|
|
||||||
Ok(_) => debug!("drop successful"),
|
|
||||||
Err(e) => debug!("error dropping request: {:?}", e)
|
|
||||||
},
|
|
||||||
Err(e) => debug!("error dropping request: {:?}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use header::Headers;
|
|
||||||
use mock::MockStream;
|
|
||||||
use super::Response;
|
|
||||||
|
|
||||||
macro_rules! lines {
|
|
||||||
($s:ident = $($line:pat),+) => ({
|
|
||||||
let s = String::from_utf8($s.write).unwrap();
|
|
||||||
let mut lines = s.split_terminator("\r\n");
|
|
||||||
|
|
||||||
$(
|
|
||||||
match lines.next() {
|
|
||||||
Some($line) => (),
|
|
||||||
other => panic!("line mismatch: {:?} != {:?}", other, stringify!($line))
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
|
|
||||||
assert_eq!(lines.next(), None);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fresh_start() {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = MockStream::new();
|
|
||||||
{
|
|
||||||
let res = Response::new(&mut stream, &mut headers);
|
|
||||||
res.start().unwrap().deconstruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
_date,
|
|
||||||
_transfer_encoding,
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_streaming_end() {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = MockStream::new();
|
|
||||||
{
|
|
||||||
let res = Response::new(&mut stream, &mut headers);
|
|
||||||
res.start().unwrap().end().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
_date,
|
|
||||||
_transfer_encoding,
|
|
||||||
"",
|
|
||||||
"0",
|
|
||||||
"" // empty zero body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fresh_drop() {
|
|
||||||
use status::StatusCode;
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = MockStream::new();
|
|
||||||
{
|
|
||||||
let mut res = Response::new(&mut stream, &mut headers);
|
|
||||||
*res.status_mut() = StatusCode::NotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 404 Not Found",
|
|
||||||
_date,
|
|
||||||
_transfer_encoding,
|
|
||||||
"",
|
|
||||||
"0",
|
|
||||||
"" // empty zero body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 windows msvc does not support unwinding
|
|
||||||
// See https://github.com/rust-lang/rust/issues/25869
|
|
||||||
#[cfg(not(all(windows, target_arch="x86", target_env="msvc")))]
|
|
||||||
#[test]
|
|
||||||
fn test_fresh_drop_panicing() {
|
|
||||||
use std::thread;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use status::StatusCode;
|
|
||||||
|
|
||||||
let stream = MockStream::new();
|
|
||||||
let stream = Arc::new(Mutex::new(stream));
|
|
||||||
let inner_stream = stream.clone();
|
|
||||||
let join_handle = thread::spawn(move || {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = inner_stream.lock().unwrap();
|
|
||||||
let mut res = Response::new(&mut *stream, &mut headers);
|
|
||||||
*res.status_mut() = StatusCode::NotFound;
|
|
||||||
|
|
||||||
panic!("inside")
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(join_handle.join().is_err());
|
|
||||||
|
|
||||||
let stream = match stream.lock() {
|
|
||||||
Err(poisoned) => poisoned.into_inner().clone(),
|
|
||||||
Ok(_) => unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 500 Internal Server Error",
|
|
||||||
_date,
|
|
||||||
_transfer_encoding,
|
|
||||||
"",
|
|
||||||
"0",
|
|
||||||
"" // empty zero body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_streaming_drop() {
|
|
||||||
use std::io::Write;
|
|
||||||
use status::StatusCode;
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = MockStream::new();
|
|
||||||
{
|
|
||||||
let mut res = Response::new(&mut stream, &mut headers);
|
|
||||||
*res.status_mut() = StatusCode::NotFound;
|
|
||||||
let mut stream = res.start().unwrap();
|
|
||||||
stream.write_all(b"foo").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 404 Not Found",
|
|
||||||
_date,
|
|
||||||
_transfer_encoding,
|
|
||||||
"",
|
|
||||||
"3",
|
|
||||||
"foo",
|
|
||||||
"0",
|
|
||||||
"" // empty zero body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_no_content() {
|
|
||||||
use status::StatusCode;
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
let mut stream = MockStream::new();
|
|
||||||
{
|
|
||||||
let mut res = Response::new(&mut stream, &mut headers);
|
|
||||||
*res.status_mut() = StatusCode::NoContent;
|
|
||||||
res.start().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
lines! { stream =
|
|
||||||
"HTTP/1.1 204 No Content",
|
|
||||||
_date,
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,6 +547,12 @@ impl Ord for StatusCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for StatusCode {
|
||||||
|
fn default() -> StatusCode {
|
||||||
|
StatusCode::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The class of an HTTP `status-code`.
|
/// The class of an HTTP `status-code`.
|
||||||
///
|
///
|
||||||
/// [RFC 7231, section 6 (Response Status Codes)](https://tools.ietf.org/html/rfc7231#section-6):
|
/// [RFC 7231, section 6 (Response Status Codes)](https://tools.ietf.org/html/rfc7231#section-6):
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ pub enum RequestUri {
|
|||||||
Star,
|
Star,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RequestUri {
|
||||||
|
fn default() -> RequestUri {
|
||||||
|
RequestUri::Star
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for RequestUri {
|
impl FromStr for RequestUri {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ impl FromStr for RequestUri {
|
|||||||
let mut temp = "http://".to_owned();
|
let mut temp = "http://".to_owned();
|
||||||
temp.push_str(s);
|
temp.push_str(s);
|
||||||
try!(Url::parse(&temp[..]));
|
try!(Url::parse(&temp[..]));
|
||||||
todo!("compare vs u.authority()");
|
//TODO: compare vs u.authority()?
|
||||||
Ok(RequestUri::Authority(s.to_owned()))
|
Ok(RequestUri::Authority(s.to_owned()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! the `HttpVersion` enum.
|
//! the `HttpVersion` enum.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use self::HttpVersion::{Http09, Http10, Http11, Http20};
|
use self::HttpVersion::{Http09, Http10, Http11, H2, H2c};
|
||||||
|
|
||||||
/// Represents a version of the HTTP spec.
|
/// Represents a version of the HTTP spec.
|
||||||
#[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash, Debug)]
|
#[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash, Debug)]
|
||||||
@@ -15,8 +15,10 @@ pub enum HttpVersion {
|
|||||||
Http10,
|
Http10,
|
||||||
/// `HTTP/1.1`
|
/// `HTTP/1.1`
|
||||||
Http11,
|
Http11,
|
||||||
/// `HTTP/2.0`
|
/// `HTTP/2.0` over TLS
|
||||||
Http20
|
H2,
|
||||||
|
/// `HTTP/2.0` over cleartext
|
||||||
|
H2c,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HttpVersion {
|
impl fmt::Display for HttpVersion {
|
||||||
@@ -25,7 +27,14 @@ impl fmt::Display for HttpVersion {
|
|||||||
Http09 => "HTTP/0.9",
|
Http09 => "HTTP/0.9",
|
||||||
Http10 => "HTTP/1.0",
|
Http10 => "HTTP/1.0",
|
||||||
Http11 => "HTTP/1.1",
|
Http11 => "HTTP/1.1",
|
||||||
Http20 => "HTTP/2.0",
|
H2 => "h2",
|
||||||
|
H2c => "h2c",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for HttpVersion {
|
||||||
|
fn default() -> HttpVersion {
|
||||||
|
Http11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
205
tests/client.rs
Normal file
205
tests/client.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#![deny(warnings)]
|
||||||
|
extern crate hyper;
|
||||||
|
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::net::TcpListener;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use hyper::client::{Handler, Request, Response, HttpConnector};
|
||||||
|
use hyper::header;
|
||||||
|
use hyper::{Method, StatusCode, Next, Encoder, Decoder};
|
||||||
|
use hyper::net::HttpStream;
|
||||||
|
|
||||||
|
fn s(bytes: &[u8]) -> &str {
|
||||||
|
::std::str::from_utf8(bytes.as_ref()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TestHandler {
|
||||||
|
opts: Opts,
|
||||||
|
tx: mpsc::Sender<Msg>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestHandler {
|
||||||
|
fn new(opts: Opts) -> (TestHandler, mpsc::Receiver<Msg>) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
(TestHandler {
|
||||||
|
opts: opts,
|
||||||
|
tx: tx
|
||||||
|
}, rx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Msg {
|
||||||
|
Head(Response),
|
||||||
|
Chunk(Vec<u8>),
|
||||||
|
Error(hyper::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(opts: &Opts) -> Next {
|
||||||
|
if let Some(timeout) = opts.read_timeout {
|
||||||
|
Next::read().timeout(timeout)
|
||||||
|
} else {
|
||||||
|
Next::read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<HttpStream> for TestHandler {
|
||||||
|
fn on_request(&mut self, req: &mut Request) -> Next {
|
||||||
|
req.set_method(self.opts.method.clone());
|
||||||
|
read(&self.opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
read(&self.opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response(&mut self, res: Response) -> Next {
|
||||||
|
use hyper::header;
|
||||||
|
// server responses can include a body until eof, if not size is specified
|
||||||
|
let mut has_body = true;
|
||||||
|
if let Some(len) = res.headers().get::<header::ContentLength>() {
|
||||||
|
if **len == 0 {
|
||||||
|
has_body = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tx.send(Msg::Head(res)).unwrap();
|
||||||
|
if has_body {
|
||||||
|
read(&self.opts)
|
||||||
|
} else {
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
let mut v = vec![0; 512];
|
||||||
|
match decoder.read(&mut v) {
|
||||||
|
Ok(n) => {
|
||||||
|
v.truncate(n);
|
||||||
|
self.tx.send(Msg::Chunk(v)).unwrap();
|
||||||
|
if n == 0 {
|
||||||
|
Next::end()
|
||||||
|
} else {
|
||||||
|
read(&self.opts)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => read(&self.opts),
|
||||||
|
_ => panic!("io read error: {:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&mut self, err: hyper::Error) -> Next {
|
||||||
|
self.tx.send(Msg::Error(err)).unwrap();
|
||||||
|
Next::remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Client {
|
||||||
|
client: Option<hyper::Client<TestHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Opts {
|
||||||
|
method: Method,
|
||||||
|
read_timeout: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Opts {
|
||||||
|
fn default() -> Opts {
|
||||||
|
Opts {
|
||||||
|
method: Method::Get,
|
||||||
|
read_timeout: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opts() -> Opts {
|
||||||
|
Opts::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opts {
|
||||||
|
fn method(mut self, method: Method) -> Opts {
|
||||||
|
self.method = method;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_timeout(mut self, timeout: Duration) -> Opts {
|
||||||
|
self.read_timeout = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
fn request<U>(&self, url: U, opts: Opts) -> mpsc::Receiver<Msg>
|
||||||
|
where U: AsRef<str> {
|
||||||
|
let (handler, rx) = TestHandler::new(opts);
|
||||||
|
self.client.as_ref().unwrap()
|
||||||
|
.request(url.as_ref().parse().unwrap(), handler).unwrap();
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Client {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.client.take().map(|c| c.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client() -> Client {
|
||||||
|
let c = hyper::Client::<TestHandler>::configure()
|
||||||
|
.connector(HttpConnector::default())
|
||||||
|
.build().unwrap();
|
||||||
|
Client {
|
||||||
|
client: Some(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_get() {
|
||||||
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = server.local_addr().unwrap();
|
||||||
|
let client = client();
|
||||||
|
let res = client.request(format!("http://{}/", addr), opts().method(Method::Get));
|
||||||
|
|
||||||
|
let mut inc = server.accept().unwrap().0;
|
||||||
|
inc.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
inc.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
let n = inc.read(&mut buf).unwrap();
|
||||||
|
let expected = format!("GET / HTTP/1.1\r\nHost: {}\r\n\r\n", addr);
|
||||||
|
assert_eq!(s(&buf[..n]), expected);
|
||||||
|
|
||||||
|
inc.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap();
|
||||||
|
|
||||||
|
if let Msg::Head(head) = res.recv().unwrap() {
|
||||||
|
assert_eq!(head.status(), &StatusCode::Ok);
|
||||||
|
assert_eq!(head.headers().get(), Some(&header::ContentLength(0)));
|
||||||
|
} else {
|
||||||
|
panic!("we lost the head!");
|
||||||
|
}
|
||||||
|
//drop(inc);
|
||||||
|
|
||||||
|
assert!(res.recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_read_timeout() {
|
||||||
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = server.local_addr().unwrap();
|
||||||
|
let client = client();
|
||||||
|
let res = client.request(format!("http://{}/", addr), opts().read_timeout(Duration::from_secs(3)));
|
||||||
|
|
||||||
|
let mut inc = server.accept().unwrap().0;
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
inc.read(&mut buf).unwrap();
|
||||||
|
|
||||||
|
match res.recv() {
|
||||||
|
Ok(Msg::Error(hyper::Error::Timeout)) => (),
|
||||||
|
other => panic!("expected timeout, actual: {:?}", other)
|
||||||
|
}
|
||||||
|
}
|
||||||
379
tests/server.rs
Normal file
379
tests/server.rs
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
#![deny(warnings)]
|
||||||
|
extern crate hyper;
|
||||||
|
|
||||||
|
use std::net::{TcpStream, SocketAddr};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use hyper::{Next, Encoder, Decoder};
|
||||||
|
use hyper::net::HttpStream;
|
||||||
|
use hyper::server::{Server, Handler, Request, Response};
|
||||||
|
|
||||||
|
struct Serve {
|
||||||
|
listening: Option<hyper::server::Listening>,
|
||||||
|
msg_rx: mpsc::Receiver<Msg>,
|
||||||
|
reply_tx: mpsc::Sender<Reply>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serve {
|
||||||
|
fn addr(&self) -> &SocketAddr {
|
||||||
|
self.listening.as_ref().unwrap().addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn head(&self) -> Request {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn body(&self) -> Vec<u8> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
while let Ok(Msg::Chunk(msg)) = self.msg_rx.try_recv() {
|
||||||
|
buf.extend(&msg);
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply(&self) -> ReplyBuilder {
|
||||||
|
ReplyBuilder {
|
||||||
|
tx: &self.reply_tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReplyBuilder<'a> {
|
||||||
|
tx: &'a mpsc::Sender<Reply>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ReplyBuilder<'a> {
|
||||||
|
fn status(self, status: hyper::StatusCode) -> Self {
|
||||||
|
self.tx.send(Reply::Status(status)).unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header<H: hyper::header::Header>(self, header: H) -> Self {
|
||||||
|
let mut headers = hyper::Headers::new();
|
||||||
|
headers.set(header);
|
||||||
|
self.tx.send(Reply::Headers(headers)).unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body<T: AsRef<[u8]>>(self, body: T) {
|
||||||
|
self.tx.send(Reply::Body(body.as_ref().into())).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Serve {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.listening.take().unwrap().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestHandler {
|
||||||
|
tx: mpsc::Sender<Msg>,
|
||||||
|
rx: mpsc::Receiver<Reply>,
|
||||||
|
peeked: Option<Vec<u8>>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Reply {
|
||||||
|
Status(hyper::StatusCode),
|
||||||
|
Headers(hyper::Headers),
|
||||||
|
Body(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
//Head(Request),
|
||||||
|
Chunk(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestHandler {
|
||||||
|
fn next(&self, next: Next) -> Next {
|
||||||
|
if let Some(dur) = self.timeout {
|
||||||
|
next.timeout(dur)
|
||||||
|
} else {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<HttpStream> for TestHandler {
|
||||||
|
fn on_request(&mut self, _req: Request) -> Next {
|
||||||
|
//self.tx.send(Msg::Head(req)).unwrap();
|
||||||
|
self.next(Next::read())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
let mut vec = vec![0; 1024];
|
||||||
|
match decoder.read(&mut vec) {
|
||||||
|
Ok(0) => {
|
||||||
|
self.next(Next::write())
|
||||||
|
}
|
||||||
|
Ok(n) => {
|
||||||
|
vec.truncate(n);
|
||||||
|
self.tx.send(Msg::Chunk(vec)).unwrap();
|
||||||
|
self.next(Next::read())
|
||||||
|
}
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => self.next(Next::read()),
|
||||||
|
_ => panic!("test error: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||||
|
loop {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Reply::Status(s)) => {
|
||||||
|
res.set_status(s);
|
||||||
|
},
|
||||||
|
Ok(Reply::Headers(headers)) => {
|
||||||
|
use std::iter::Extend;
|
||||||
|
res.headers_mut().extend(headers.iter());
|
||||||
|
},
|
||||||
|
Ok(Reply::Body(body)) => {
|
||||||
|
self.peeked = Some(body);
|
||||||
|
},
|
||||||
|
Err(..) => {
|
||||||
|
return if self.peeked.is_some() {
|
||||||
|
self.next(Next::write())
|
||||||
|
} else {
|
||||||
|
self.next(Next::end())
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
match self.peeked {
|
||||||
|
Some(ref body) => {
|
||||||
|
encoder.write(body).unwrap();
|
||||||
|
self.next(Next::end())
|
||||||
|
},
|
||||||
|
None => self.next(Next::end())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serve() -> Serve {
|
||||||
|
serve_with_timeout(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serve_with_timeout(dur: Option<Duration>) -> Serve {
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
let (msg_tx, msg_rx) = mpsc::channel();
|
||||||
|
let (reply_tx, reply_rx) = mpsc::channel();
|
||||||
|
let mut reply_rx = Some(reply_rx);
|
||||||
|
let (listening, server) = Server::http(&"127.0.0.1:0".parse().unwrap()).unwrap()
|
||||||
|
.handle(move |_| TestHandler {
|
||||||
|
tx: msg_tx.clone(),
|
||||||
|
timeout: dur,
|
||||||
|
rx: reply_rx.take().unwrap(),
|
||||||
|
peeked: None,
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let thread_name = format!("test-server-{}: {:?}", listening.addr(), dur);
|
||||||
|
thread::Builder::new().name(thread_name).spawn(move || {
|
||||||
|
server.run();
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
Serve {
|
||||||
|
listening: Some(listening),
|
||||||
|
msg_rx: msg_rx,
|
||||||
|
reply_tx: reply_tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_get_should_ignore_body() {
|
||||||
|
let server = serve();
|
||||||
|
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
\r\n\
|
||||||
|
I shouldn't be read.\r\n\
|
||||||
|
").unwrap();
|
||||||
|
req.read(&mut [0; 256]).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(server.body(), b"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_get_with_body() {
|
||||||
|
let server = serve();
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Content-Length: 19\r\n\
|
||||||
|
\r\n\
|
||||||
|
I'm a good request.\r\n\
|
||||||
|
").unwrap();
|
||||||
|
req.read(&mut [0; 256]).unwrap();
|
||||||
|
|
||||||
|
// note: doesnt include trailing \r\n, cause Content-Length wasn't 21
|
||||||
|
assert_eq!(server.body(), b"I'm a good request.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_get_fixed_response() {
|
||||||
|
let foo_bar = b"foo bar baz";
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
||||||
|
.body(foo_bar);
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
|
\r\n\
|
||||||
|
").unwrap();
|
||||||
|
let mut body = String::new();
|
||||||
|
req.read_to_string(&mut body).unwrap();
|
||||||
|
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||||
|
|
||||||
|
assert_eq!(&body[n..], "foo bar baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_get_chunked_response() {
|
||||||
|
let foo_bar = b"foo bar baz";
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.header(hyper::header::TransferEncoding::chunked())
|
||||||
|
.body(foo_bar);
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
|
\r\n\
|
||||||
|
").unwrap();
|
||||||
|
let mut body = String::new();
|
||||||
|
req.read_to_string(&mut body).unwrap();
|
||||||
|
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||||
|
|
||||||
|
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_post_with_chunked_body() {
|
||||||
|
let server = serve();
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
POST / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Transfer-Encoding: chunked\r\n\
|
||||||
|
\r\n\
|
||||||
|
1\r\n\
|
||||||
|
q\r\n\
|
||||||
|
2\r\n\
|
||||||
|
we\r\n\
|
||||||
|
2\r\n\
|
||||||
|
rt\r\n\
|
||||||
|
0\r\n\
|
||||||
|
\r\n
|
||||||
|
").unwrap();
|
||||||
|
req.read(&mut [0; 256]).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(server.body(), b"qwert");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn server_empty_response() {
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok);
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
|
\r\n\
|
||||||
|
").unwrap();
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(response, "foo");
|
||||||
|
assert!(!response.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
|
|
||||||
|
let mut lines = response.lines();
|
||||||
|
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||||
|
|
||||||
|
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||||
|
assert_eq!(lines.next(), Some(""));
|
||||||
|
assert_eq!(lines.next(), None);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_empty_response_chunked() {
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.body("");
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
|
\r\n\
|
||||||
|
").unwrap();
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
|
||||||
|
assert!(response.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
|
|
||||||
|
let mut lines = response.lines();
|
||||||
|
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||||
|
|
||||||
|
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||||
|
assert_eq!(lines.next(), Some(""));
|
||||||
|
// 0\r\n\r\n
|
||||||
|
assert_eq!(lines.next(), Some("0"));
|
||||||
|
assert_eq!(lines.next(), Some(""));
|
||||||
|
assert_eq!(lines.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_empty_response_chunked_without_calling_write() {
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.header(hyper::header::TransferEncoding::chunked());
|
||||||
|
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
|
\r\n\
|
||||||
|
").unwrap();
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
|
||||||
|
assert!(response.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
|
|
||||||
|
let mut lines = response.lines();
|
||||||
|
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||||
|
|
||||||
|
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||||
|
assert_eq!(lines.next(), Some(""));
|
||||||
|
// 0\r\n\r\n
|
||||||
|
assert_eq!(lines.next(), Some("0"));
|
||||||
|
assert_eq!(lines.next(), Some(""));
|
||||||
|
assert_eq!(lines.next(), None);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user