feat(lib): redesign API to use Futures and Tokio
There are many changes involved with this, but let's just talk about
user-facing changes.
- Creating a `Client` and `Server` now needs a Tokio `Core` event loop
to attach to.
- `Request` and `Response` both no longer implement the
`std::io::{Read,Write}` traits, but instead represent their bodies as a
`futures::Stream` of items, where each item is a `Chunk`.
- The `Client.request` method now takes a `Request`, instead of being
used as a builder, and returns a `Future` that resolves to `Response`.
- The `Handler` trait for servers is no more, and instead the Tokio
`Service` trait is used. This allows interoperability with generic
middleware.
BREAKING CHANGE: A big sweeping set of breaking changes.
This commit is contained in:
@@ -2,15 +2,10 @@ language: rust
|
|||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- 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
|
||||||
- rust: stable
|
- rust: stable
|
||||||
- rust: stable
|
|
||||||
env: FEATURES="--no-default-features"
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
apt: true
|
apt: true
|
||||||
|
|||||||
35
Cargo.toml
35
Cargo.toml
@@ -7,49 +7,34 @@ readme = "README.md"
|
|||||||
documentation = "http://hyperium.github.io/hyper"
|
documentation = "http://hyperium.github.io/hyper"
|
||||||
repository = "https://github.com/hyperium/hyper"
|
repository = "https://github.com/hyperium/hyper"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Sean McArthur <sean.monstar@gmail.com>",
|
authors = ["Sean McArthur <sean.monstar@gmail.com>"]
|
||||||
"Jonathan Reem <jonathan.reem@gmail.com>"]
|
|
||||||
keywords = ["http", "hyper", "hyperium"]
|
keywords = ["http", "hyper", "hyperium"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
futures = "0.1.7"
|
||||||
|
futures-cpupool = "0.1"
|
||||||
httparse = "1.0"
|
httparse = "1.0"
|
||||||
language-tags = "0.2"
|
language-tags = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
mime = "0.2"
|
mime = "0.2"
|
||||||
rotor = "0.6"
|
relay = "0.1"
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
spmc = "0.2"
|
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
|
tokio-core = "0.1"
|
||||||
|
tokio-proto = "0.1"
|
||||||
|
tokio-service = "0.1"
|
||||||
unicase = "1.0"
|
unicase = "1.0"
|
||||||
url = "1.0"
|
url = "1.0"
|
||||||
vecio = "0.1"
|
|
||||||
|
|
||||||
[dependencies.cookie]
|
[dependencies.cookie]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.openssl]
|
|
||||||
version = "0.7"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.openssl-verify]
|
|
||||||
version = "0.1"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.security-framework]
|
|
||||||
version = "0.1.4"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.serde]
|
|
||||||
version = "0.8"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.3"
|
|
||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
|
pretty_env_logger = "0.1"
|
||||||
|
spmc = "0.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ssl"]
|
default = []
|
||||||
ssl = ["openssl", "openssl-verify"]
|
|
||||||
serde-serialization = ["serde", "mime/serde"]
|
|
||||||
nightly = []
|
nightly = []
|
||||||
|
|||||||
61
benches/end_to_end.rs
Normal file
61
benches/end_to_end.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate futures;
|
||||||
|
extern crate hyper;
|
||||||
|
extern crate tokio_core;
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
|
use hyper::header::{ContentLength, ContentType};
|
||||||
|
use hyper::server::{Service, Request, Response};
|
||||||
|
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn one_request_at_a_time(b: &mut test::Bencher) {
|
||||||
|
extern crate pretty_env_logger;
|
||||||
|
let _ = pretty_env_logger::init();
|
||||||
|
let mut core = Core::new().unwrap();
|
||||||
|
let handle = core.handle();
|
||||||
|
|
||||||
|
let addr = hyper::Server::http(&"127.0.0.1:0".parse().unwrap(), &handle).unwrap()
|
||||||
|
.handle(|| Ok(Hello), &handle).unwrap();
|
||||||
|
|
||||||
|
let mut client = hyper::Client::new(&handle);
|
||||||
|
|
||||||
|
let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap();
|
||||||
|
|
||||||
|
b.bytes = 160;
|
||||||
|
b.iter(move || {
|
||||||
|
let work = client.get(url.clone()).and_then(|res| {
|
||||||
|
res.body().for_each(|_chunk| {
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
core.run(work).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static PHRASE: &'static [u8] = b"Hello, World!";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Hello;
|
||||||
|
|
||||||
|
impl Service for Hello {
|
||||||
|
type Request = Request;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = hyper::Error;
|
||||||
|
type Future = ::futures::Finished<Response, hyper::Error>;
|
||||||
|
fn call(&mut self, _req: Request) -> Self::Future {
|
||||||
|
::futures::finished(
|
||||||
|
Response::new()
|
||||||
|
.with_header(ContentLength(PHRASE.len() as u64))
|
||||||
|
.with_header(ContentType::plaintext())
|
||||||
|
.with_body(PHRASE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,387 +1,3 @@
|
|||||||
% Server Guide
|
% Server Guide
|
||||||
|
|
||||||
# Hello, World
|
|
||||||
|
|
||||||
Let's start off by creating a simple server to just serve a text response
|
|
||||||
of "Hello, World!" to every request.
|
|
||||||
|
|
||||||
```no_run
|
|
||||||
extern crate hyper;
|
|
||||||
use hyper::{Decoder, Encoder, HttpStream as Http, Next};
|
|
||||||
use hyper::server::{Server, Handler, Request, Response};
|
|
||||||
|
|
||||||
struct Text(&'static [u8]);
|
|
||||||
|
|
||||||
impl Handler<Http> for Text {
|
|
||||||
fn on_request(&mut self, _req: Request<Http>) -> Next {
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
|
||||||
use hyper::header::ContentLength;
|
|
||||||
res.headers_mut().set(ContentLength(self.0.len() as u64));
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
|
||||||
encoder.write(self.0).unwrap(); // for now
|
|
||||||
Next::end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
|
||||||
let (listening, server) = Server::http(&addr).unwrap()
|
|
||||||
.handle(|_| Text(b"Hello, World")).unwrap();
|
|
||||||
|
|
||||||
println!("Listening on http://{}", listening);
|
|
||||||
server.run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There is quite a few concepts here, so let's tackle them one by one.
|
|
||||||
|
|
||||||
## Handler
|
|
||||||
|
|
||||||
The [`Handler`][Handler] is how you define what should happen during the lifetime
|
|
||||||
of an HTTP message. We've implemented it for the `Text`, defining what should
|
|
||||||
happen at each event during an HTTP message.
|
|
||||||
|
|
||||||
## Next
|
|
||||||
|
|
||||||
Every event in the [`Handler`][Handler] returns a [`Next`][Next]. This signals
|
|
||||||
to hyper what the `Handler` would wishes to do next, and hyper will call the
|
|
||||||
appropriate method of the `Handler` when the action is ready again.
|
|
||||||
|
|
||||||
So, in our "Hello World" server, you'll notice that when a request comes in, we
|
|
||||||
have no interest in the `Request` or its body. We immediately just wish to write
|
|
||||||
"Hello, World!", and be done. So, in `on_request`, we return `Next::write()`,
|
|
||||||
which tells hyper we wish to write the response.
|
|
||||||
|
|
||||||
After `on_response` is called, we ask for `Next::write()` again, because we
|
|
||||||
still need to write the response body. hyper knows that the next time the
|
|
||||||
transport is ready to be written, since it already called `on_response`, it
|
|
||||||
will call `on_response_writable`, which is where we can write the text body.
|
|
||||||
|
|
||||||
Once we're all done with the response, we can tell hyper to finish by returning
|
|
||||||
`Next::end()`. hyper will try to finish flushing all the output, and if the
|
|
||||||
conditions are met, it may try to use the underlying transport for another
|
|
||||||
request. This is also known as "keep-alive".
|
|
||||||
|
|
||||||
## Server
|
|
||||||
|
|
||||||
In the `main` function, a [`Server`][Server] is created that will utilize our
|
|
||||||
`Hello` handler. We use the default options, though you may wish to peruse
|
|
||||||
them, especially the `max_sockets` option, as it is conservative by default.
|
|
||||||
|
|
||||||
We pass a constructor closure to `Server.handle`, which constructs a `Handler`
|
|
||||||
to be used for each incoming request.
|
|
||||||
|
|
||||||
# Non-blocking IO
|
|
||||||
|
|
||||||
## Don't Panic
|
|
||||||
|
|
||||||
There is actually a very bad practice in the "Hello, World" example. The usage
|
|
||||||
of `decoder.write(x).unwrap()` will panic if the write operation fails. A panic
|
|
||||||
will take down the whole thread, which means the event loop and all other
|
|
||||||
in-progress requests. So don't do it. It's bad.
|
|
||||||
|
|
||||||
What makes it worse, is that the write operation is much more likely to fail
|
|
||||||
when using non-blocking IO. If the write would block the thread, instead of
|
|
||||||
doing so, it will return an `io::Error` with the `kind` of `WouldBlock`. These
|
|
||||||
are expected errors.
|
|
||||||
|
|
||||||
## WouldBlock
|
|
||||||
|
|
||||||
Instead, we should inspect when there is a read or write error to see if the
|
|
||||||
`kind` was a `WouldBlock` error. Since `WouldBlock` is so common when using
|
|
||||||
non-blocking IO, the `Encoder` and `Decoder` provide `try_` methods that will
|
|
||||||
special case `WouldBlock`, allowing you to treat all `Err` cases as actual
|
|
||||||
errors.
|
|
||||||
|
|
||||||
Additionally, it's possible there was a partial write of the response body, so
|
|
||||||
we should probably change the example to keep track of it's progress. Can you
|
|
||||||
see how we should change the example to better handle these conditions?
|
|
||||||
|
|
||||||
This will just show the updated `on_response_writable` method, the rest stays
|
|
||||||
the same:
|
|
||||||
|
|
||||||
```no_run
|
|
||||||
# extern crate hyper;
|
|
||||||
# use hyper::{Encoder, HttpStream as Http, Next};
|
|
||||||
|
|
||||||
# struct Text(&'static [u8]);
|
|
||||||
|
|
||||||
# impl Text {
|
|
||||||
fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
|
||||||
match encoder.try_write(self.0) {
|
|
||||||
Ok(Some(n)) => {
|
|
||||||
if n == self.0.len() {
|
|
||||||
// all done!
|
|
||||||
Next::end()
|
|
||||||
} else {
|
|
||||||
// a partial write!
|
|
||||||
// with a static array, we can just move our pointer
|
|
||||||
// another option could be to store a separate index field
|
|
||||||
self.0 = &self.0[n..];
|
|
||||||
// there's still more to write, so ask to write again
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(None) => {
|
|
||||||
// would block, ask to write again
|
|
||||||
Next::write()
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
println!("oh noes, we cannot say hello! {}", e);
|
|
||||||
// remove (kill) this transport
|
|
||||||
Next::remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# }
|
|
||||||
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Routing
|
|
||||||
|
|
||||||
What if we wanted to serve different messages depending on the URL of the
|
|
||||||
request? Say, we wanted to respond with "Hello, World!" to `/hello`, but
|
|
||||||
"Good-bye" with `/bye`. Let's adjust our example to do that.
|
|
||||||
|
|
||||||
```no_run
|
|
||||||
extern crate hyper;
|
|
||||||
use hyper::{Decoder, Encoder, HttpStream as Http, Next, StatusCode};
|
|
||||||
use hyper::server::{Server, Handler, Request, Response};
|
|
||||||
|
|
||||||
struct Text(StatusCode, &'static [u8]);
|
|
||||||
|
|
||||||
impl Handler<Http> for Text {
|
|
||||||
fn on_request(&mut self, req: Request<Http>) -> Next {
|
|
||||||
use hyper::RequestUri;
|
|
||||||
let path = match *req.uri() {
|
|
||||||
RequestUri::AbsolutePath { path: ref p, .. } => p,
|
|
||||||
RequestUri::AbsoluteUri(ref url) => url.path(),
|
|
||||||
// other 2 forms are for CONNECT and OPTIONS methods
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
match path {
|
|
||||||
"/hello" => {
|
|
||||||
self.1 = b"Hello, World!";
|
|
||||||
},
|
|
||||||
"/bye" => {
|
|
||||||
self.1 = b"Good-bye";
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
self.0 = StatusCode::NotFound;
|
|
||||||
self.1 = b"Not Found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
|
||||||
# Next::write()
|
|
||||||
# }
|
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
|
||||||
use hyper::header::ContentLength;
|
|
||||||
// we send an HTTP Status Code, 200 OK, or 404 Not Found
|
|
||||||
res.set_status(self.0);
|
|
||||||
res.headers_mut().set(ContentLength(self.1.len() as u64));
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
|
||||||
# match encoder.try_write(self.1) {
|
|
||||||
# Ok(Some(n)) => {
|
|
||||||
# if n == self.1.len() {
|
|
||||||
# Next::end()
|
|
||||||
# } else {
|
|
||||||
# self.1 = &self.1[n..];
|
|
||||||
# Next::write()
|
|
||||||
# }
|
|
||||||
# },
|
|
||||||
# Ok(None) => {
|
|
||||||
# Next::write()
|
|
||||||
# },
|
|
||||||
# Err(e) => {
|
|
||||||
# println!("oh noes, we cannot say hello! {}", e);
|
|
||||||
# Next::remove()
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
|
||||||
let (listening, server) = Server::http(&addr).unwrap()
|
|
||||||
.handle(|_| Text(StatusCode::Ok, b"")).unwrap();
|
|
||||||
|
|
||||||
println!("Listening on http://{}", listening);
|
|
||||||
server.run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Waiting
|
|
||||||
|
|
||||||
More often than not, a server needs to something "expensive" before it can
|
|
||||||
provide a response to a request. This may be talking to a database, reading
|
|
||||||
a file, processing an image, sending its own HTTP request to another server,
|
|
||||||
or anything else that would impede the event loop thread. These sorts of actions
|
|
||||||
should be done off the event loop thread, when complete, should notify hyper
|
|
||||||
that it can now proceed. This is done by combining `Next::wait()` and the
|
|
||||||
[`Control`][Control].
|
|
||||||
|
|
||||||
## Control
|
|
||||||
|
|
||||||
The `Control` is provided to the `Handler` constructor; it is the argument we
|
|
||||||
have so far been ignoring. It's not needed if we don't ever need to wait a
|
|
||||||
transport. The `Control` is usually sent to a queue, or another thread, or
|
|
||||||
wherever makes sense to be able to use it when the "blocking" operations are
|
|
||||||
complete.
|
|
||||||
|
|
||||||
To focus on hyper instead of obscure blocking operations, we'll use this useless
|
|
||||||
sleeping thread to show it works.
|
|
||||||
|
|
||||||
```no_run
|
|
||||||
extern crate hyper;
|
|
||||||
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use hyper::{Control, Next};
|
|
||||||
|
|
||||||
fn calculate_ultimate_question(rx: mpsc::Receiver<(Control, mpsc::Sender<&'static [u8]>)>) {
|
|
||||||
thread::spawn(move || {
|
|
||||||
while let Ok((ctrl, tx)) = rx.recv() {
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
tx.send(b"42").unwrap();
|
|
||||||
ctrl.ready(Next::write()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our worker will spawn a thread that waits on messages. When receiving a message,
|
|
||||||
after a short nap, it will send back the "result" of the work, and wake up the
|
|
||||||
waiting transport with a `Next::write()` desire.
|
|
||||||
|
|
||||||
## Wait
|
|
||||||
|
|
||||||
Finally, let's tie in our worker thread into our `Text` handler:
|
|
||||||
|
|
||||||
```no_run
|
|
||||||
extern crate hyper;
|
|
||||||
use hyper::{Control, Decoder, Encoder, HttpStream as Http, Next, StatusCode};
|
|
||||||
use hyper::server::{Server, Handler, Request, Response};
|
|
||||||
|
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
struct Text {
|
|
||||||
status: StatusCode,
|
|
||||||
text: &'static [u8],
|
|
||||||
control: Option<Control>,
|
|
||||||
worker_tx: mpsc::Sender<(Control, mpsc::Sender<&'static [u8]>)>,
|
|
||||||
worker_rx: Option<mpsc::Receiver<&'static [u8]>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<Http> for Text {
|
|
||||||
fn on_request(&mut self, req: Request<Http>) -> Next {
|
|
||||||
use hyper::RequestUri;
|
|
||||||
let path = match *req.uri() {
|
|
||||||
RequestUri::AbsolutePath { path: ref p, .. } => p,
|
|
||||||
RequestUri::AbsoluteUri(ref url) => url.path(),
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
match path {
|
|
||||||
"/hello" => {
|
|
||||||
self.text = b"Hello, World!";
|
|
||||||
},
|
|
||||||
"/bye" => {
|
|
||||||
self.text = b"Good-bye";
|
|
||||||
},
|
|
||||||
"/question" => {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
// queue work on our worker
|
|
||||||
self.worker_tx.send((self.control.take().unwrap(), tx)).unwrap();
|
|
||||||
// save receive channel for response handling
|
|
||||||
self.worker_rx = Some(rx);
|
|
||||||
// tell hyper we need to wait until we can continue
|
|
||||||
return Next::wait();
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.status = StatusCode::NotFound;
|
|
||||||
self.text = b"Not Found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
|
||||||
# Next::write()
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
|
||||||
use hyper::header::ContentLength;
|
|
||||||
res.set_status(self.status);
|
|
||||||
if let Some(rx) = self.worker_rx.take() {
|
|
||||||
self.text = rx.recv().unwrap();
|
|
||||||
}
|
|
||||||
res.headers_mut().set(ContentLength(self.text.len() as u64));
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
|
||||||
# unimplemented!()
|
|
||||||
# }
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn calculate_ultimate_question(rx: mpsc::Receiver<(Control, mpsc::Sender<&'static [u8]>)>) {
|
|
||||||
# use std::sync::mpsc;
|
|
||||||
# use std::thread;
|
|
||||||
# use std::time::Duration;
|
|
||||||
# thread::spawn(move || {
|
|
||||||
# while let Ok((ctrl, tx)) = rx.recv() {
|
|
||||||
# thread::sleep(Duration::from_millis(500));
|
|
||||||
# tx.send(b"42").unwrap();
|
|
||||||
# ctrl.ready(Next::write()).unwrap();
|
|
||||||
# }
|
|
||||||
# });
|
|
||||||
# }
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
calculate_ultimate_question(rx);
|
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
|
||||||
let (listening, server) = Server::http(&addr).unwrap()
|
|
||||||
.handle(move |ctrl| Text {
|
|
||||||
status: StatusCode::Ok,
|
|
||||||
text: b"",
|
|
||||||
control: Some(ctrl),
|
|
||||||
worker_tx: tx.clone(),
|
|
||||||
worker_rx: None,
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
println!("Listening on http://{}", listening);
|
|
||||||
server.run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Control]: ../hyper/struct.Control.html
|
|
||||||
[Handler]: ../hyper/server/trait.Handler.html
|
|
||||||
[Next]: ../hyper/struct.Next.html
|
|
||||||
[Server]: ../hyper/server/struct.Server.html
|
|
||||||
|
|||||||
@@ -1,68 +1,20 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
extern crate futures;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
|
extern crate tokio_core;
|
||||||
|
|
||||||
extern crate env_logger;
|
extern crate pretty_env_logger;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io::{self, Write};
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use hyper::client::{Client, Request, Response, DefaultTransport as HttpStream};
|
use futures::Future;
|
||||||
use hyper::header::Connection;
|
use futures::stream::Stream;
|
||||||
use hyper::{Decoder, Encoder, Next};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
use hyper::Client;
|
||||||
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:example: {}", e);
|
|
||||||
Next::end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_error(&mut self, err: hyper::Error) -> Next {
|
|
||||||
println!("ERROR:example: {}", err);
|
|
||||||
Next::remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
pretty_env_logger::init().unwrap();
|
||||||
|
|
||||||
let url = match env::args().nth(1) {
|
let url = match env::args().nth(1) {
|
||||||
Some(url) => url,
|
Some(url) => url,
|
||||||
@@ -72,11 +24,26 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let url = hyper::Url::parse(&url).unwrap();
|
||||||
let client = Client::new().expect("Failed to create a Client");
|
if url.scheme() != "http" {
|
||||||
client.request(url.parse().unwrap(), Dump(tx)).unwrap();
|
println!("This example only works with 'http' URLs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// wait till done
|
let mut core = tokio_core::reactor::Core::new().unwrap();
|
||||||
let _ = rx.recv();
|
let handle = core.handle();
|
||||||
client.close();
|
let client = Client::new(&handle);
|
||||||
|
|
||||||
|
let work = client.get(url).and_then(|res| {
|
||||||
|
println!("Response: {}", res.status());
|
||||||
|
println!("Headers: \n{}", res.headers());
|
||||||
|
|
||||||
|
res.body().for_each(|chunk| {
|
||||||
|
io::stdout().write_all(&chunk).map_err(From::from)
|
||||||
|
})
|
||||||
|
}).map(|_| {
|
||||||
|
println!("\n\nDone.");
|
||||||
|
});
|
||||||
|
|
||||||
|
core.run(work).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,39 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate env_logger;
|
extern crate futures;
|
||||||
extern crate num_cpus;
|
extern crate pretty_env_logger;
|
||||||
|
//extern crate num_cpus;
|
||||||
|
|
||||||
use hyper::{Decoder, Encoder, Next, HttpStream};
|
use hyper::header::{ContentLength, ContentType};
|
||||||
use hyper::server::{Server, Handler, Request, Response, HttpListener};
|
use hyper::server::{Server, Service, Request, Response};
|
||||||
|
|
||||||
static PHRASE: &'static [u8] = b"Hello World!";
|
static PHRASE: &'static [u8] = b"Hello World!";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
struct Hello;
|
struct Hello;
|
||||||
|
|
||||||
impl Handler<HttpStream> for Hello {
|
impl Service for Hello {
|
||||||
fn on_request(&mut self, _: Request<HttpStream>) -> Next {
|
type Request = Request;
|
||||||
Next::write()
|
type Response = Response;
|
||||||
}
|
type Error = hyper::Error;
|
||||||
fn on_request_readable(&mut self, _: &mut Decoder<HttpStream>) -> Next {
|
type Future = ::futures::Finished<Response, hyper::Error>;
|
||||||
Next::write()
|
fn call(&self, _req: Request) -> Self::Future {
|
||||||
}
|
::futures::finished(
|
||||||
fn on_response(&mut self, response: &mut Response) -> Next {
|
Response::new()
|
||||||
use hyper::header::ContentLength;
|
.with_header(ContentLength(PHRASE.len() as u64))
|
||||||
response.headers_mut().set(ContentLength(PHRASE.len() as u64));
|
.with_header(ContentType::plaintext())
|
||||||
Next::write()
|
.with_body(PHRASE)
|
||||||
}
|
)
|
||||||
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();
|
pretty_env_logger::init().unwrap();
|
||||||
|
let addr = "127.0.0.1:3000".parse().unwrap();
|
||||||
let listener = HttpListener::bind(&"127.0.0.1:3000".parse().unwrap()).unwrap();
|
let _server = Server::standalone(|tokio| {
|
||||||
let mut handles = Vec::new();
|
Server::http(&addr, tokio)?
|
||||||
|
.handle(|| Ok(Hello), tokio)
|
||||||
for _ in 0..num_cpus::get() {
|
}).unwrap();
|
||||||
let listener = listener.try_clone().unwrap();
|
println!("Listening on http://{}", addr);
|
||||||
handles.push(::std::thread::spawn(move || {
|
|
||||||
Server::new(listener)
|
|
||||||
.handle(|_| Hello).unwrap();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
println!("Listening on http://127.0.0.1:3000");
|
|
||||||
|
|
||||||
for handle in handles {
|
|
||||||
handle.join().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +1,57 @@
|
|||||||
#![deny(warnings)]
|
//#![deny(warnings)]
|
||||||
|
extern crate futures;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate env_logger;
|
extern crate pretty_env_logger;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use hyper::{Get, Post, StatusCode, RequestUri, Decoder, Encoder, HttpStream, Next};
|
use hyper::{Get, Post, StatusCode};
|
||||||
use hyper::header::ContentLength;
|
use hyper::header::ContentLength;
|
||||||
use hyper::server::{Server, Handler, Request, Response};
|
use hyper::server::{Server, Service, Request, Response};
|
||||||
|
|
||||||
struct Echo {
|
|
||||||
buf: Vec<u8>,
|
|
||||||
read_pos: usize,
|
|
||||||
write_pos: usize,
|
|
||||||
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";
|
static INDEX: &'static [u8] = b"Try POST /echo";
|
||||||
|
|
||||||
impl Echo {
|
#[derive(Clone, Copy)]
|
||||||
fn new() -> Echo {
|
struct Echo;
|
||||||
Echo {
|
|
||||||
buf: vec![0; 4096],
|
|
||||||
read_pos: 0,
|
|
||||||
write_pos: 0,
|
|
||||||
eof: false,
|
|
||||||
route: Route::NotFound,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<HttpStream> for Echo {
|
impl Service for Echo {
|
||||||
fn on_request(&mut self, req: Request<HttpStream>) -> Next {
|
type Request = Request;
|
||||||
match *req.uri() {
|
type Response = Response;
|
||||||
RequestUri::AbsolutePath { ref path, .. } => match (req.method(), &path[..]) {
|
type Error = hyper::Error;
|
||||||
(&Get, "/") | (&Get, "/echo") => {
|
type Future = ::futures::Finished<Response, hyper::Error>;
|
||||||
info!("GET Index");
|
|
||||||
self.route = Route::Index;
|
fn call(&self, req: Request) -> Self::Future {
|
||||||
Next::write()
|
::futures::finished(match (req.method(), req.path()) {
|
||||||
}
|
(&Get, Some("/")) | (&Get, Some("/echo")) => {
|
||||||
(&Post, "/echo") => {
|
Response::new()
|
||||||
info!("POST Echo");
|
.with_header(ContentLength(INDEX.len() as u64))
|
||||||
let mut is_more = true;
|
.with_body(INDEX)
|
||||||
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(),
|
|
||||||
},
|
},
|
||||||
_ => Next::write()
|
(&Post, Some("/echo")) => {
|
||||||
}
|
let mut res = Response::new();
|
||||||
}
|
if let Some(len) = req.headers().get::<ContentLength>() {
|
||||||
fn on_request_readable(&mut self, transport: &mut Decoder<HttpStream>) -> Next {
|
res.headers_mut().set(len.clone());
|
||||||
match self.route {
|
|
||||||
Route::Echo(ref body) => {
|
|
||||||
if self.read_pos < self.buf.len() {
|
|
||||||
match transport.try_read(&mut self.buf[self.read_pos..]) {
|
|
||||||
Ok(Some(0)) => {
|
|
||||||
debug!("Read 0, eof");
|
|
||||||
self.eof = true;
|
|
||||||
Next::write()
|
|
||||||
},
|
|
||||||
Ok(Some(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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => Next::read_and_write(),
|
|
||||||
Err(e) => {
|
|
||||||
println!("read error {:?}", e);
|
|
||||||
Next::end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Next::write()
|
|
||||||
}
|
}
|
||||||
|
res.with_body(req.body())
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Response::new()
|
||||||
|
.with_status(StatusCode::NotFound)
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
|
||||||
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.try_write(&self.buf[self.write_pos..self.read_pos]) {
|
|
||||||
Ok(Some(0)) => panic!("write ZERO"),
|
|
||||||
Ok(Some(n)) => {
|
|
||||||
self.write_pos += n;
|
|
||||||
Next::write()
|
|
||||||
}
|
|
||||||
Ok(None) => Next::write(),
|
|
||||||
Err(e) => {
|
|
||||||
println!("write error {:?}", e);
|
|
||||||
Next::end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !self.eof {
|
|
||||||
Next::read()
|
|
||||||
} else {
|
|
||||||
Next::end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
pretty_env_logger::init().unwrap();
|
||||||
let server = Server::http(&"127.0.0.1:1337".parse().unwrap()).unwrap();
|
let addr = "127.0.0.1:1337".parse().unwrap();
|
||||||
let (listening, server) = server.handle(|_| Echo::new()).unwrap();
|
let (listening, server) = Server::standalone(|tokio| {
|
||||||
|
Server::http(&addr, tokio)?
|
||||||
|
.handle(|| Ok(Echo), tokio)
|
||||||
|
}).unwrap();
|
||||||
println!("Listening on http://{}", listening);
|
println!("Listening on http://{}", listening);
|
||||||
server.run();
|
server.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
use std::collections::hash_map::{HashMap, Entry};
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
//use std::net::SocketAddr;
|
||||||
|
|
||||||
use rotor::mio::tcp::TcpStream;
|
use futures::{Future, Poll, Async};
|
||||||
|
use tokio::io::Io;
|
||||||
|
use tokio::reactor::Handle;
|
||||||
|
use tokio::net::{TcpStream, TcpStreamNew};
|
||||||
|
use tokio_service::Service;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use net::{HttpStream, HttpsStream, Transport, SslClient};
|
use super::dns;
|
||||||
use super::dns::Dns;
|
|
||||||
use super::Registration;
|
|
||||||
|
|
||||||
/// A connector creates a Transport to a remote address..
|
/// A connector creates an Io to a remote address..
|
||||||
pub trait Connect {
|
///
|
||||||
/// Type of Transport to create
|
/// This trait is not implemented directly, and only exists to make
|
||||||
type Output: Transport;
|
/// the intent clearer. A connector should implement `Service` with
|
||||||
/// The key used to determine if an existing socket can be used.
|
/// `Request=Url` and `Response: Io` instead.
|
||||||
type Key: Eq + Hash + Clone + fmt::Debug;
|
pub trait Connect: Service<Request=Url, Error=io::Error> + 'static {
|
||||||
/// Returns the key based off the Url.
|
/// The connected Io Stream.
|
||||||
fn key(&self, &Url) -> Option<Self::Key>;
|
type Output: Io + 'static;
|
||||||
|
/// A Future that will resolve to the connected Stream.
|
||||||
|
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
|
||||||
/// Connect to a remote address.
|
/// Connect to a remote address.
|
||||||
fn connect(&mut self, &Url) -> io::Result<Self::Key>;
|
fn connect(&self, Url) -> <Self as Connect>::Future;
|
||||||
/// Returns a connected socket and associated host.
|
|
||||||
fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>;
|
|
||||||
#[doc(hidden)]
|
|
||||||
/// Configure number of dns workers to use.
|
|
||||||
fn dns_workers(&mut self, usize);
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn register(&mut self, Registration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A connector for the `http` scheme.
|
impl<T> Connect for T
|
||||||
pub struct HttpConnector {
|
where T: Service<Request=Url, Error=io::Error> + 'static,
|
||||||
dns: Option<Dns>,
|
T::Response: Io,
|
||||||
threads: usize,
|
T::Future: Future<Error=io::Error>,
|
||||||
resolving: HashMap<String, Vec<(&'static str, String, u16)>>,
|
{
|
||||||
}
|
type Output = T::Response;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
impl HttpConnector {
|
fn connect(&self, url: Url) -> <Self as Connect>::Future {
|
||||||
/// Set the number of resolver threads.
|
self.call(url)
|
||||||
///
|
|
||||||
/// 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 {
|
/// A connector for the `http` scheme.
|
||||||
fn default() -> HttpConnector {
|
#[derive(Clone)]
|
||||||
|
pub struct HttpConnector {
|
||||||
|
dns: dns::Dns,
|
||||||
|
handle: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpConnector {
|
||||||
|
|
||||||
|
/// Construct a new HttpConnector.
|
||||||
|
///
|
||||||
|
/// Takes number of DNS worker threads.
|
||||||
|
pub fn new(threads: usize, handle: &Handle) -> HttpConnector {
|
||||||
HttpConnector {
|
HttpConnector {
|
||||||
dns: None,
|
dns: dns::Dns::new(threads),
|
||||||
threads: 4,
|
handle: handle.clone(),
|
||||||
resolving: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,79 +61,115 @@ impl Default for HttpConnector {
|
|||||||
impl fmt::Debug for HttpConnector {
|
impl fmt::Debug for HttpConnector {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.debug_struct("HttpConnector")
|
f.debug_struct("HttpConnector")
|
||||||
.field("threads", &self.threads)
|
|
||||||
.field("resolving", &self.resolving)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connect for HttpConnector {
|
impl Service for HttpConnector {
|
||||||
type Output = HttpStream;
|
type Request = Url;
|
||||||
type Key = (&'static str, String, u16);
|
type Response = TcpStream;
|
||||||
|
type Error = io::Error;
|
||||||
|
type Future = HttpConnecting;
|
||||||
|
|
||||||
fn dns_workers(&mut self, count: usize) {
|
fn call(&self, url: Url) -> Self::Future {
|
||||||
self.threads = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
debug!("Http::connect({:?})", url);
|
||||||
if let Some(key) = self.key(url) {
|
let host = match url.host_str() {
|
||||||
let host = url.host_str().expect("http scheme must have a host");
|
Some(s) => s,
|
||||||
self.dns.as_ref().expect("dns workers lost").resolve(host);
|
None => return HttpConnecting {
|
||||||
self.resolving.entry(host.to_owned()).or_insert_with(Vec::new).push(key.clone());
|
state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, "invalid url"))),
|
||||||
Ok(key)
|
handle: self.handle.clone(),
|
||||||
} else {
|
},
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> {
|
|
||||||
let (host, addrs) = match self.dns.as_ref().expect("dns workers lost").resolved() {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(_) => return None
|
|
||||||
};
|
};
|
||||||
//TODO: try all addrs
|
let port = url.port_or_known_default().unwrap_or(80);
|
||||||
let addr = addrs.and_then(|mut addrs| Ok(addrs.next().unwrap()));
|
|
||||||
debug!("Http::resolved <- ({:?}, {:?})", host, addr);
|
HttpConnecting {
|
||||||
if let Entry::Occupied(mut entry) = self.resolving.entry(host) {
|
state: State::Resolving(self.dns.resolve(host.into(), port)),
|
||||||
let resolved = entry.get_mut().remove(0);
|
handle: self.handle.clone(),
|
||||||
if entry.get().is_empty() {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
let port = resolved.2;
|
|
||||||
Some((resolved, addr.and_then(|addr| TcpStream::connect(&SocketAddr::new(addr, port))
|
|
||||||
.map(HttpStream))
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
trace!("^-- resolved but not in hashmap?");
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self, reg: Registration) {
|
}
|
||||||
self.dns = Some(Dns::new(reg.notify, self.threads));
|
|
||||||
|
/// A Future representing work to connect to a URL.
|
||||||
|
pub struct HttpConnecting {
|
||||||
|
state: State,
|
||||||
|
handle: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Resolving(dns::Query),
|
||||||
|
Connecting(ConnectingTcp),
|
||||||
|
Error(Option<io::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for HttpConnecting {
|
||||||
|
type Item = TcpStream;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
loop {
|
||||||
|
let state;
|
||||||
|
match self.state {
|
||||||
|
State::Resolving(ref mut query) => {
|
||||||
|
match try!(query.poll()) {
|
||||||
|
Async::NotReady => return Ok(Async::NotReady),
|
||||||
|
Async::Ready(addrs) => {
|
||||||
|
state = State::Connecting(ConnectingTcp {
|
||||||
|
addrs: addrs,
|
||||||
|
current: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
State::Connecting(ref mut c) => return c.poll(&self.handle).map_err(From::from),
|
||||||
|
State::Error(ref mut e) => return Err(e.take().expect("polled more than once")),
|
||||||
|
}
|
||||||
|
self.state = state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A connector that can protect HTTP streams using SSL.
|
impl fmt::Debug for HttpConnecting {
|
||||||
#[derive(Debug, Default)]
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
pub struct HttpsConnector<S: SslClient> {
|
f.pad("HttpConnecting")
|
||||||
http: HttpConnector,
|
}
|
||||||
ssl: S
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ConnectingTcp {
|
||||||
|
addrs: dns::IpAddrs,
|
||||||
|
current: Option<TcpStreamNew>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectingTcp {
|
||||||
|
// not a Future, since passing a &Handle to poll
|
||||||
|
fn poll(&mut self, handle: &Handle) -> Poll<TcpStream, io::Error> {
|
||||||
|
let mut err = None;
|
||||||
|
loop {
|
||||||
|
if let Some(ref mut current) = self.current {
|
||||||
|
match current.poll() {
|
||||||
|
Ok(ok) => return Ok(ok),
|
||||||
|
Err(e) => {
|
||||||
|
trace!("connect error {:?}", e);
|
||||||
|
err = Some(e);
|
||||||
|
if let Some(addr) = self.addrs.next() {
|
||||||
|
debug!("connecting to {:?}", addr);
|
||||||
|
*current = TcpStream::connect(&addr, handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(addr) = self.addrs.next() {
|
||||||
|
debug!("connecting to {:?}", addr);
|
||||||
|
self.current = Some(TcpStream::connect(&addr, handle));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(err.take().expect("missing connect error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
impl<S: SslClient> HttpsConnector<S> {
|
impl<S: SslClient> HttpsConnector<S> {
|
||||||
/// Create a new connector using the provided SSL implementation.
|
/// Create a new connector using the provided SSL implementation.
|
||||||
pub fn new(s: S) -> HttpsConnector<S> {
|
pub fn new(s: S) -> HttpsConnector<S> {
|
||||||
@@ -143,80 +179,22 @@ impl<S: SslClient> HttpsConnector<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
impl<S: SslClient> Connect for HttpsConnector<S> {
|
#[cfg(test)]
|
||||||
type Output = HttpsStream<S::Stream>;
|
mod tests {
|
||||||
type Key = (&'static str, String, u16);
|
use std::io;
|
||||||
|
use tokio::reactor::Core;
|
||||||
|
use url::Url;
|
||||||
|
use super::{Connect, HttpConnector};
|
||||||
|
|
||||||
fn dns_workers(&mut self, count: usize) {
|
#[test]
|
||||||
self.http.dns_workers(count)
|
fn test_non_http_url() {
|
||||||
|
let mut core = Core::new().unwrap();
|
||||||
|
let url = Url::parse("file:///home/sean/foo.txt").unwrap();
|
||||||
|
let connector = HttpConnector::new(1, &core.handle());
|
||||||
|
|
||||||
|
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
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_with(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>();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +1,53 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::thread;
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
use ::spmc;
|
use ::futures::{Future, Poll};
|
||||||
|
use ::futures_cpupool::{CpuPool, CpuFuture};
|
||||||
use http::channel;
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Dns {
|
pub struct Dns {
|
||||||
tx: spmc::Sender<String>,
|
pool: CpuPool,
|
||||||
rx: channel::Receiver<Answer>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Answer = (String, io::Result<IpAddrs>);
|
impl Dns {
|
||||||
|
pub fn new(threads: usize) -> Dns {
|
||||||
|
Dns {
|
||||||
|
pool: CpuPool::new(threads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(&self, host: String, port: u16) -> Query {
|
||||||
|
Query(self.pool.spawn_fn(move || work(host, port)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Query(CpuFuture<IpAddrs, io::Error>);
|
||||||
|
|
||||||
|
impl Future for Query {
|
||||||
|
type Item = IpAddrs;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
self.0.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IpAddrs {
|
pub struct IpAddrs {
|
||||||
iter: vec::IntoIter<SocketAddr>,
|
iter: vec::IntoIter<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for IpAddrs {
|
impl Iterator for IpAddrs {
|
||||||
type Item = IpAddr;
|
type Item = SocketAddr;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next(&mut self) -> Option<IpAddr> {
|
fn next(&mut self) -> Option<SocketAddr> {
|
||||||
self.iter.next().map(|addr| addr.ip())
|
self.iter.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dns {
|
pub type Answer = io::Result<IpAddrs>;
|
||||||
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) {
|
fn work(hostname: String, port: u16) -> Answer {
|
||||||
self.tx.send(hostname.into()).expect("DNS workers all died unexpectedly");
|
debug!("resolve {:?}:{:?}", hostname, port);
|
||||||
}
|
(&*hostname, port).to_socket_addrs().map(|i| IpAddrs { iter: i })
|
||||||
|
|
||||||
pub fn resolved(&self) -> Result<Answer, channel::TryRecvError> {
|
|
||||||
self.rx.try_recv()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn work(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) {
|
|
||||||
thread::Builder::new().name(String::from("hyper-dns")).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(|i| IpAddrs{ iter: i }) {
|
|
||||||
Ok(addrs) => (host, Ok(addrs)),
|
|
||||||
Err(e) => (host, Err(e))
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(_) = notify.send(res) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
worker.shutdown = true;
|
|
||||||
}).expect("spawn dns thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,51 +3,49 @@
|
|||||||
//! The HTTP `Client` uses asynchronous IO, and utilizes the `Handler` trait
|
//! The HTTP `Client` uses asynchronous IO, and utilizes the `Handler` trait
|
||||||
//! to convey when IO events are available for a given request.
|
//! to convey when IO events are available for a given request.
|
||||||
|
|
||||||
use std::collections::{VecDeque, HashMap};
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::marker::PhantomData;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use rotor::{self, Scope, EventSet, PollOpt};
|
use futures::{Poll, Async, Future};
|
||||||
|
use relay;
|
||||||
|
use tokio::io::Io;
|
||||||
|
use tokio::reactor::Handle;
|
||||||
|
use tokio_proto::BindClient;
|
||||||
|
use tokio_proto::streaming::Message;
|
||||||
|
use tokio_proto::streaming::pipeline::ClientProto;
|
||||||
|
use tokio_proto::util::client_proxy::ClientProxy;
|
||||||
|
pub use tokio_service::Service;
|
||||||
|
|
||||||
use header::Host;
|
use header::{Headers, Host};
|
||||||
use http::{self, Next, RequestHead, ReadyResult};
|
use http::{self, TokioBody};
|
||||||
use net::Transport;
|
use method::Method;
|
||||||
|
use self::pool::{Pool, Pooled};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
use {Url};
|
use {Url};
|
||||||
|
|
||||||
pub use self::connect::{Connect, DefaultConnector, HttpConnector, HttpsConnector, DefaultTransport};
|
pub use self::connect::{HttpConnector, Connect};
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
||||||
mod connect;
|
mod connect;
|
||||||
mod dns;
|
mod dns;
|
||||||
|
mod pool;
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
/// A Client to make outgoing HTTP requests.
|
/// A Client to make outgoing HTTP requests.
|
||||||
pub struct Client<H> {
|
// If the Connector is clone, then the Client can be clone easily.
|
||||||
tx: http::channel::Sender<Notify<H>>,
|
#[derive(Clone)]
|
||||||
|
pub struct Client<C> {
|
||||||
|
connector: C,
|
||||||
|
handle: Handle,
|
||||||
|
pool: Pool<TokioClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H> Clone for Client<H> {
|
impl Client<HttpConnector> {
|
||||||
fn clone(&self) -> Client<H> {
|
|
||||||
Client {
|
|
||||||
tx: self.tx.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H> fmt::Debug for Client<H> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.pad("Client")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H> Client<H> {
|
|
||||||
/// Configure a Client.
|
/// Configure a Client.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@@ -56,116 +54,218 @@ impl<H> Client<H> {
|
|||||||
/// # use hyper::Client;
|
/// # use hyper::Client;
|
||||||
/// let client = Client::configure()
|
/// let client = Client::configure()
|
||||||
/// .keep_alive(true)
|
/// .keep_alive(true)
|
||||||
/// .max_sockets(10_000)
|
|
||||||
/// .build().unwrap();
|
/// .build().unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn configure() -> Config<DefaultConnector> {
|
pub fn configure() -> Config<UseDefaultConnector> {
|
||||||
Config::default()
|
Config::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: Handler<<DefaultConnector as Connect>::Output>> Client<H> {
|
impl Client<HttpConnector> {
|
||||||
/// Create a new Client with the default config.
|
/// Create a new Client with the default config.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> ::Result<Client<H>> {
|
pub fn new(handle: &Handle) -> Client<HttpConnector> {
|
||||||
Client::<H>::configure().build()
|
Client::configure().build(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: Send> Client<H> {
|
impl<C: Connect> Client<C> {
|
||||||
/// Create a new client with a specific connector.
|
/// Create a new client with a specific connector.
|
||||||
fn configured<T, C>(config: Config<C>) -> ::Result<Client<H>>
|
#[inline]
|
||||||
where H: Handler<T>,
|
fn configured(config: Config<C>, handle: &Handle) -> Client<C> {
|
||||||
T: Transport,
|
Client {
|
||||||
C: Connect<Output=T> + Send + 'static {
|
connector: config.connector,
|
||||||
let mut rotor_config = rotor::Config::new();
|
handle: handle.clone(),
|
||||||
rotor_config.slab_capacity(config.max_sockets);
|
pool: Pool::new(config.keep_alive, config.keep_alive_timeout),
|
||||||
rotor_config.mio().notify_capacity(config.max_sockets);
|
|
||||||
let keep_alive = config.keep_alive;
|
|
||||||
let connect_timeout = config.connect_timeout;
|
|
||||||
let mut loop_ = try!(rotor::Loop::new(&rotor_config));
|
|
||||||
let mut notifier = None;
|
|
||||||
let mut connector = config.connector;
|
|
||||||
connector.dns_workers(config.dns_workers);
|
|
||||||
{
|
|
||||||
let not = &mut notifier;
|
|
||||||
loop_.add_machine_with(move |scope| {
|
|
||||||
let (tx, rx) = http::channel::new(scope.notifier());
|
|
||||||
let (dns_tx, dns_rx) = http::channel::share(&tx);
|
|
||||||
*not = Some(tx);
|
|
||||||
connector.register(Registration {
|
|
||||||
notify: (dns_tx, dns_rx),
|
|
||||||
});
|
|
||||||
rotor::Response::ok(ClientFsm::Connector(connector, rx))
|
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifier = notifier.expect("loop.add_machine_with failed");
|
|
||||||
let _handle = try!(thread::Builder::new().name("hyper-client".to_owned()).spawn(move || {
|
|
||||||
loop_.run(Context {
|
|
||||||
connect_timeout: connect_timeout,
|
|
||||||
keep_alive: keep_alive,
|
|
||||||
idle_conns: HashMap::new(),
|
|
||||||
queue: HashMap::new(),
|
|
||||||
awaiting_slot: VecDeque::new(),
|
|
||||||
}).unwrap()
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(Client {
|
|
||||||
//handle: Some(handle),
|
|
||||||
tx: notifier,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a new request using this Client.
|
/// Send a GET Request using this Client.
|
||||||
///
|
#[inline]
|
||||||
/// ## Error
|
pub fn get(&self, url: Url) -> FutureResponse {
|
||||||
///
|
self.request(Request::new(Method::Get, url))
|
||||||
/// If the event loop thread has died, or the queue is full, a `ClientError`
|
}
|
||||||
/// will be returned.
|
|
||||||
pub fn request(&self, url: Url, handler: H) -> Result<(), ClientError<H>> {
|
/// Send a constructed Request using this Client.
|
||||||
self.tx.send(Notify::Connect(url, handler)).map_err(|e| {
|
#[inline]
|
||||||
match e.0 {
|
pub fn request(&self, req: Request) -> FutureResponse {
|
||||||
Some(Notify::Connect(url, handler)) => ClientError(Some((url, handler))),
|
self.call(req)
|
||||||
_ => ClientError(None)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Future` that will resolve to an HTTP Response.
|
||||||
|
pub struct FutureResponse(Box<Future<Item=Response, Error=::Error> + 'static>);
|
||||||
|
|
||||||
|
impl fmt::Debug for FutureResponse {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad("Future<Response>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for FutureResponse {
|
||||||
|
type Item = Response;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
self.0.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Connect> Service for Client<C> {
|
||||||
|
type Request = Request;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = ::Error;
|
||||||
|
type Future = FutureResponse;
|
||||||
|
|
||||||
|
fn call(&self, req: Request) -> Self::Future {
|
||||||
|
let url = req.url().clone();
|
||||||
|
|
||||||
|
let (mut head, body) = request::split(req);
|
||||||
|
let mut headers = Headers::new();
|
||||||
|
headers.set(Host {
|
||||||
|
hostname: url.host_str().unwrap().to_owned(),
|
||||||
|
port: url.port().or(None),
|
||||||
|
});
|
||||||
|
headers.extend(head.headers.iter());
|
||||||
|
head.subject.1 = RequestUri::AbsolutePath {
|
||||||
|
path: url.path().to_owned(),
|
||||||
|
query: url.query().map(ToOwned::to_owned),
|
||||||
|
};
|
||||||
|
head.headers = headers;
|
||||||
|
|
||||||
|
let checkout = self.pool.checkout(&url[..::url::Position::BeforePath]);
|
||||||
|
let connect = {
|
||||||
|
let handle = self.handle.clone();
|
||||||
|
let pool = self.pool.clone();
|
||||||
|
let pool_key = Rc::new(url[..::url::Position::BeforePath].to_owned());
|
||||||
|
self.connector.connect(url)
|
||||||
|
.map(move |io| {
|
||||||
|
let (tx, rx) = relay::channel();
|
||||||
|
let client = HttpClient {
|
||||||
|
client_rx: RefCell::new(Some(rx)),
|
||||||
|
}.bind_client(&handle, io);
|
||||||
|
let pooled = pool.pooled(pool_key, client);
|
||||||
|
tx.complete(pooled.clone());
|
||||||
|
pooled
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let race = checkout.select(connect)
|
||||||
|
.map(|(client, _work)| client)
|
||||||
|
.map_err(|(e, _work)| {
|
||||||
|
// the Pool Checkout cannot error, so the only error
|
||||||
|
// is from the Connector
|
||||||
|
// XXX: should wait on the Checkout? Problem is
|
||||||
|
// that if the connector is failing, it may be that we
|
||||||
|
// never had a pooled stream at all
|
||||||
|
e.into()
|
||||||
|
});
|
||||||
|
let req = race.and_then(move |client| {
|
||||||
|
let msg = match body {
|
||||||
|
Some(body) => {
|
||||||
|
Message::WithBody(head, body.into())
|
||||||
|
},
|
||||||
|
None => Message::WithoutBody(head),
|
||||||
|
};
|
||||||
|
client.call(msg)
|
||||||
|
});
|
||||||
|
FutureResponse(Box::new(req.map(|msg| {
|
||||||
|
match msg {
|
||||||
|
Message::WithoutBody(head) => response::new(head, None),
|
||||||
|
Message::WithBody(head, body) => response::new(head, Some(body.into())),
|
||||||
}
|
}
|
||||||
})
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the Client loop.
|
}
|
||||||
pub fn close(self) {
|
|
||||||
// Most errors mean that the Receivers are already dead, which would
|
impl<C> fmt::Debug for Client<C> {
|
||||||
// imply the EventLoop panicked.
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let _ = self.tx.send(Notify::Shutdown);
|
f.pad("Client")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokioClient = ClientProxy<Message<http::RequestHead, TokioBody>, Message<http::ResponseHead, TokioBody>, ::Error>;
|
||||||
|
|
||||||
|
struct HttpClient {
|
||||||
|
client_rx: RefCell<Option<relay::Receiver<Pooled<TokioClient>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Io + 'static> ClientProto<T> for HttpClient {
|
||||||
|
type Request = http::RequestHead;
|
||||||
|
type RequestBody = http::Chunk;
|
||||||
|
type Response = http::ResponseHead;
|
||||||
|
type ResponseBody = http::Chunk;
|
||||||
|
type Error = ::Error;
|
||||||
|
type Transport = http::Conn<T, http::ClientTransaction, Pooled<TokioClient>>;
|
||||||
|
type BindTransport = BindingClient<T>;
|
||||||
|
|
||||||
|
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||||
|
BindingClient {
|
||||||
|
rx: self.client_rx.borrow_mut().take().expect("client_rx was lost"),
|
||||||
|
io: Some(io),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BindingClient<T> {
|
||||||
|
rx: relay::Receiver<Pooled<TokioClient>>,
|
||||||
|
io: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Io + 'static> Future for BindingClient<T> {
|
||||||
|
type Item = http::Conn<T, http::ClientTransaction, Pooled<TokioClient>>;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.rx.poll() {
|
||||||
|
Ok(Async::Ready(client)) => Ok(Async::Ready(
|
||||||
|
http::Conn::new(self.io.take().expect("binding client io lost"), client)
|
||||||
|
)),
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
Err(_canceled) => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for a Client
|
/// Configuration for a Client
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Config<C> {
|
pub struct Config<C> {
|
||||||
connect_timeout: Duration,
|
//connect_timeout: Duration,
|
||||||
connector: C,
|
connector: C,
|
||||||
keep_alive: bool,
|
keep_alive: bool,
|
||||||
keep_alive_timeout: Option<Duration>,
|
keep_alive_timeout: Option<Duration>,
|
||||||
//TODO: make use of max_idle config
|
//TODO: make use of max_idle config
|
||||||
max_idle: usize,
|
max_idle: usize,
|
||||||
max_sockets: usize,
|
|
||||||
dns_workers: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Config<C> where C: Connect + Send + 'static {
|
/// Phantom type used to signal that `Config` should create a `HttpConnector`.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct UseDefaultConnector(());
|
||||||
|
|
||||||
|
impl Config<UseDefaultConnector> {
|
||||||
|
fn default() -> Config<UseDefaultConnector> {
|
||||||
|
Config {
|
||||||
|
//connect_timeout: Duration::from_secs(10),
|
||||||
|
connector: UseDefaultConnector(()),
|
||||||
|
keep_alive: true,
|
||||||
|
keep_alive_timeout: Some(Duration::from_secs(90)),
|
||||||
|
max_idle: 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Config<C> {
|
||||||
/// Set the `Connect` type to be used.
|
/// Set the `Connect` type to be used.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn connector<CC: Connect>(self, val: CC) -> Config<CC> {
|
pub fn connector<CC: Connect>(self, val: CC) -> Config<CC> {
|
||||||
Config {
|
Config {
|
||||||
connect_timeout: self.connect_timeout,
|
//connect_timeout: self.connect_timeout,
|
||||||
connector: val,
|
connector: val,
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
keep_alive_timeout: Some(Duration::from_secs(60 * 2)),
|
keep_alive_timeout: self.keep_alive_timeout,
|
||||||
max_idle: self.max_idle,
|
max_idle: self.max_idle,
|
||||||
max_sockets: self.max_sockets,
|
|
||||||
dns_workers: self.dns_workers,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,15 +289,7 @@ impl<C> Config<C> where C: Connect + Send + 'static {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the max table size allocated for holding on to live sockets.
|
/*
|
||||||
///
|
|
||||||
/// Default is 1024.
|
|
||||||
#[inline]
|
|
||||||
pub fn max_sockets(mut self, val: usize) -> Config<C> {
|
|
||||||
self.max_sockets = val;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the timeout for connecting to a URL.
|
/// Set the timeout for connecting to a URL.
|
||||||
///
|
///
|
||||||
/// Default is 10 seconds.
|
/// Default is 10 seconds.
|
||||||
@@ -206,584 +298,25 @@ impl<C> Config<C> where C: Connect + Send + 'static {
|
|||||||
self.connect_timeout = val;
|
self.connect_timeout = val;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
/// Set number of Dns workers to use for this client
|
impl<C: Connect> Config<C> {
|
||||||
///
|
|
||||||
/// Default is 4
|
|
||||||
#[inline]
|
|
||||||
pub fn dns_workers(mut self, workers: usize) -> Config<C> {
|
|
||||||
self.dns_workers = workers;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct the Client with this configuration.
|
/// Construct the Client with this configuration.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build<H: Handler<C::Output>>(self) -> ::Result<Client<H>> {
|
pub fn build(self, handle: &Handle) -> Client<C> {
|
||||||
Client::configured(self)
|
Client::configured(self, handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config<DefaultConnector> {
|
impl Config<UseDefaultConnector> {
|
||||||
fn default() -> Config<DefaultConnector> {
|
/// Construct the Client with this configuration.
|
||||||
Config {
|
#[inline]
|
||||||
connect_timeout: Duration::from_secs(10),
|
pub fn build(self, handle: &Handle) -> Client<HttpConnector> {
|
||||||
connector: DefaultConnector::default(),
|
self.connector(HttpConnector::new(4, handle)).build(handle)
|
||||||
keep_alive: true,
|
|
||||||
keep_alive_timeout: Some(Duration::from_secs(60 * 2)),
|
|
||||||
max_idle: 5,
|
|
||||||
max_sockets: 1024,
|
|
||||||
dns_workers: 4,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that can occur when trying to queue a request.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ClientError<H>(Option<(Url, H)>);
|
|
||||||
|
|
||||||
impl<H> ClientError<H> {
|
|
||||||
/// If the event loop was down, the `Url` and `Handler` can be recovered
|
|
||||||
/// from this method.
|
|
||||||
pub fn recover(self) -> Option<(Url, H)> {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H: fmt::Debug + ::std::any::Any> ::std::error::Error for ClientError<H> {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Cannot queue request"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H> fmt::Display for ClientError<H> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Cannot queue request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait to react to client events that happen for each message.
|
|
||||||
///
|
|
||||||
/// Each event handler returns it's desired `Next` action.
|
|
||||||
pub trait Handler<T: Transport>: Send + 'static {
|
|
||||||
/// This event occurs first, triggering when a `Request` head can be written..
|
|
||||||
fn on_request(&mut self, request: &mut Request) -> http::Next;
|
|
||||||
/// This event occurs each time the `Request` is ready to be written to.
|
|
||||||
fn on_request_writable(&mut self, request: &mut http::Encoder<T>) -> http::Next;
|
|
||||||
/// This event occurs after the first time this handler signals `Next::read()`,
|
|
||||||
/// and a Response has been parsed.
|
|
||||||
fn on_response(&mut self, response: Response) -> http::Next;
|
|
||||||
/// This event occurs each time the `Response` is ready to be read from.
|
|
||||||
fn on_response_readable(&mut self, response: &mut http::Decoder<T>) -> http::Next;
|
|
||||||
|
|
||||||
/// This event occurs whenever an `Error` occurs outside of the other events.
|
|
||||||
///
|
|
||||||
/// This could IO errors while waiting for events, or a timeout, etc.
|
|
||||||
fn on_error(&mut self, err: ::Error) -> http::Next {
|
|
||||||
debug!("default Handler.on_error({:?})", err);
|
|
||||||
http::Next::remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This event occurs when this Handler has requested to remove the Transport.
|
|
||||||
fn on_remove(self, _transport: T) where Self: Sized {
|
|
||||||
debug!("default Handler.on_remove");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Receive a `Control` to manage waiting for this request.
|
|
||||||
fn on_control(&mut self, _: http::Control) {
|
|
||||||
debug!("default Handler.on_control()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Message<H: Handler<T>, T: Transport> {
|
|
||||||
handler: H,
|
|
||||||
url: Option<Url>,
|
|
||||||
_marker: PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H: Handler<T>, T: Transport> http::MessageHandler<T> for Message<H, T> {
|
|
||||||
type Message = http::ClientMessage;
|
|
||||||
|
|
||||||
fn on_outgoing(&mut self, head: &mut RequestHead) -> Next {
|
|
||||||
let url = self.url.take().expect("Message.url is missing");
|
|
||||||
if let Some(host) = url.host_str() {
|
|
||||||
head.headers.set(Host {
|
|
||||||
hostname: host.to_owned(),
|
|
||||||
port: url.port(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
head.subject.1 = RequestUri::AbsolutePath {
|
|
||||||
path: url.path().to_owned(),
|
|
||||||
query: url.query().map(|q| q.to_owned()),
|
|
||||||
};
|
|
||||||
let mut req = self::request::new(head);
|
|
||||||
self.handler.on_request(&mut req)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_encode(&mut self, transport: &mut http::Encoder<T>) -> Next {
|
|
||||||
self.handler.on_request_writable(transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_incoming(&mut self, head: http::ResponseHead, _: &T) -> Next {
|
|
||||||
trace!("on_incoming {:?}", head);
|
|
||||||
let resp = response::new(head);
|
|
||||||
self.handler.on_response(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_decode(&mut self, transport: &mut http::Decoder<T>) -> Next {
|
|
||||||
self.handler.on_response_readable(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Context<K, H, C: Connect> {
|
|
||||||
connect_timeout: Duration,
|
|
||||||
keep_alive: bool,
|
|
||||||
idle_conns: HashMap<K, VecDeque<http::Control>>,
|
|
||||||
queue: HashMap<K, VecDeque<Queued<H>>>,
|
|
||||||
awaiting_slot: VecDeque<(C::Key, C::Output)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro for advancing state of a ClientFsm::Socket
|
|
||||||
///
|
|
||||||
/// This was previously a method on Context, but due to eviction needs, this
|
|
||||||
/// block now needs access to the registration APIs on rotor::Scope.
|
|
||||||
macro_rules! conn_response {
|
|
||||||
($scope:expr, $conn:expr, $time:expr) => {{
|
|
||||||
match $conn {
|
|
||||||
Some((conn, timeout)) => {
|
|
||||||
//TODO: HTTP2: a connection doesn't need to be idle to be used for a second stream
|
|
||||||
if conn.is_idle() {
|
|
||||||
$scope.idle_conns.entry(conn.key().clone()).or_insert_with(VecDeque::new)
|
|
||||||
.push_back(conn.control());
|
|
||||||
}
|
|
||||||
match timeout {
|
|
||||||
Some(dur) => rotor::Response::ok(ClientFsm::Socket(conn))
|
|
||||||
.deadline($time + dur),
|
|
||||||
None => rotor::Response::ok(ClientFsm::Socket(conn)),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if let Some((key, socket)) = $scope.awaiting_slot.pop_front() {
|
|
||||||
rotor_try!($scope.register(&socket, EventSet::writable() | EventSet::hup(), PollOpt::level()));
|
|
||||||
rotor::Response::ok(ClientFsm::Connecting((key, socket)))
|
|
||||||
} else {
|
|
||||||
rotor::Response::done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: http::Key, H, C: Connect> Context<K, H, C> {
|
|
||||||
fn pop_queue(&mut self, key: &K) -> Option<Queued<H>> {
|
|
||||||
let mut should_remove = false;
|
|
||||||
let queued = {
|
|
||||||
self.queue.get_mut(key).and_then(|vec| {
|
|
||||||
let queued = vec.pop_front();
|
|
||||||
if vec.is_empty() {
|
|
||||||
should_remove = true;
|
|
||||||
}
|
|
||||||
queued
|
|
||||||
})
|
|
||||||
};
|
|
||||||
if should_remove {
|
|
||||||
self.queue.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
queued
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, H, T, C> http::MessageHandlerFactory<K, T> for Context<K, H, C>
|
|
||||||
where K: http::Key,
|
|
||||||
H: Handler<T>,
|
|
||||||
T: Transport,
|
|
||||||
C: Connect
|
|
||||||
{
|
|
||||||
type Output = Message<H, T>;
|
|
||||||
|
|
||||||
fn create(&mut self, seed: http::Seed<K>) -> Option<Self::Output> {
|
|
||||||
let key = seed.key();
|
|
||||||
self.pop_queue(key).map(|queued| {
|
|
||||||
let (url, mut handler) = (queued.url, queued.handler);
|
|
||||||
handler.on_control(seed.control());
|
|
||||||
|
|
||||||
Message {
|
|
||||||
handler: handler,
|
|
||||||
url: Some(url),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keep_alive_interest(&self) -> Next {
|
|
||||||
Next::wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Notify<T> {
|
|
||||||
Connect(Url, T),
|
|
||||||
Shutdown,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ClientFsm<C, H>
|
|
||||||
where C: Connect,
|
|
||||||
C::Output: Transport,
|
|
||||||
H: Handler<C::Output> {
|
|
||||||
Connector(C, http::channel::Receiver<Notify<H>>),
|
|
||||||
Connecting((C::Key, C::Output)),
|
|
||||||
Socket(http::Conn<C::Key, C::Output, Message<H, C::Output>>)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<C, H> Send for ClientFsm<C, H>
|
|
||||||
where
|
|
||||||
C: Connect + Send,
|
|
||||||
//C::Key, // Key doesn't need to be Send
|
|
||||||
C::Output: Transport, // Tranport doesn't need to be Send
|
|
||||||
H: Handler<C::Output> + Send
|
|
||||||
{}
|
|
||||||
|
|
||||||
impl<C, H> rotor::Machine for ClientFsm<C, H>
|
|
||||||
where C: Connect,
|
|
||||||
C::Key: fmt::Debug,
|
|
||||||
C::Output: Transport,
|
|
||||||
H: Handler<C::Output> {
|
|
||||||
type Context = Context<C::Key, H, C>;
|
|
||||||
type Seed = (C::Key, C::Output);
|
|
||||||
|
|
||||||
fn create(seed: Self::Seed, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, rotor::Void> {
|
|
||||||
rotor_try!(scope.register(&seed.1, EventSet::writable() | EventSet::hup(), PollOpt::level()));
|
|
||||||
rotor::Response::ok(ClientFsm::Connecting(seed))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ready(self, events: EventSet, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
match self {
|
|
||||||
ClientFsm::Socket(conn) => {
|
|
||||||
let mut conn = Some(conn);
|
|
||||||
loop {
|
|
||||||
match conn.take().unwrap().ready(events, scope) {
|
|
||||||
ReadyResult::Done(res) => {
|
|
||||||
let now = scope.now();
|
|
||||||
return conn_response!(scope, res, now);
|
|
||||||
},
|
|
||||||
ReadyResult::Continue(c) => conn = Some(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ClientFsm::Connecting(mut seed) => {
|
|
||||||
if events.is_error() || events.is_hup() {
|
|
||||||
if let Some(err) = seed.1.take_socket_error().err() {
|
|
||||||
debug!("error while connecting: {:?}", err);
|
|
||||||
scope.pop_queue(&seed.0).map(move |mut queued| queued.handler.on_error(::Error::Io(err)));
|
|
||||||
} else {
|
|
||||||
trace!("connecting is_error, but no socket error");
|
|
||||||
}
|
|
||||||
|
|
||||||
rotor::Response::done()
|
|
||||||
} else if events.is_writable() {
|
|
||||||
if scope.queue.contains_key(&seed.0) {
|
|
||||||
trace!("connected and writable {:?}", seed.0);
|
|
||||||
rotor::Response::ok(
|
|
||||||
ClientFsm::Socket(
|
|
||||||
http::Conn::new(
|
|
||||||
seed.0,
|
|
||||||
seed.1,
|
|
||||||
Next::write().timeout(scope.connect_timeout),
|
|
||||||
scope.notifier(),
|
|
||||||
scope.now()
|
|
||||||
).keep_alive(scope.keep_alive)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
trace!("connected, but queued handler is gone: {:?}", seed.0); // probably took too long connecting
|
|
||||||
rotor::Response::done()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// spurious?
|
|
||||||
rotor::Response::ok(ClientFsm::Connecting(seed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClientFsm::Connector(..) => {
|
|
||||||
unreachable!("Connector can never be ready")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawned(self, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
match self {
|
|
||||||
ClientFsm::Connector(..) => self.connect(scope),
|
|
||||||
other => rotor::Response::ok(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_error(
|
|
||||||
self,
|
|
||||||
scope: &mut Scope<Self::Context>,
|
|
||||||
error: rotor::SpawnError<Self::Seed>
|
|
||||||
) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
// see if there's an idle connections that can be terminated. If yes, put this seed on a
|
|
||||||
// list waiting for empty slot.
|
|
||||||
if let rotor::SpawnError::NoSlabSpace((key, socket)) = error {
|
|
||||||
if let Some(mut queued) = scope.pop_queue(&key) {
|
|
||||||
trace!("attempting to remove an idle socket");
|
|
||||||
// Remove an idle connection. Any connection. Just make some space
|
|
||||||
// for the new request.
|
|
||||||
let mut remove_keys = Vec::new();
|
|
||||||
let mut found_idle = false;
|
|
||||||
|
|
||||||
// Check all idle connections regardless of origin
|
|
||||||
for (key, idle) in scope.idle_conns.iter_mut() {
|
|
||||||
// Pop from the front since those are lease recently used
|
|
||||||
while let Some(ctrl) = idle.pop_front() {
|
|
||||||
// Signal connection to close. An err here means the
|
|
||||||
// socket is already dead can should be tossed.
|
|
||||||
if ctrl.ready(Next::remove()).is_ok() {
|
|
||||||
found_idle = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This list is empty, mark it for removal
|
|
||||||
if idle.is_empty() {
|
|
||||||
remove_keys.push(key.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if found, stop looking for an idle connection.
|
|
||||||
if found_idle {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("idle conns: {:?}", scope.idle_conns);
|
|
||||||
|
|
||||||
// Remove empty idle lists.
|
|
||||||
for key in &remove_keys {
|
|
||||||
scope.idle_conns.remove(&key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if found_idle {
|
|
||||||
// A socket should be evicted soon; put it on a queue to
|
|
||||||
// consume newly freed slot. Also need to put the Queued<H>
|
|
||||||
// back onto front of queue.
|
|
||||||
scope.awaiting_slot.push_back((key.clone(), socket));
|
|
||||||
scope.queue
|
|
||||||
.entry(key)
|
|
||||||
.or_insert_with(VecDeque::new)
|
|
||||||
.push_back(queued);
|
|
||||||
} else {
|
|
||||||
// Couldn't evict a socket, just run the error handler.
|
|
||||||
debug!("Error spawning state machine; slab full and no sockets idle");
|
|
||||||
let _ = queued.handler.on_error(::Error::Full);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.connect(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timeout(self, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
trace!("timeout now = {:?}", scope.now());
|
|
||||||
match self {
|
|
||||||
ClientFsm::Connector(..) => {
|
|
||||||
let now = scope.now();
|
|
||||||
let mut empty_keys = Vec::new();
|
|
||||||
{
|
|
||||||
for (key, mut vec) in &mut scope.queue {
|
|
||||||
while !vec.is_empty() && vec[0].deadline <= now {
|
|
||||||
vec.pop_front()
|
|
||||||
.map(|mut queued| queued.handler.on_error(::Error::Timeout));
|
|
||||||
}
|
|
||||||
if vec.is_empty() {
|
|
||||||
empty_keys.push(key.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for key in &empty_keys {
|
|
||||||
scope.queue.remove(key);
|
|
||||||
}
|
|
||||||
match self.deadline(scope) {
|
|
||||||
Some(deadline) => {
|
|
||||||
rotor::Response::ok(self).deadline(deadline)
|
|
||||||
},
|
|
||||||
None => rotor::Response::ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClientFsm::Connecting(..) => unreachable!(),
|
|
||||||
ClientFsm::Socket(conn) => {
|
|
||||||
let res = conn.timeout(scope);
|
|
||||||
let now = scope.now();
|
|
||||||
conn_response!(scope, res, now)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wakeup(self, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
match self {
|
|
||||||
ClientFsm::Connector(..) => {
|
|
||||||
self.connect(scope)
|
|
||||||
},
|
|
||||||
ClientFsm::Socket(conn) => {
|
|
||||||
let res = conn.wakeup(scope);
|
|
||||||
let now = scope.now();
|
|
||||||
conn_response!(scope, res, now)
|
|
||||||
},
|
|
||||||
ClientFsm::Connecting(..) => unreachable!("connecting sockets should not be woken up")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C, H> ClientFsm<C, H>
|
|
||||||
where C: Connect,
|
|
||||||
C::Key: fmt::Debug,
|
|
||||||
C::Output: Transport,
|
|
||||||
H: Handler<C::Output> {
|
|
||||||
fn connect(self, scope: &mut rotor::Scope<<Self as rotor::Machine>::Context>) -> rotor::Response<Self, <Self as rotor::Machine>::Seed> {
|
|
||||||
match self {
|
|
||||||
ClientFsm::Connector(mut connector, rx) => {
|
|
||||||
if let Some((key, res)) = connector.connected() {
|
|
||||||
match res {
|
|
||||||
Ok(socket) => {
|
|
||||||
trace!("connecting {:?}", key);
|
|
||||||
return rotor::Response::spawn(ClientFsm::Connector(connector, rx), (key, socket));
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
trace!("connect error = {:?}", e);
|
|
||||||
scope.pop_queue(&key).map(|mut queued| queued.handler.on_error(::Error::Io(e)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(Notify::Connect(url, mut handler)) => {
|
|
||||||
// check pool for sockets to this domain
|
|
||||||
if let Some(key) = connector.key(&url) {
|
|
||||||
let mut remove_idle = false;
|
|
||||||
let mut woke_up = false;
|
|
||||||
if let Some(mut idle) = scope.idle_conns.get_mut(&key) {
|
|
||||||
// Pop from back since those are most recently used. Connections
|
|
||||||
// at the front are allowed to expire.
|
|
||||||
while let Some(ctrl) = idle.pop_back() {
|
|
||||||
// err means the socket has since died
|
|
||||||
if ctrl.ready(Next::write()).is_ok() {
|
|
||||||
woke_up = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remove_idle = idle.is_empty();
|
|
||||||
}
|
|
||||||
if remove_idle {
|
|
||||||
scope.idle_conns.remove(&key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if woke_up {
|
|
||||||
trace!("woke up idle conn for '{}'", url);
|
|
||||||
let deadline = scope.now() + scope.connect_timeout;
|
|
||||||
scope.queue
|
|
||||||
.entry(key)
|
|
||||||
.or_insert_with(VecDeque::new)
|
|
||||||
.push_back(Queued {
|
|
||||||
deadline: deadline,
|
|
||||||
handler: handler,
|
|
||||||
url: url
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this connector cannot handle this url anyways
|
|
||||||
let _ = handler.on_error(io::Error::new(io::ErrorKind::InvalidInput, "invalid url for connector").into());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// no exist connection, call connector
|
|
||||||
match connector.connect(&url) {
|
|
||||||
Ok(key) => {
|
|
||||||
let deadline = scope.now() + scope.connect_timeout;
|
|
||||||
scope.queue
|
|
||||||
.entry(key)
|
|
||||||
.or_insert_with(VecDeque::new)
|
|
||||||
.push_back(Queued {
|
|
||||||
deadline: deadline,
|
|
||||||
handler: handler,
|
|
||||||
url: url
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _todo = handler.on_error(e.into());
|
|
||||||
trace!("Connect error, next={:?}", _todo);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Notify::Shutdown) => {
|
|
||||||
scope.shutdown_loop();
|
|
||||||
return rotor::Response::done()
|
|
||||||
},
|
|
||||||
Err(mpsc::TryRecvError::Disconnected) => {
|
|
||||||
// if there is no way to send additional requests,
|
|
||||||
// what more can the loop do? i suppose we should
|
|
||||||
// shutdown.
|
|
||||||
scope.shutdown_loop();
|
|
||||||
return rotor::Response::done()
|
|
||||||
}
|
|
||||||
Err(mpsc::TryRecvError::Empty) => {
|
|
||||||
// spurious wakeup or loop is done
|
|
||||||
let fsm = ClientFsm::Connector(connector, rx);
|
|
||||||
return match fsm.deadline(scope) {
|
|
||||||
Some(deadline) => {
|
|
||||||
rotor::Response::ok(fsm).deadline(deadline)
|
|
||||||
},
|
|
||||||
None => rotor::Response::ok(fsm)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
other => rotor::Response::ok(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deadline(&self, scope: &mut rotor::Scope<<Self as rotor::Machine>::Context>) -> Option<rotor::Time> {
|
|
||||||
match *self {
|
|
||||||
ClientFsm::Connector(..) => {
|
|
||||||
let mut earliest = None;
|
|
||||||
for vec in scope.queue.values() {
|
|
||||||
for queued in vec {
|
|
||||||
match earliest {
|
|
||||||
Some(ref mut earliest) => {
|
|
||||||
if queued.deadline < *earliest {
|
|
||||||
*earliest = queued.deadline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => earliest = Some(queued.deadline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace!("deadline = {:?}, now = {:?}", earliest, scope.now());
|
|
||||||
earliest
|
|
||||||
}
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Queued<H> {
|
|
||||||
deadline: rotor::Time,
|
|
||||||
handler: H,
|
|
||||||
url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Registration {
|
|
||||||
notify: (http::channel::Sender<self::dns::Answer>, http::channel::Receiver<self::dns::Answer>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
353
src/client/pool.rs
Normal file
353
src/client/pool.rs
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::ops::{Deref, DerefMut, BitAndAssign};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use futures::{Future, Async, Poll};
|
||||||
|
use relay;
|
||||||
|
|
||||||
|
use http::{KeepAlive, KA};
|
||||||
|
|
||||||
|
pub struct Pool<T> {
|
||||||
|
inner: Rc<RefCell<PoolInner<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PoolInner<T> {
|
||||||
|
enabled: bool,
|
||||||
|
idle: HashMap<Rc<String>, Vec<Entry<T>>>,
|
||||||
|
parked: HashMap<Rc<String>, VecDeque<relay::Sender<Entry<T>>>>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Pool<T> {
|
||||||
|
pub fn new(enabled: bool, timeout: Option<Duration>) -> Pool<T> {
|
||||||
|
Pool {
|
||||||
|
inner: Rc::new(RefCell::new(PoolInner {
|
||||||
|
enabled: enabled,
|
||||||
|
idle: HashMap::new(),
|
||||||
|
parked: HashMap::new(),
|
||||||
|
timeout: timeout,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkout(&self, key: &str) -> Checkout<T> {
|
||||||
|
Checkout {
|
||||||
|
key: Rc::new(key.to_owned()),
|
||||||
|
pool: self.clone(),
|
||||||
|
parked: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put(&mut self, key: Rc<String>, entry: Entry<T>) {
|
||||||
|
trace!("Pool::put {:?}", key);
|
||||||
|
let mut remove_parked = false;
|
||||||
|
let tx = self.inner.borrow_mut().parked.get_mut(&key).and_then(|parked| {
|
||||||
|
let mut ret = None;
|
||||||
|
while let Some(tx) = parked.pop_front() {
|
||||||
|
if !tx.is_canceled() {
|
||||||
|
ret = Some(tx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
trace!("Pool::put removing canceled parked {:?}", key);
|
||||||
|
}
|
||||||
|
remove_parked = parked.is_empty();
|
||||||
|
ret
|
||||||
|
});
|
||||||
|
if remove_parked {
|
||||||
|
self.inner.borrow_mut().parked.remove(&key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tx) = tx {
|
||||||
|
trace!("Pool::put found parked {:?}", key);
|
||||||
|
tx.complete(entry);
|
||||||
|
} else {
|
||||||
|
self.inner.borrow_mut()
|
||||||
|
.idle.entry(key)
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pooled(&self, key: Rc<String>, value: T) -> Pooled<T> {
|
||||||
|
trace!("Pool::pooled {:?}", key);
|
||||||
|
Pooled {
|
||||||
|
entry: Entry {
|
||||||
|
value: value,
|
||||||
|
is_reused: false,
|
||||||
|
status: Rc::new(Cell::new(KA::Busy)),
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
pool: self.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_enabled(&self) -> bool {
|
||||||
|
self.inner.borrow().enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reuse(&self, key: Rc<String>, mut entry: Entry<T>) -> Pooled<T> {
|
||||||
|
trace!("Pool::reuse {:?}", key);
|
||||||
|
entry.is_reused = true;
|
||||||
|
entry.status.set(KA::Busy);
|
||||||
|
Pooled {
|
||||||
|
entry: entry,
|
||||||
|
key: key,
|
||||||
|
pool: self.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn park(&mut self, key: Rc<String>, tx: relay::Sender<Entry<T>>) {
|
||||||
|
trace!("Pool::park {:?}", key);
|
||||||
|
self.inner.borrow_mut()
|
||||||
|
.parked.entry(key)
|
||||||
|
.or_insert(VecDeque::new())
|
||||||
|
.push_back(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for Pool<T> {
|
||||||
|
fn clone(&self) -> Pool<T> {
|
||||||
|
Pool {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Pooled<T> {
|
||||||
|
entry: Entry<T>,
|
||||||
|
key: Rc<String>,
|
||||||
|
pool: Pool<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Pooled<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
&self.entry.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for Pooled<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.entry.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> KeepAlive for Pooled<T> {
|
||||||
|
fn busy(&mut self) {
|
||||||
|
self.entry.status.set(KA::Busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable(&mut self) {
|
||||||
|
self.entry.status.set(KA::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idle(&mut self) {
|
||||||
|
let previous = self.status();
|
||||||
|
self.entry.status.set(KA::Idle(Instant::now()));
|
||||||
|
if let KA::Idle(..) = previous {
|
||||||
|
trace!("Pooled::idle already idle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.entry.is_reused = true;
|
||||||
|
if self.pool.is_enabled() {
|
||||||
|
self.pool.put(self.key.clone(), self.entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self) -> KA {
|
||||||
|
self.entry.status.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Pooled<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Pooled")
|
||||||
|
.field("status", &self.entry.status.get())
|
||||||
|
.field("key", &self.key)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> BitAndAssign<bool> for Pooled<T> {
|
||||||
|
fn bitand_assign(&mut self, enabled: bool) {
|
||||||
|
if !enabled {
|
||||||
|
self.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Entry<T> {
|
||||||
|
value: T,
|
||||||
|
is_reused: bool,
|
||||||
|
status: Rc<Cell<KA>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Checkout<T> {
|
||||||
|
key: Rc<String>,
|
||||||
|
pool: Pool<T>,
|
||||||
|
parked: Option<relay::Receiver<Entry<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Future for Checkout<T> {
|
||||||
|
type Item = Pooled<T>;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
trace!("Checkout::poll");
|
||||||
|
let mut drop_parked = false;
|
||||||
|
if let Some(ref mut rx) = self.parked {
|
||||||
|
match rx.poll() {
|
||||||
|
Ok(Async::Ready(entry)) => {
|
||||||
|
trace!("Checkout::poll found client in relay for {:?}", self.key);
|
||||||
|
return Ok(Async::Ready(self.pool.reuse(self.key.clone(), entry)));
|
||||||
|
},
|
||||||
|
Ok(Async::NotReady) => (),
|
||||||
|
Err(_canceled) => drop_parked = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if drop_parked {
|
||||||
|
self.parked.take();
|
||||||
|
}
|
||||||
|
let expiration = Expiration::new(self.pool.inner.borrow().timeout);
|
||||||
|
let key = &self.key;
|
||||||
|
trace!("Checkout::poll url = {:?}, expiration = {:?}", key, expiration.0);
|
||||||
|
let mut should_remove = false;
|
||||||
|
let entry = self.pool.inner.borrow_mut().idle.get_mut(key).and_then(|list| {
|
||||||
|
trace!("Checkout::poll key found {:?}", key);
|
||||||
|
while let Some(entry) = list.pop() {
|
||||||
|
match entry.status.get() {
|
||||||
|
KA::Idle(idle_at) if !expiration.expires(idle_at) => {
|
||||||
|
trace!("Checkout::poll found idle client for {:?}", key);
|
||||||
|
should_remove = list.is_empty();
|
||||||
|
return Some(entry);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
trace!("Checkout::poll removing unacceptable pooled {:?}", key);
|
||||||
|
// every other case the Entry should just be dropped
|
||||||
|
// 1. Idle but expired
|
||||||
|
// 2. Busy (something else somehow took it?)
|
||||||
|
// 3. Disabled don't reuse of course
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
should_remove = true;
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
|
if should_remove {
|
||||||
|
self.pool.inner.borrow_mut().idle.remove(key);
|
||||||
|
}
|
||||||
|
match entry {
|
||||||
|
Some(entry) => Ok(Async::Ready(self.pool.reuse(self.key.clone(), entry))),
|
||||||
|
None => {
|
||||||
|
if self.parked.is_none() {
|
||||||
|
let (tx, mut rx) = relay::channel();
|
||||||
|
let _ = rx.poll(); // park this task
|
||||||
|
self.pool.park(self.key.clone(), tx);
|
||||||
|
self.parked = Some(rx);
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Expiration(Option<Instant>);
|
||||||
|
|
||||||
|
impl Expiration {
|
||||||
|
fn new(dur: Option<Duration>) -> Expiration {
|
||||||
|
Expiration(dur.map(|dur| Instant::now() - dur))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expires(&self, instant: Instant) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
Some(expire) => expire > instant,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use futures::{Async, Future};
|
||||||
|
use http::KeepAlive;
|
||||||
|
use super::Pool;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pool_checkout_smoke() {
|
||||||
|
let pool = Pool::new(true, Some(Duration::from_secs(5)));
|
||||||
|
let key = Rc::new("foo".to_string());
|
||||||
|
let mut pooled = pool.pooled(key.clone(), 41);
|
||||||
|
pooled.idle();
|
||||||
|
|
||||||
|
match pool.checkout(&key).poll().unwrap() {
|
||||||
|
Async::Ready(pooled) => assert_eq!(*pooled, 41),
|
||||||
|
_ => panic!("not ready"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pool_checkout_returns_none_if_expired() {
|
||||||
|
::futures::lazy(|| {
|
||||||
|
let pool = Pool::new(true, Some(Duration::from_secs(1)));
|
||||||
|
let key = Rc::new("foo".to_string());
|
||||||
|
let mut pooled = pool.pooled(key.clone(), 41);
|
||||||
|
pooled.idle();
|
||||||
|
::std::thread::sleep(pool.inner.borrow().timeout.unwrap());
|
||||||
|
assert!(pool.checkout(&key).poll().unwrap().is_not_ready());
|
||||||
|
::futures::future::ok::<(), ()>(())
|
||||||
|
}).wait().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pool_removes_expired() {
|
||||||
|
let pool = Pool::new(true, Some(Duration::from_secs(1)));
|
||||||
|
let key = Rc::new("foo".to_string());
|
||||||
|
|
||||||
|
let mut pooled1 = pool.pooled(key.clone(), 41);
|
||||||
|
pooled1.idle();
|
||||||
|
let mut pooled2 = pool.pooled(key.clone(), 5);
|
||||||
|
pooled2.idle();
|
||||||
|
let mut pooled3 = pool.pooled(key.clone(), 99);
|
||||||
|
pooled3.idle();
|
||||||
|
|
||||||
|
|
||||||
|
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||||
|
::std::thread::sleep(pool.inner.borrow().timeout.unwrap());
|
||||||
|
|
||||||
|
pooled1.idle();
|
||||||
|
pooled2.idle(); // idle after sleep, not expired
|
||||||
|
pool.checkout(&key).poll().unwrap();
|
||||||
|
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(1));
|
||||||
|
pool.checkout(&key).poll().unwrap();
|
||||||
|
assert!(pool.inner.borrow().idle.get(&key).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pool_checkout_task_unparked() {
|
||||||
|
let pool = Pool::new(true, Some(Duration::from_secs(10)));
|
||||||
|
let key = Rc::new("foo".to_string());
|
||||||
|
let pooled1 = pool.pooled(key.clone(), 41);
|
||||||
|
|
||||||
|
let mut pooled = pooled1.clone();
|
||||||
|
let checkout = pool.checkout(&key).join(::futures::lazy(move || {
|
||||||
|
// the checkout future will park first,
|
||||||
|
// and then this lazy future will be polled, which will insert
|
||||||
|
// the pooled back into the pool
|
||||||
|
//
|
||||||
|
// this test makes sure that doing so will unpark the checkout
|
||||||
|
pooled.idle();
|
||||||
|
Ok(())
|
||||||
|
})).map(|(entry, _)| entry);
|
||||||
|
assert_eq!(*checkout.wait().unwrap(), *pooled1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +1,90 @@
|
|||||||
//! Client Requests
|
use std::fmt;
|
||||||
|
|
||||||
|
use Url;
|
||||||
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use http::RequestHead;
|
use http::{Body, RequestHead};
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
use version::HttpVersion;
|
use version::HttpVersion;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// A client request to a remote server.
|
/// A client request to a remote server.
|
||||||
#[derive(Debug)]
|
pub struct Request {
|
||||||
pub struct Request<'a> {
|
method: Method,
|
||||||
head: &'a mut RequestHead
|
url: Url,
|
||||||
|
version: HttpVersion,
|
||||||
|
headers: Headers,
|
||||||
|
body: Option<Body>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Request<'a> {
|
impl Request {
|
||||||
|
/// Construct a new Request.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(method: Method, url: Url) -> Request {
|
||||||
|
Request {
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
version: HttpVersion::default(),
|
||||||
|
headers: Headers::new(),
|
||||||
|
body: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read the Request Url.
|
/// Read the Request Url.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri(&self) -> &RequestUri { &self.head.subject.1 }
|
pub fn url(&self) -> &Url { &self.url }
|
||||||
|
|
||||||
/// Readthe Request Version.
|
/// Readthe Request Version.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> &HttpVersion { &self.head.version }
|
pub fn version(&self) -> &HttpVersion { &self.version }
|
||||||
|
|
||||||
/// Read the Request headers.
|
/// Read the Request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &Headers { &self.head.headers }
|
pub fn headers(&self) -> &Headers { &self.headers }
|
||||||
|
|
||||||
/// Read the Request method.
|
/// Read the Request method.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&self) -> &Method { &self.head.subject.0 }
|
pub fn method(&self) -> &Method { &self.method }
|
||||||
|
|
||||||
/// Set the Method of this request.
|
/// Set the Method of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_method(&mut self, method: Method) { self.head.subject.0 = method; }
|
pub fn set_method(&mut self, method: Method) { self.method = method; }
|
||||||
|
|
||||||
/// 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.head.headers }
|
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
|
||||||
|
|
||||||
/// Set the `RequestUri` of this request.
|
/// Set the `Url` of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_uri(&mut self, uri: RequestUri) { self.head.subject.1 = uri; }
|
pub fn set_url(&mut self, url: Url) { self.url = url; }
|
||||||
|
|
||||||
/// Set the `HttpVersion` of this request.
|
/// Set the `HttpVersion` of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_version(&mut self, version: HttpVersion) { self.head.version = version; }
|
pub fn set_version(&mut self, version: HttpVersion) { self.version = version; }
|
||||||
|
|
||||||
|
/// Set the body of the request.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_body<T: Into<Body>>(&mut self, body: T) { self.body = Some(body.into()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(head: &mut RequestHead) -> Request {
|
impl fmt::Debug for Request {
|
||||||
Request { head: head }
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Request")
|
||||||
|
.field("method", &self.method)
|
||||||
|
.field("url", &self.url)
|
||||||
|
.field("version", &self.version)
|
||||||
|
.field("headers", &self.headers)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split(req: Request) -> (RequestHead, Option<Body>) {
|
||||||
|
let head = RequestHead {
|
||||||
|
subject: ::http::RequestLine(req.method, RequestUri::AbsoluteUri(req.url)),
|
||||||
|
headers: req.headers,
|
||||||
|
version: req.version,
|
||||||
|
};
|
||||||
|
(head, req.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
//! Client Responses
|
use std::fmt;
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
//use net::NetworkStream;
|
use http::{self, RawStatus, Body};
|
||||||
use http::{self, RawStatus};
|
|
||||||
use status;
|
use status;
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
pub fn new(incoming: http::ResponseHead) -> Response {
|
pub fn new(incoming: http::ResponseHead, body: Option<Body>) -> Response {
|
||||||
trace!("Response::new");
|
trace!("Response::new");
|
||||||
let status = status::StatusCode::from_u16(incoming.subject.0);
|
let status = status::StatusCode::from_u16(incoming.subject.0);
|
||||||
debug!("version={:?}, status={:?}", incoming.version, status);
|
debug!("version={:?}, status={:?}", incoming.version, status);
|
||||||
@@ -16,17 +16,18 @@ pub fn new(incoming: http::ResponseHead) -> Response {
|
|||||||
version: incoming.version,
|
version: incoming.version,
|
||||||
headers: incoming.headers,
|
headers: incoming.headers,
|
||||||
status_raw: incoming.subject,
|
status_raw: incoming.subject,
|
||||||
|
body: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A response for a client request to a remote server.
|
/// A response for a client request to a remote server.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
status: status::StatusCode,
|
status: status::StatusCode,
|
||||||
headers: header::Headers,
|
headers: header::Headers,
|
||||||
version: version::HttpVersion,
|
version: version::HttpVersion,
|
||||||
status_raw: RawStatus,
|
status_raw: RawStatus,
|
||||||
|
body: Option<Body>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
@@ -42,170 +43,23 @@ impl Response {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn status_raw(&self) -> &RawStatus { &self.status_raw }
|
pub fn status_raw(&self) -> &RawStatus { &self.status_raw }
|
||||||
|
|
||||||
/// Get the final URL of this response.
|
|
||||||
#[inline]
|
|
||||||
//pub fn url(&self) -> &Url { &self.url }
|
|
||||||
|
|
||||||
/// Get the HTTP version of this response from the server.
|
/// Get the HTTP version of this response from the server.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> &version::HttpVersion { &self.version }
|
pub fn version(&self) -> &version::HttpVersion { &self.version }
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/// Take the `Body` of this response.
|
||||||
impl Drop for Response {
|
#[inline]
|
||||||
fn drop(&mut self) {
|
pub fn body(mut self) -> Body {
|
||||||
// if not drained, theres old bits in the Reader. we can't reuse this,
|
self.body.take().unwrap_or(Body::empty())
|
||||||
// since those old bits would end up in new Responses
|
|
||||||
//
|
|
||||||
// otherwise, the response has been drained. we should check that the
|
|
||||||
// server has agreed to keep the connection open
|
|
||||||
let is_drained = !self.message.has_body();
|
|
||||||
trace!("Response.drop is_drained={}", is_drained);
|
|
||||||
if !(is_drained && http::should_keep_alive(self.version, &self.headers)) {
|
|
||||||
trace!("Response.drop closing connection");
|
|
||||||
if let Err(e) = self.message.close_connection() {
|
|
||||||
error!("Response.drop error closing connection: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(test)]
|
impl fmt::Debug for Response {
|
||||||
mod tests {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
/*
|
f.debug_struct("Response")
|
||||||
use std::io::{self, Read};
|
.field("status", &self.status)
|
||||||
|
.field("version", &self.version)
|
||||||
use url::Url;
|
.field("headers", &self.headers)
|
||||||
|
.finish()
|
||||||
use header::TransferEncoding;
|
|
||||||
use header::Encoding;
|
|
||||||
use http::HttpMessage;
|
|
||||||
use mock::MockStream;
|
|
||||||
use status;
|
|
||||||
use version;
|
|
||||||
use http::h1::Http11Message;
|
|
||||||
|
|
||||||
use super::Response;
|
|
||||||
|
|
||||||
fn read_to_string(mut r: Response) -> io::Result<String> {
|
|
||||||
let mut s = String::new();
|
|
||||||
try!(r.read_to_string(&mut s));
|
|
||||||
Ok(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_into_inner() {
|
|
||||||
let message: Box<HttpMessage> = Box::new(
|
|
||||||
Http11Message::with_stream(Box::new(MockStream::new())));
|
|
||||||
let message = message.downcast::<Http11Message>().ok().unwrap();
|
|
||||||
let b = message.into_inner().downcast::<MockStream>().ok().unwrap();
|
|
||||||
assert_eq!(b, Box::new(MockStream::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_chunked_response() {
|
|
||||||
let stream = MockStream::with_input(b"\
|
|
||||||
HTTP/1.1 200 OK\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"
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = Url::parse("http://hyper.rs").unwrap();
|
|
||||||
let res = Response::new(url, Box::new(stream)).unwrap();
|
|
||||||
|
|
||||||
// The status line is correct?
|
|
||||||
assert_eq!(res.status, status::StatusCode::Ok);
|
|
||||||
assert_eq!(res.version, version::HttpVersion::Http11);
|
|
||||||
// The header is correct?
|
|
||||||
match res.headers.get::<TransferEncoding>() {
|
|
||||||
Some(encodings) => {
|
|
||||||
assert_eq!(1, encodings.len());
|
|
||||||
assert_eq!(Encoding::Chunked, encodings[0]);
|
|
||||||
},
|
|
||||||
None => panic!("Transfer-Encoding: chunked expected!"),
|
|
||||||
};
|
|
||||||
// The body is correct?
|
|
||||||
assert_eq!(read_to_string(res).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 stream = MockStream::with_input(b"\
|
|
||||||
HTTP/1.1 200 OK\r\n\
|
|
||||||
Transfer-Encoding: chunked\r\n\
|
|
||||||
\r\n\
|
|
||||||
X\r\n\
|
|
||||||
1\r\n\
|
|
||||||
0\r\n\
|
|
||||||
\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = Url::parse("http://hyper.rs").unwrap();
|
|
||||||
let res = Response::new(url, Box::new(stream)).unwrap();
|
|
||||||
|
|
||||||
assert!(read_to_string(res).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests that when a chunk size contains an invalid extension, an error is
|
|
||||||
/// returned.
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_chunk_size_extension() {
|
|
||||||
let stream = MockStream::with_input(b"\
|
|
||||||
HTTP/1.1 200 OK\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"
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = Url::parse("http://hyper.rs").unwrap();
|
|
||||||
let res = Response::new(url, Box::new(stream)).unwrap();
|
|
||||||
|
|
||||||
assert!(read_to_string(res).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 stream = MockStream::with_input(b"\
|
|
||||||
HTTP/1.1 200 OK\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"
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = Url::parse("http://hyper.rs").unwrap();
|
|
||||||
let res = Response::new(url, Box::new(stream)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(read_to_string(res).unwrap(), "1".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_error_closes() {
|
|
||||||
let url = Url::parse("http://hyper.rs").unwrap();
|
|
||||||
let stream = MockStream::with_input(b"\
|
|
||||||
definitely not http
|
|
||||||
");
|
|
||||||
|
|
||||||
assert!(Response::new(url, Box::new(stream)).is_err());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/error.rs
28
src/error.rs
@@ -8,9 +8,6 @@ use std::string::FromUtf8Error;
|
|||||||
use httparse;
|
use httparse;
|
||||||
use url;
|
use url;
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
use openssl::ssl::error::SslError;
|
|
||||||
|
|
||||||
use self::Error::{
|
use self::Error::{
|
||||||
Method,
|
Method,
|
||||||
Uri,
|
Uri,
|
||||||
@@ -19,7 +16,6 @@ use self::Error::{
|
|||||||
Status,
|
Status,
|
||||||
Timeout,
|
Timeout,
|
||||||
Io,
|
Io,
|
||||||
Ssl,
|
|
||||||
TooLarge,
|
TooLarge,
|
||||||
Incomplete,
|
Incomplete,
|
||||||
Utf8
|
Utf8
|
||||||
@@ -49,12 +45,8 @@ pub enum Error {
|
|||||||
Status,
|
Status,
|
||||||
/// A timeout occurred waiting for an IO event.
|
/// A timeout occurred waiting for an IO event.
|
||||||
Timeout,
|
Timeout,
|
||||||
/// Event loop is full and cannot process request
|
|
||||||
Full,
|
|
||||||
/// 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.
|
|
||||||
Ssl(Box<StdError + Send + Sync>),
|
|
||||||
/// Parsing a field as string failed
|
/// Parsing a field as string failed
|
||||||
Utf8(Utf8Error),
|
Utf8(Utf8Error),
|
||||||
|
|
||||||
@@ -76,7 +68,6 @@ impl fmt::Display for Error {
|
|||||||
match *self {
|
match *self {
|
||||||
Uri(ref e) => fmt::Display::fmt(e, f),
|
Uri(ref e) => fmt::Display::fmt(e, f),
|
||||||
Io(ref e) => fmt::Display::fmt(e, f),
|
Io(ref e) => fmt::Display::fmt(e, f),
|
||||||
Ssl(ref e) => fmt::Display::fmt(e, f),
|
|
||||||
Utf8(ref e) => fmt::Display::fmt(e, f),
|
Utf8(ref e) => fmt::Display::fmt(e, f),
|
||||||
ref e => f.write_str(e.description()),
|
ref e => f.write_str(e.description()),
|
||||||
}
|
}
|
||||||
@@ -93,10 +84,8 @@ impl StdError for Error {
|
|||||||
Status => "Invalid Status provided",
|
Status => "Invalid Status provided",
|
||||||
Incomplete => "Message is incomplete",
|
Incomplete => "Message is incomplete",
|
||||||
Timeout => "Timeout",
|
Timeout => "Timeout",
|
||||||
Error::Full => "Event loop is full",
|
|
||||||
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(),
|
|
||||||
Utf8(ref e) => e.description(),
|
Utf8(ref e) => e.description(),
|
||||||
Error::__Nonexhaustive(ref void) => match *void {}
|
Error::__Nonexhaustive(ref void) => match *void {}
|
||||||
}
|
}
|
||||||
@@ -105,8 +94,9 @@ impl StdError for Error {
|
|||||||
fn cause(&self) -> Option<&StdError> {
|
fn cause(&self) -> Option<&StdError> {
|
||||||
match *self {
|
match *self {
|
||||||
Io(ref error) => Some(error),
|
Io(ref error) => Some(error),
|
||||||
Ssl(ref error) => Some(&**error),
|
|
||||||
Uri(ref error) => Some(error),
|
Uri(ref error) => Some(error),
|
||||||
|
Utf8(ref error) => Some(error),
|
||||||
|
Error::__Nonexhaustive(ref void) => match *void {},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,16 +114,6 @@ impl From<url::ParseError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
impl From<SslError> for Error {
|
|
||||||
fn from(err: SslError) -> Error {
|
|
||||||
match err {
|
|
||||||
SslError::StreamError(err) => Io(err),
|
|
||||||
err => Ssl(Box::new(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Utf8Error> for Error {
|
impl From<Utf8Error> for Error {
|
||||||
fn from(err: Utf8Error) -> Error {
|
fn from(err: Utf8Error) -> Error {
|
||||||
Utf8(err)
|
Utf8(err)
|
||||||
@@ -181,9 +161,9 @@ mod tests {
|
|||||||
($from:expr => $error:pat) => {
|
($from:expr => $error:pat) => {
|
||||||
match Error::from($from) {
|
match Error::from($from) {
|
||||||
e @ $error => {
|
e @ $error => {
|
||||||
assert!(e.description().len() > 5);
|
assert!(e.description().len() >= 5);
|
||||||
} ,
|
} ,
|
||||||
_ => panic!("{:?}", $from)
|
e => panic!("{:?}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ header! {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, AccessControlAllowMethods};
|
/// use hyper::header::{Headers, AccessControlAllowMethods};
|
||||||
/// use hyper::method::Method;
|
/// use hyper::Method;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
@@ -28,7 +28,7 @@ header! {
|
|||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, AccessControlAllowMethods};
|
/// use hyper::header::{Headers, AccessControlAllowMethods};
|
||||||
/// use hyper::method::Method;
|
/// use hyper::Method;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ header! {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, AccessControlRequestMethod};
|
/// use hyper::header::{Headers, AccessControlRequestMethod};
|
||||||
/// use hyper::method::Method;
|
/// use hyper::Method;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(AccessControlRequestMethod(Method::Get));
|
/// headers.set(AccessControlRequestMethod(Method::Get));
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ header! {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, Allow};
|
/// use hyper::header::{Headers, Allow};
|
||||||
/// use hyper::method::Method;
|
/// use hyper::Method;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
@@ -30,7 +30,7 @@ header! {
|
|||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, Allow};
|
/// use hyper::header::{Headers, Allow};
|
||||||
/// use hyper::method::Method;
|
/// use hyper::Method;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ impl fmt::Display for ContentLength {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__hyper__deref!(ContentLength => u64);
|
__hyper__deref!(ContentLength => u64);
|
||||||
__hyper_generate_header_serialization!(ContentLength);
|
|
||||||
|
|
||||||
__hyper__tm!(ContentLength, tests {
|
__hyper__tm!(ContentLength, tests {
|
||||||
// Testcase from RFC
|
// Testcase from RFC
|
||||||
|
|||||||
@@ -182,31 +182,6 @@ macro_rules! test_header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! __hyper_generate_header_serialization {
|
|
||||||
($id:ident) => {
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl ::serde::Serialize for $id {
|
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
|
||||||
where S: ::serde::Serializer {
|
|
||||||
format!("{}", self).serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl ::serde::Deserialize for $id {
|
|
||||||
fn deserialize<D>(deserializer: &mut D) -> Result<$id, D::Error>
|
|
||||||
where D: ::serde::Deserializer {
|
|
||||||
let string_representation: String =
|
|
||||||
try!(::serde::Deserialize::deserialize(deserializer));
|
|
||||||
let raw = string_representation.into_bytes().into();
|
|
||||||
Ok($crate::header::Header::parse_header(&raw).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! header {
|
macro_rules! header {
|
||||||
// $a:meta: Attributes associated with the header item (usually docs)
|
// $a:meta: Attributes associated with the header item (usually docs)
|
||||||
@@ -238,8 +213,6 @@ macro_rules! header {
|
|||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__hyper_generate_header_serialization!($id);
|
|
||||||
};
|
};
|
||||||
// List header, one or more items
|
// List header, one or more items
|
||||||
($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+) => {
|
($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+) => {
|
||||||
@@ -265,7 +238,6 @@ macro_rules! header {
|
|||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__hyper_generate_header_serialization!($id);
|
|
||||||
};
|
};
|
||||||
// Single value header
|
// Single value header
|
||||||
($(#[$a:meta])*($id:ident, $n:expr) => [$value:ty]) => {
|
($(#[$a:meta])*($id:ident, $n:expr) => [$value:ty]) => {
|
||||||
@@ -290,7 +262,6 @@ macro_rules! header {
|
|||||||
::std::fmt::Display::fmt(&**self, f)
|
::std::fmt::Display::fmt(&**self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__hyper_generate_header_serialization!($id);
|
|
||||||
};
|
};
|
||||||
// List header, one or more items with "*" option
|
// List header, one or more items with "*" option
|
||||||
($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+}) => {
|
($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+}) => {
|
||||||
@@ -330,7 +301,6 @@ macro_rules! header {
|
|||||||
self.fmt_header(f)
|
self.fmt_header(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__hyper_generate_header_serialization!($id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// optional test module
|
// optional test module
|
||||||
@@ -421,4 +391,4 @@ mod transfer_encoding;
|
|||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod user_agent;
|
mod user_agent;
|
||||||
mod vary;
|
mod vary;
|
||||||
mod warning;
|
mod warning;
|
||||||
|
|||||||
@@ -85,11 +85,6 @@ use unicase::UniCase;
|
|||||||
|
|
||||||
use self::internals::{Item, VecMap, Entry};
|
use self::internals::{Item, VecMap, Entry};
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
use serde::de;
|
|
||||||
|
|
||||||
pub use self::shared::*;
|
pub use self::shared::*;
|
||||||
pub use self::common::*;
|
pub use self::common::*;
|
||||||
pub use self::raw::Raw;
|
pub use self::raw::Raw;
|
||||||
@@ -437,44 +432,6 @@ impl fmt::Debug for Headers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl Serialize for Headers {
|
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
|
||||||
where S: Serializer
|
|
||||||
{
|
|
||||||
let mut state = try!(serializer.serialize_map(Some(self.len())));
|
|
||||||
for header in self.iter() {
|
|
||||||
try!(serializer.serialize_map_key(&mut state, header.name()));
|
|
||||||
try!(serializer.serialize_map_value(&mut state, header.value_string()));
|
|
||||||
}
|
|
||||||
serializer.serialize_map_end(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl Deserialize for Headers {
|
|
||||||
fn deserialize<D>(deserializer: &mut D) -> Result<Headers, D::Error> where D: Deserializer {
|
|
||||||
struct HeadersVisitor;
|
|
||||||
|
|
||||||
impl de::Visitor for HeadersVisitor {
|
|
||||||
type Value = Headers;
|
|
||||||
|
|
||||||
fn visit_map<V>(&mut self, mut visitor: V) -> Result<Headers, V::Error>
|
|
||||||
where V: de::MapVisitor {
|
|
||||||
let mut result = Headers::new();
|
|
||||||
while let Some((key, value)) = try!(visitor.visit()) {
|
|
||||||
let (key, value): (String, String) = (key, value);
|
|
||||||
result.set_raw(key, vec![value.into_bytes()]);
|
|
||||||
}
|
|
||||||
try!(visitor.end());
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_map(HeadersVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An `Iterator` over the fields in a `Headers` map.
|
/// An `Iterator` over the fields in a `Headers` map.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct HeadersItems<'a> {
|
pub struct HeadersItems<'a> {
|
||||||
|
|||||||
97
src/http/body.rs
Normal file
97
src/http/body.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use std::convert::From;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio_proto;
|
||||||
|
use http::Chunk;
|
||||||
|
use futures::{Poll, Stream};
|
||||||
|
use futures::sync::mpsc;
|
||||||
|
|
||||||
|
pub type TokioBody = tokio_proto::streaming::Body<Chunk, ::Error>;
|
||||||
|
|
||||||
|
/// A `Stream` for `Chunk`s used in requests and responses.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Body(TokioBody);
|
||||||
|
|
||||||
|
impl Body {
|
||||||
|
/// Return an empty body stream
|
||||||
|
pub fn empty() -> Body {
|
||||||
|
Body(TokioBody::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a body stream with an associated sender half
|
||||||
|
pub fn pair() -> (mpsc::Sender<Result<Chunk, ::Error>>, Body) {
|
||||||
|
let (tx, rx) = TokioBody::pair();
|
||||||
|
let rx = Body(rx);
|
||||||
|
(tx, rx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for Body {
|
||||||
|
type Item = Chunk;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Chunk>, ::Error> {
|
||||||
|
self.0.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Body> for tokio_proto::streaming::Body<Chunk, ::Error> {
|
||||||
|
fn from(b: Body) -> tokio_proto::streaming::Body<Chunk, ::Error> {
|
||||||
|
b.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<tokio_proto::streaming::Body<Chunk, ::Error>> for Body {
|
||||||
|
fn from(tokio_body: tokio_proto::streaming::Body<Chunk, ::Error>) -> Body {
|
||||||
|
Body(tokio_body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<mpsc::Receiver<Result<Chunk, ::Error>>> for Body {
|
||||||
|
fn from(src: mpsc::Receiver<Result<Chunk, ::Error>>) -> Body {
|
||||||
|
Body(src.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Chunk> for Body {
|
||||||
|
fn from (chunk: Chunk) -> Body {
|
||||||
|
Body(TokioBody::from(chunk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Body {
|
||||||
|
fn from (vec: Vec<u8>) -> Body {
|
||||||
|
Body(TokioBody::from(Chunk::from(vec)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arc<Vec<u8>>> for Body {
|
||||||
|
fn from (vec: Arc<Vec<u8>>) -> Body {
|
||||||
|
Body(TokioBody::from(Chunk::from(vec)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static [u8]> for Body {
|
||||||
|
fn from (slice: &'static [u8]) -> Body {
|
||||||
|
Body(TokioBody::from(Chunk::from(slice)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Body {
|
||||||
|
fn from (s: String) -> Body {
|
||||||
|
Body(TokioBody::from(Chunk::from(s.into_bytes())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Body {
|
||||||
|
fn from (slice: &'static str) -> Body {
|
||||||
|
Body(TokioBody::from(Chunk::from(slice.as_bytes())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _assert_send() {
|
||||||
|
fn _assert<T: Send>() {}
|
||||||
|
|
||||||
|
_assert::<Body>();
|
||||||
|
_assert::<Chunk>();
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read, Write};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
|
|
||||||
const INIT_BUFFER_SIZE: usize = 4096;
|
const INIT_BUFFER_SIZE: usize = 4096;
|
||||||
const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100;
|
pub const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
vec: Vec<u8>,
|
vec: Vec<u8>,
|
||||||
read_pos: usize,
|
tail: usize,
|
||||||
write_pos: usize,
|
head: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
@@ -24,7 +24,17 @@ impl Buffer {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.read_pos - self.write_pos
|
self.tail - self.head
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn available(&self) -> usize {
|
||||||
|
self.vec.len() - self.tail
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_max_size(&self) -> bool {
|
||||||
|
self.len() >= MAX_BUFFER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -34,45 +44,88 @@ impl Buffer {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
&self.vec[self.write_pos..self.read_pos]
|
&self.vec[self.head..self.tail]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn consume(&mut self, pos: usize) {
|
pub fn consume(&mut self, pos: usize) {
|
||||||
debug_assert!(self.read_pos >= self.write_pos + pos);
|
debug_assert!(self.tail >= self.head + pos);
|
||||||
self.write_pos += pos;
|
self.head += pos;
|
||||||
if self.write_pos == self.read_pos {
|
if self.head == self.tail {
|
||||||
self.write_pos = 0;
|
self.head = 0;
|
||||||
self.read_pos = 0;
|
self.tail = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_leading_lines(&mut self) {
|
||||||
|
while !self.is_empty() {
|
||||||
|
match self.vec[self.head] {
|
||||||
|
b'\r' | b'\n' => {
|
||||||
|
self.consume(1);
|
||||||
|
},
|
||||||
|
_ => return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_from<R: Read>(&mut self, r: &mut R) -> io::Result<usize> {
|
pub fn read_from<R: Read>(&mut self, r: &mut R) -> io::Result<usize> {
|
||||||
self.maybe_reserve();
|
self.maybe_reserve(1);
|
||||||
let n = try!(r.read(&mut self.vec[self.read_pos..]));
|
let n = try!(r.read(&mut self.vec[self.tail..]));
|
||||||
self.read_pos += n;
|
self.tail += n;
|
||||||
|
self.maybe_reset();
|
||||||
Ok(n)
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_into<W: Write>(&mut self, w: &mut W) -> io::Result<usize> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(0)
|
||||||
|
} else {
|
||||||
|
let n = try!(w.write(&mut self.vec[self.head..self.tail]));
|
||||||
|
self.head += n;
|
||||||
|
self.maybe_reset();
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, data: &[u8]) -> usize {
|
||||||
|
trace!("Buffer::write len = {:?}", data.len());
|
||||||
|
self.maybe_reserve(data.len());
|
||||||
|
let len = cmp::min(self.available(), data.len());
|
||||||
|
assert!(self.available() >= len);
|
||||||
|
unsafe {
|
||||||
|
// in rust 1.9, we could use slice::copy_from_slice
|
||||||
|
ptr::copy(
|
||||||
|
data.as_ptr(),
|
||||||
|
self.vec.as_mut_ptr().offset(self.tail as isize),
|
||||||
|
len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.tail += len;
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn maybe_reserve(&mut self) {
|
fn maybe_reserve(&mut self, needed: usize) {
|
||||||
let cap = self.vec.len();
|
let cap = self.vec.len();
|
||||||
if cap == 0 {
|
if cap == 0 {
|
||||||
trace!("reserving initial {}", INIT_BUFFER_SIZE);
|
// first reserve
|
||||||
self.vec = vec![0; INIT_BUFFER_SIZE];
|
let init = cmp::max(INIT_BUFFER_SIZE, needed);
|
||||||
} else if self.write_pos > 0 && self.read_pos == cap {
|
trace!("reserving initial {}", init);
|
||||||
let count = self.read_pos - self.write_pos;
|
self.vec = vec![0; init];
|
||||||
|
} else if self.head > 0 && self.tail == cap && self.head >= needed {
|
||||||
|
// there is space to shift over
|
||||||
|
let count = self.tail - self.head;
|
||||||
trace!("moving buffer bytes over by {}", count);
|
trace!("moving buffer bytes over by {}", count);
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::copy(
|
ptr::copy(
|
||||||
self.vec.as_ptr().offset(self.write_pos as isize),
|
self.vec.as_ptr().offset(self.head as isize),
|
||||||
self.vec.as_mut_ptr(),
|
self.vec.as_mut_ptr(),
|
||||||
count
|
count
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.read_pos -= count;
|
self.tail -= count;
|
||||||
self.write_pos = 0;
|
self.head = 0;
|
||||||
} else if self.read_pos == cap && cap < MAX_BUFFER_SIZE {
|
} else if self.tail == cap && cap < MAX_BUFFER_SIZE {
|
||||||
self.vec.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap);
|
self.vec.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap);
|
||||||
let new = self.vec.capacity() - cap;
|
let new = self.vec.capacity() - cap;
|
||||||
trace!("reserved {}", new);
|
trace!("reserved {}", new);
|
||||||
@@ -80,36 +133,11 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap<'a, 'b: 'a, R: io::Read>(&'a mut self, reader: &'b mut R) -> BufReader<'a, R> {
|
#[inline]
|
||||||
BufReader {
|
fn maybe_reset(&mut self) {
|
||||||
buf: self,
|
if self.tail != 0 && self.tail == self.head {
|
||||||
reader: reader
|
self.tail = 0;
|
||||||
}
|
self.head = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BufReader<'a, R: io::Read + 'a> {
|
|
||||||
buf: &'a mut Buffer,
|
|
||||||
reader: &'a mut R
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, R: io::Read + 'a> BufReader<'a, R> {
|
|
||||||
pub fn get_ref(&self) -> &R {
|
|
||||||
self.reader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
77
src/http/chunk.rs
Normal file
77
src/http/chunk.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A piece of a message body.
|
||||||
|
pub struct Chunk(Inner);
|
||||||
|
|
||||||
|
enum Inner {
|
||||||
|
Owned(Vec<u8>),
|
||||||
|
Referenced(Arc<Vec<u8>>),
|
||||||
|
Static(&'static [u8]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Vec<u8>) -> Chunk {
|
||||||
|
Chunk(Inner::Owned(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arc<Vec<u8>>> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Arc<Vec<u8>>) -> Chunk {
|
||||||
|
Chunk(Inner::Referenced(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static [u8]> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn from(slice: &'static [u8]) -> Chunk {
|
||||||
|
Chunk(Inner::Static(slice))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn from(s: String) -> Chunk {
|
||||||
|
s.into_bytes().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn from(slice: &'static str) -> Chunk {
|
||||||
|
slice.as_bytes().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for Chunk {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
match self.0 {
|
||||||
|
Inner::Owned(ref vec) => vec,
|
||||||
|
Inner::Referenced(ref vec) => {
|
||||||
|
let v: &Vec<u8> = vec.borrow();
|
||||||
|
v.as_slice()
|
||||||
|
}
|
||||||
|
Inner::Static(slice) => slice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Chunk {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(self.as_ref(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
1607
src/http/conn.rs
1607
src/http/conn.rs
File diff suppressed because it is too large
Load Diff
@@ -277,7 +277,7 @@ mod tests {
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use super::Decoder;
|
use super::Decoder;
|
||||||
use super::ChunkedState;
|
use super::ChunkedState;
|
||||||
use mock::Async;
|
use mock::AsyncIo;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_chunk_size() {
|
fn test_read_chunk_size() {
|
||||||
@@ -422,7 +422,7 @@ mod tests {
|
|||||||
-> String {
|
-> String {
|
||||||
let content_len = content.len();
|
let content_len = content.len();
|
||||||
let mock_buf = io::Cursor::new(content.clone());
|
let mock_buf = io::Cursor::new(content.clone());
|
||||||
let mut ins = Async::new(mock_buf, block_at);
|
let mut ins = AsyncIo::new(mock_buf, block_at);
|
||||||
let mut outs = vec![];
|
let mut outs = vec![];
|
||||||
loop {
|
loop {
|
||||||
let mut buf = vec![0; read_buffer_size];
|
let mut buf = vec![0; read_buffer_size];
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use http::internal::{AtomicWrite, WriteBuf};
|
use http::io::AtomicWrite;
|
||||||
|
|
||||||
/// Encoders to handle different Transfer-Encodings.
|
/// Encoders to handle different Transfer-Encodings.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Encoder {
|
pub struct Encoder {
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
prefix: Prefix,
|
|
||||||
is_closed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@@ -26,27 +23,16 @@ impl Encoder {
|
|||||||
pub fn chunked() -> Encoder {
|
pub fn chunked() -> Encoder {
|
||||||
Encoder {
|
Encoder {
|
||||||
kind: Kind::Chunked(Chunked::Init),
|
kind: Kind::Chunked(Chunked::Init),
|
||||||
prefix: Prefix(None),
|
|
||||||
is_closed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length(len: u64) -> Encoder {
|
pub fn length(len: u64) -> Encoder {
|
||||||
Encoder {
|
Encoder {
|
||||||
kind: Kind::Length(len),
|
kind: Kind::Length(len),
|
||||||
prefix: Prefix(None),
|
|
||||||
is_closed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefix(&mut self, prefix: WriteBuf<Vec<u8>>) {
|
|
||||||
self.prefix.0 = Some(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
if self.prefix.0.is_some() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Length(0) |
|
Kind::Length(0) |
|
||||||
Kind::Chunked(Chunked::End) => true,
|
Kind::Chunked(Chunked::End) => true,
|
||||||
@@ -54,71 +40,26 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User has called `encoder.close()` in a `Handler`.
|
|
||||||
pub fn is_closed(&self) -> bool {
|
|
||||||
self.is_closed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(&mut self) {
|
|
||||||
self.is_closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(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> {
|
pub fn encode<W: AtomicWrite>(&mut self, w: &mut W, msg: &[u8]) -> io::Result<usize> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Chunked(ref mut chunked) => {
|
Kind::Chunked(ref mut chunked) => {
|
||||||
chunked.encode(w, &mut self.prefix, msg)
|
chunked.encode(w, msg)
|
||||||
},
|
},
|
||||||
Kind::Length(ref mut remaining) => {
|
Kind::Length(ref mut remaining) => {
|
||||||
let mut n = {
|
let n = {
|
||||||
let max = cmp::min(*remaining as usize, msg.len());
|
let max = cmp::min(*remaining as usize, msg.len());
|
||||||
|
trace!("sized write, len = {}", max);
|
||||||
let slice = &msg[..max];
|
let slice = &msg[..max];
|
||||||
|
|
||||||
let prefix = self.prefix.0.as_ref().map(|buf| &buf.bytes[buf.pos..]).unwrap_or(b"");
|
try!(w.write_atomic(&[slice]))
|
||||||
|
|
||||||
try!(w.write_atomic(&[prefix, slice]))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
n = self.prefix.update(n);
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Err(io::Error::new(io::ErrorKind::WouldBlock, "would block"));
|
return Err(io::Error::new(io::ErrorKind::WouldBlock, "would block"));
|
||||||
}
|
}
|
||||||
|
|
||||||
*remaining -= n as u64;
|
*remaining -= n as u64;
|
||||||
|
trace!("sized write complete, remaining = {}", remaining);
|
||||||
Ok(n)
|
Ok(n)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -138,7 +79,7 @@ enum Chunked {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Chunked {
|
impl Chunked {
|
||||||
fn encode<W: AtomicWrite>(&mut self, w: &mut W, prefix: &mut Prefix, msg: &[u8]) -> io::Result<usize> {
|
fn encode<W: AtomicWrite>(&mut self, w: &mut W, msg: &[u8]) -> io::Result<usize> {
|
||||||
match *self {
|
match *self {
|
||||||
Chunked::Init => {
|
Chunked::Init => {
|
||||||
let mut size = ChunkSize {
|
let mut size = ChunkSize {
|
||||||
@@ -158,28 +99,24 @@ impl Chunked {
|
|||||||
let pieces = match *self {
|
let pieces = match *self {
|
||||||
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
||||||
Chunked::Size(ref 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()],
|
&size.bytes[size.pos.into() .. size.len.into()],
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
msg,
|
msg,
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
],
|
],
|
||||||
Chunked::SizeCr => [
|
Chunked::SizeCr => [
|
||||||
&b""[..],
|
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
msg,
|
msg,
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
],
|
],
|
||||||
Chunked::SizeLf => [
|
Chunked::SizeLf => [
|
||||||
&b""[..],
|
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b"\n"[..],
|
&b"\n"[..],
|
||||||
msg,
|
msg,
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
],
|
],
|
||||||
Chunked::Body(pos) => [
|
Chunked::Body(pos) => [
|
||||||
&b""[..],
|
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&msg[pos..],
|
&msg[pos..],
|
||||||
@@ -189,14 +126,12 @@ impl Chunked {
|
|||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
|
||||||
&b"\r\n"[..],
|
&b"\r\n"[..],
|
||||||
],
|
],
|
||||||
Chunked::BodyLf => [
|
Chunked::BodyLf => [
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
&b""[..],
|
||||||
&b""[..],
|
|
||||||
&b"\n"[..],
|
&b"\n"[..],
|
||||||
],
|
],
|
||||||
Chunked::End => unreachable!("Chunked::End shouldn't write more")
|
Chunked::End => unreachable!("Chunked::End shouldn't write more")
|
||||||
@@ -204,9 +139,6 @@ impl Chunked {
|
|||||||
try!(w.write_atomic(&pieces))
|
try!(w.write_atomic(&pieces))
|
||||||
};
|
};
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
n = prefix.update(n);
|
|
||||||
}
|
|
||||||
while n > 0 {
|
while n > 0 {
|
||||||
match *self {
|
match *self {
|
||||||
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
|
||||||
@@ -321,30 +253,10 @@ impl io::Write for ChunkSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Encoder;
|
use super::Encoder;
|
||||||
use mock::{Async, Buf};
|
use mock::{AsyncIo, Buf};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunked_encode_sync() {
|
fn test_chunked_encode_sync() {
|
||||||
@@ -359,7 +271,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunked_encode_async() {
|
fn test_chunked_encode_async() {
|
||||||
let mut dst = Async::new(Buf::new(), 7);
|
let mut dst = AsyncIo::new(Buf::new(), 7);
|
||||||
let mut encoder = Encoder::chunked();
|
let mut encoder = Encoder::chunked();
|
||||||
|
|
||||||
assert!(encoder.encode(&mut dst, b"foo bar").is_err());
|
assert!(encoder.encode(&mut dst, b"foo bar").is_err());
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
/*
|
|
||||||
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::decode::Decoder;
|
||||||
pub use self::encode::Encoder;
|
pub use self::encode::Encoder;
|
||||||
|
|
||||||
@@ -23,7 +5,7 @@ pub use self::parse::parse;
|
|||||||
|
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fn should_have_response_body(method: &Method, status: u16) -> bool {
|
fn should_have_response_body(method: &Method, status: u16) -> bool {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::fmt::{self, Write};
|
|||||||
use httparse;
|
use httparse;
|
||||||
|
|
||||||
use header::{self, Headers, ContentLength, TransferEncoding};
|
use header::{self, Headers, ContentLength, TransferEncoding};
|
||||||
use http::{MessageHead, RawStatus, Http1Message, ParseResult, ServerMessage, ClientMessage, RequestLine};
|
use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine};
|
||||||
use http::h1::{Encoder, Decoder};
|
use http::h1::{Encoder, Decoder};
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use status::StatusCode;
|
use status::StatusCode;
|
||||||
@@ -13,17 +13,15 @@ use version::HttpVersion::{Http10, Http11};
|
|||||||
const MAX_HEADERS: usize = 100;
|
const MAX_HEADERS: usize = 100;
|
||||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||||
|
|
||||||
pub fn parse<T: Http1Message<Incoming=I>, I>(buf: &[u8]) -> ParseResult<I> {
|
pub fn parse<T: Http1Transaction<Incoming=I>, I>(buf: &[u8]) -> ParseResult<I> {
|
||||||
if buf.len() == 0 {
|
if buf.len() == 0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
trace!("parse({:?})", buf);
|
trace!("parse({:?})", buf);
|
||||||
<T as Http1Message>::parse(buf)
|
<T as Http1Transaction>::parse(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Http1Transaction for ServerTransaction {
|
||||||
|
|
||||||
impl Http1Message for ServerMessage {
|
|
||||||
type Incoming = RequestLine;
|
type Incoming = RequestLine;
|
||||||
type Outgoing = StatusCode;
|
type Outgoing = StatusCode;
|
||||||
|
|
||||||
@@ -60,7 +58,7 @@ impl Http1Message for ServerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
fn encode(head: &mut MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||||
use ::header;
|
use ::header;
|
||||||
trace!("writing head: {:?}", head);
|
trace!("writing head: {:?}", head);
|
||||||
|
|
||||||
@@ -103,9 +101,14 @@ impl Http1Message for ServerMessage {
|
|||||||
}
|
}
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_set_length(_head: &MessageHead<Self::Outgoing>) -> bool {
|
||||||
|
//TODO: pass method, check if method == HEAD
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Http1Message for ClientMessage {
|
impl Http1Transaction for ClientTransaction {
|
||||||
type Incoming = RawStatus;
|
type Incoming = RawStatus;
|
||||||
type Outgoing = RequestLine;
|
type Outgoing = RequestLine;
|
||||||
|
|
||||||
@@ -162,7 +165,7 @@ impl Http1Message for ClientMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
fn encode(head: &mut MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||||
trace!("writing head: {:?}", head);
|
trace!("writing head: {:?}", head);
|
||||||
|
|
||||||
|
|
||||||
@@ -203,6 +206,14 @@ impl Http1Message for ClientMessage {
|
|||||||
|
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool {
|
||||||
|
match &head.subject.0 {
|
||||||
|
&Method::Get | &Method::Head => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FastWrite<'a>(&'a mut Vec<u8>);
|
struct FastWrite<'a>(&'a mut Vec<u8>);
|
||||||
@@ -238,17 +249,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_request() {
|
fn test_parse_request() {
|
||||||
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||||
parse::<http::ServerMessage, _>(raw).unwrap();
|
parse::<http::ServerTransaction, _>(raw).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_raw_status() {
|
fn test_parse_raw_status() {
|
||||||
let raw = b"HTTP/1.1 200 OK\r\n\r\n";
|
let raw = b"HTTP/1.1 200 OK\r\n\r\n";
|
||||||
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
let (res, _) = parse::<http::ClientTransaction, _>(raw).unwrap().unwrap();
|
||||||
assert_eq!(res.subject.1, "OK");
|
assert_eq!(res.subject.1, "OK");
|
||||||
|
|
||||||
let raw = b"HTTP/1.1 200 Howdy\r\n\r\n";
|
let raw = b"HTTP/1.1 200 Howdy\r\n\r\n";
|
||||||
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
let (res, _) = parse::<http::ClientTransaction, _>(raw).unwrap().unwrap();
|
||||||
assert_eq!(res.subject.1, "Howdy");
|
assert_eq!(res.subject.1, "Howdy");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +271,7 @@ mod tests {
|
|||||||
fn bench_parse_incoming(b: &mut Bencher) {
|
fn bench_parse_incoming(b: &mut Bencher) {
|
||||||
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
parse::<http::ServerMessage, _>(raw).unwrap()
|
parse::<http::ServerTransaction, _>(raw).unwrap()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
207
src/http/io.rs
Normal file
207
src/http/io.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use futures::Async;
|
||||||
|
use tokio::io::Io;
|
||||||
|
|
||||||
|
use http::{Http1Transaction, h1, MessageHead, ParseResult};
|
||||||
|
use http::buffer::Buffer;
|
||||||
|
|
||||||
|
pub struct Buffered<T> {
|
||||||
|
io: T,
|
||||||
|
read_buf: Buffer,
|
||||||
|
write_buf: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Buffered<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Buffered")
|
||||||
|
.field("read_buf", &self.read_buf)
|
||||||
|
.field("write_buf", &self.write_buf)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Io> Buffered<T> {
|
||||||
|
pub fn new(io: T) -> Buffered<T> {
|
||||||
|
Buffered {
|
||||||
|
io: io,
|
||||||
|
read_buf: Buffer::new(),
|
||||||
|
write_buf: Buffer::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_buf(&self) -> &[u8] {
|
||||||
|
self.read_buf.bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_leading_lines(&mut self) {
|
||||||
|
self.read_buf.consume_leading_lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_read(&mut self) -> Async<()> {
|
||||||
|
self.io.poll_read()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse<S: Http1Transaction>(&mut self) -> ::Result<Option<MessageHead<S::Incoming>>> {
|
||||||
|
match self.read_buf.read_from(&mut self.io) {
|
||||||
|
Ok(0) => {
|
||||||
|
trace!("parse eof");
|
||||||
|
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "parse eof").into());
|
||||||
|
}
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::WouldBlock => {},
|
||||||
|
_ => return Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match try!(parse::<S, _>(self.read_buf.bytes())) {
|
||||||
|
Some((head, len)) => {
|
||||||
|
trace!("parsed {} bytes out of {}", len, self.read_buf.len());
|
||||||
|
self.read_buf.consume(len);
|
||||||
|
Ok(Some(head))
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if self.read_buf.is_max_size() {
|
||||||
|
debug!("MAX_BUFFER_SIZE reached, closing");
|
||||||
|
Err(::Error::TooLarge)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer<B: AsRef<[u8]>>(&mut self, buf: B) {
|
||||||
|
self.write_buf.write(buf.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn io_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.io
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Read> Read for Buffered<T> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
trace!("Buffered.read self={}, buf={}", self.read_buf.len(), buf.len());
|
||||||
|
let n = try!(self.read_buf.bytes().read(buf));
|
||||||
|
self.read_buf.consume(n);
|
||||||
|
if n == 0 {
|
||||||
|
self.read_buf.reset();
|
||||||
|
self.io.read(&mut buf[n..])
|
||||||
|
} else {
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Write> Write for Buffered<T> {
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
Ok(self.write_buf.write(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.write_buf.write_into(&mut self.io).and_then(|_n| {
|
||||||
|
if self.write_buf.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "wouldblock"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn parse<T: Http1Transaction<Incoming=I>, I>(rdr: &[u8]) -> ParseResult<I> {
|
||||||
|
h1::parse::<T, I>(rdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Cursor<T: AsRef<[u8]>> {
|
||||||
|
bytes: T,
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> Cursor<T> {
|
||||||
|
pub fn new(bytes: T) -> Cursor<T> {
|
||||||
|
Cursor {
|
||||||
|
bytes: bytes,
|
||||||
|
pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_written(&self) -> bool {
|
||||||
|
trace!("Cursor::is_written pos = {}, len = {}", self.pos, self.bytes.as_ref().len());
|
||||||
|
self.pos >= self.bytes.as_ref().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub fn write_to<W: Write>(&mut self, dst: &mut W) -> io::Result<usize> {
|
||||||
|
dst.write(&self.bytes.as_ref()[self.pos..]).map(|n| {
|
||||||
|
self.pos += n;
|
||||||
|
n
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn buf(&self) -> &[u8] {
|
||||||
|
&self.bytes.as_ref()[self.pos..]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn consume(&mut self, num: usize) {
|
||||||
|
trace!("Cursor::consume({})", num);
|
||||||
|
self.pos = ::std::cmp::min(self.bytes.as_ref().len(), self.pos + num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> fmt::Debug for Cursor<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let bytes = self.buf();
|
||||||
|
let reasonable_max = ::std::cmp::min(bytes.len(), 32);
|
||||||
|
write!(f, "Cursor({:?})", &bytes[..reasonable_max])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
if cfg!(not(windows)) {
|
||||||
|
warn!("write_atomic not using writev");
|
||||||
|
}
|
||||||
|
let vec = bufs.concat();
|
||||||
|
self.write(&vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iobuf_write_empty_slice() {
|
||||||
|
use mock::{AsyncIo, Buf as MockBuf};
|
||||||
|
|
||||||
|
let mut mock = AsyncIo::new(MockBuf::new(), 256);
|
||||||
|
mock.error(io::Error::new(io::ErrorKind::Other, "logic error"));
|
||||||
|
|
||||||
|
let mut io_buf = Buffered::new(mock);
|
||||||
|
|
||||||
|
// underlying io will return the logic error upon write,
|
||||||
|
// so we are testing that the io_buf does not trigger a write
|
||||||
|
// when there is nothing to flush
|
||||||
|
io_buf.flush().expect("should short-circuit flush");
|
||||||
|
}
|
||||||
397
src/http/mod.rs
397
src/http/mod.rs
@@ -1,227 +1,44 @@
|
|||||||
//! 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::fmt;
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use header::Connection;
|
use header::{Connection, ConnectionOption};
|
||||||
use header::ConnectionOption::{KeepAlive, Close};
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use net::Transport;
|
|
||||||
use status::StatusCode;
|
use status::StatusCode;
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
use version::HttpVersion;
|
use version::HttpVersion;
|
||||||
use version::HttpVersion::{Http10, Http11};
|
use version::HttpVersion::{Http10, Http11};
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
pub use self::conn::{Conn, KeepAlive, KA};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
pub use self::body::{Body, TokioBody};
|
||||||
|
pub use self::chunk::Chunk;
|
||||||
pub use self::conn::{Conn, MessageHandler, MessageHandlerFactory, Seed, Key, ReadyResult};
|
|
||||||
|
|
||||||
|
mod body;
|
||||||
|
//mod buf;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
pub mod channel;
|
mod chunk;
|
||||||
mod conn;
|
mod conn;
|
||||||
|
mod io;
|
||||||
mod h1;
|
mod h1;
|
||||||
//mod h2;
|
//mod h2;
|
||||||
|
|
||||||
/// Wraps a `Transport` to provide HTTP decoding when reading.
|
/*
|
||||||
#[derive(Debug)]
|
macro_rules! nonblocking {
|
||||||
pub struct Decoder<'a, T: Read + 'a>(DecoderImpl<'a, T>);
|
($e:expr) => ({
|
||||||
|
match $e {
|
||||||
/// 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> Trans<'a, T> {
|
|
||||||
fn get_ref(&self) -> &T {
|
|
||||||
match *self {
|
|
||||||
Trans::Port(ref t) => &*t,
|
|
||||||
Trans::Buf(ref buf) => buf.get_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read from the `Transport`.
|
|
||||||
#[inline]
|
|
||||||
pub 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to read from the `Transport`.
|
|
||||||
///
|
|
||||||
/// This method looks for the `WouldBlock` error. If the read did not block,
|
|
||||||
/// a return value would be `Ok(Some(x))`. If the read would block,
|
|
||||||
/// this method would return `Ok(None)`.
|
|
||||||
#[inline]
|
|
||||||
pub fn try_read(&mut self, buf: &mut [u8]) -> io::Result<Option<usize>> {
|
|
||||||
match self.read(buf) {
|
|
||||||
Ok(n) => Ok(Some(n)),
|
Ok(n) => Ok(Some(n)),
|
||||||
Err(e) => match e.kind() {
|
Err(e) => match e.kind() {
|
||||||
io::ErrorKind::WouldBlock => Ok(None),
|
stdio::ErrorKind::WouldBlock => Ok(None),
|
||||||
_ => Err(e)
|
_ => Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
/// Get a reference to the transport.
|
|
||||||
pub fn get_ref(&self) -> &T {
|
|
||||||
match self.0 {
|
|
||||||
DecoderImpl::H1(_, ref transport) => transport.get_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write to the `Transport`.
|
|
||||||
#[inline]
|
|
||||||
pub 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) => {
|
|
||||||
if encoder.is_closed() {
|
|
||||||
Ok(0)
|
|
||||||
} else {
|
|
||||||
encoder.encode(*transport, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to write to the `Transport`.
|
|
||||||
///
|
|
||||||
/// This method looks for the `WouldBlock` error. If the write did not block,
|
|
||||||
/// a return value would be `Ok(Some(x))`. If the write would block,
|
|
||||||
/// this method would return `Ok(None)`.
|
|
||||||
#[inline]
|
|
||||||
pub fn try_write(&mut self, data: &[u8]) -> io::Result<Option<usize>> {
|
|
||||||
match self.write(data) {
|
|
||||||
Ok(n) => Ok(Some(n)),
|
|
||||||
Err(e) => match e.kind() {
|
|
||||||
io::ErrorKind::WouldBlock => Ok(None),
|
|
||||||
_ => Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Closes an encoder, signaling that no more writing will occur.
|
|
||||||
///
|
|
||||||
/// This is needed for encodings that don't know the length of the content
|
|
||||||
/// beforehand. Most common instance would be usage of
|
|
||||||
/// `Transfer-Enciding: chunked`. You would call `close()` to signal
|
|
||||||
/// the `Encoder` should write the end chunk, or `0\r\n\r\n`.
|
|
||||||
pub fn close(&mut self) {
|
|
||||||
match self.0 {
|
|
||||||
EncoderImpl::H1(ref mut encoder, _) => encoder.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the transport.
|
|
||||||
pub fn get_ref(&self) -> &T {
|
|
||||||
match self.0 {
|
|
||||||
EncoderImpl::H1(_, ref transport) => &*transport
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Read> Read for Decoder<'a, T> {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Transport> Write for Encoder<'a, T> {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
|
||||||
self.write(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.
|
/// An Incoming Message head. Includes request/status line, and headers.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct MessageHead<S> {
|
pub struct MessageHead<S> {
|
||||||
/// HTTP version of the message.
|
/// HTTP version of the message.
|
||||||
pub version: HttpVersion,
|
pub version: HttpVersion,
|
||||||
@@ -234,7 +51,7 @@ pub struct MessageHead<S> {
|
|||||||
/// An incoming request message.
|
/// An incoming request message.
|
||||||
pub type RequestHead = MessageHead<RequestLine>;
|
pub type RequestHead = MessageHead<RequestLine>;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct RequestLine(pub Method, pub RequestUri);
|
pub struct RequestLine(pub Method, pub RequestUri);
|
||||||
|
|
||||||
impl fmt::Display for RequestLine {
|
impl fmt::Display for RequestLine {
|
||||||
@@ -274,18 +91,13 @@ impl Default for RawStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
impl From<MessageHead<::StatusCode>> for MessageHead<RawStatus> {
|
||||||
impl Serialize for RawStatus {
|
fn from(head: MessageHead<::StatusCode>) -> MessageHead<RawStatus> {
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
MessageHead {
|
||||||
(self.0, &self.1).serialize(serializer)
|
subject: head.subject.into(),
|
||||||
}
|
version: head.version,
|
||||||
}
|
headers: head.headers,
|
||||||
|
}
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl Deserialize for RawStatus {
|
|
||||||
fn deserialize<D>(deserializer: &mut D) -> Result<RawStatus, D::Error> where D: Deserializer {
|
|
||||||
let representation: (u16, String) = try!(Deserialize::deserialize(deserializer));
|
|
||||||
Ok(RawStatus(representation.0, Cow::Owned(representation.1)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,174 +106,31 @@ impl Deserialize for RawStatus {
|
|||||||
pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool {
|
pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool {
|
||||||
let ret = match (version, headers.get::<Connection>()) {
|
let ret = match (version, headers.get::<Connection>()) {
|
||||||
(Http10, None) => false,
|
(Http10, None) => false,
|
||||||
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
|
(Http10, Some(conn)) if !conn.contains(&ConnectionOption::KeepAlive) => false,
|
||||||
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
(Http11, Some(conn)) if conn.contains(&ConnectionOption::Close) => false,
|
||||||
_ => true
|
_ => true
|
||||||
};
|
};
|
||||||
trace!("should_keep_alive(version={:?}, header={:?}) = {:?}", version, headers.get::<Connection>(), ret);
|
trace!("should_keep_alive(version={:?}, header={:?}) = {:?}", version, headers.get::<Connection>(), ret);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
pub enum ServerMessage {}
|
pub enum ServerTransaction {}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientMessage {}
|
pub enum ClientTransaction {}
|
||||||
|
|
||||||
pub trait Http1Message {
|
pub trait Http1Transaction {
|
||||||
type Incoming;
|
type Incoming;
|
||||||
type Outgoing: Default;
|
type Outgoing: Default;
|
||||||
|
//type KeepAlive: KeepAlive;
|
||||||
fn parse(bytes: &[u8]) -> ParseResult<Self::Incoming>;
|
fn parse(bytes: &[u8]) -> ParseResult<Self::Incoming>;
|
||||||
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<h1::Decoder>;
|
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<h1::Decoder>;
|
||||||
fn encode(head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> h1::Encoder;
|
fn encode(head: &mut MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> h1::Encoder;
|
||||||
|
fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to signal desired events when working with asynchronous IO.
|
type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>;
|
||||||
#[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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal enum for `Next`
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum Next_ {
|
|
||||||
Read,
|
|
||||||
Write,
|
|
||||||
ReadWrite,
|
|
||||||
Wait,
|
|
||||||
End,
|
|
||||||
Remove,
|
|
||||||
}
|
|
||||||
|
|
||||||
// An enum representing all the possible actions to taken when registering
|
|
||||||
// with the event loop.
|
|
||||||
#[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 reg(&self) -> Reg {
|
|
||||||
self.interest.register()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Next_ {
|
|
||||||
fn register(&self) -> Reg {
|
|
||||||
match *self {
|
|
||||||
Next_::Read => Reg::Read,
|
|
||||||
Next_::Write => Reg::Write,
|
|
||||||
Next_::ReadWrite => Reg::ReadWrite,
|
|
||||||
Next_::Wait => Reg::Wait,
|
|
||||||
Next_::End => Reg::Remove,
|
|
||||||
Next_::Remove => Reg::Remove,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_keep_alive() {
|
fn test_should_keep_alive() {
|
||||||
|
|||||||
84
src/lib.rs
84
src/lib.rs
@@ -13,34 +13,22 @@
|
|||||||
//! Hyper provides both a [Client](client/index.html) and a
|
//! Hyper provides both a [Client](client/index.html) and a
|
||||||
//! [Server](server/index.html), along with a
|
//! [Server](server/index.html), along with a
|
||||||
//! [typed Headers system](header/index.html).
|
//! [typed Headers system](header/index.html).
|
||||||
//!
|
|
||||||
//! If just getting started, consider looking over the **[Guide](../guide/)**.
|
extern crate cookie;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate futures_cpupool;
|
||||||
|
extern crate httparse;
|
||||||
|
#[macro_use] extern crate language_tags;
|
||||||
|
#[macro_use] extern crate log;
|
||||||
|
#[macro_use] pub extern crate mime;
|
||||||
|
extern crate relay;
|
||||||
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 tokio_core as tokio;
|
||||||
#[cfg(feature = "openssl")]
|
extern crate tokio_proto;
|
||||||
extern crate openssl;
|
extern crate tokio_service;
|
||||||
#[cfg(feature = "openssl-verify")]
|
|
||||||
extern crate openssl_verify;
|
|
||||||
#[cfg(feature = "security-framework")]
|
|
||||||
extern crate security_framework;
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
extern crate serde;
|
|
||||||
extern crate cookie;
|
|
||||||
extern crate unicase;
|
extern crate unicase;
|
||||||
extern crate httparse;
|
#[macro_use] extern crate url;
|
||||||
extern crate rotor;
|
|
||||||
extern crate spmc;
|
|
||||||
extern crate vecio;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate language_tags;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate mime as mime_crate;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
#[cfg(all(test, feature = "nightly"))]
|
#[cfg(all(test, feature = "nightly"))]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
@@ -50,20 +38,22 @@ pub use url::Url;
|
|||||||
pub use client::Client;
|
pub use client::Client;
|
||||||
pub use error::{Result, Error};
|
pub use error::{Result, Error};
|
||||||
pub use header::Headers;
|
pub use header::Headers;
|
||||||
pub use http::{Next, Encoder, Decoder, Control, ControlError};
|
pub use http::{Body, Chunk};
|
||||||
pub use method::Method::{self, Get, Head, Post, Delete};
|
pub use method::Method::{self, Get, Head, Post, Delete};
|
||||||
pub use net::{HttpStream, Transport};
|
|
||||||
pub use status::StatusCode::{self, Ok, BadRequest, NotFound};
|
pub use status::StatusCode::{self, Ok, BadRequest, NotFound};
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use uri::RequestUri;
|
pub use uri::RequestUri;
|
||||||
pub use version::HttpVersion;
|
pub use version::HttpVersion;
|
||||||
|
|
||||||
macro_rules! rotor_try {
|
macro_rules! unimplemented {
|
||||||
($e:expr) => ({
|
() => ({
|
||||||
match $e {
|
panic!("unimplemented")
|
||||||
Ok(v) => v,
|
});
|
||||||
Err(e) => return ::rotor::Response::error(e.into())
|
($msg:expr) => ({
|
||||||
}
|
unimplemented!("{}", $msg)
|
||||||
|
});
|
||||||
|
($fmt:expr, $($arg:tt)*) => ({
|
||||||
|
panic!(concat!("unimplemented: ", $fmt), $($arg)*)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,32 +61,10 @@ macro_rules! rotor_try {
|
|||||||
mod mock;
|
mod mock;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod method;
|
mod method;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
mod http;
|
mod http;
|
||||||
pub mod net;
|
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod uri;
|
mod uri;
|
||||||
pub mod version;
|
mod version;
|
||||||
|
|
||||||
/// Re-exporting the mime crate, for convenience.
|
|
||||||
pub mod mime {
|
|
||||||
pub use mime_crate::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#[allow(unconditional_recursion)]
|
|
||||||
fn _assert_send<T: Send>() {
|
|
||||||
_assert_send::<Client>();
|
|
||||||
_assert_send::<client::Request<net::Fresh>>();
|
|
||||||
_assert_send::<client::Response>();
|
|
||||||
_assert_send::<error::Error>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unconditional_recursion)]
|
|
||||||
fn _assert_sync<T: Sync>() {
|
|
||||||
_assert_sync::<Client>();
|
|
||||||
_assert_sync::<error::Error>();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ use error::Error;
|
|||||||
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
|
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
|
||||||
Extension};
|
Extension};
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
|
|
||||||
/// The Request Method (VERB)
|
/// The Request Method (VERB)
|
||||||
///
|
///
|
||||||
@@ -134,21 +132,6 @@ impl Default for Method {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl Serialize for Method {
|
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
|
||||||
format!("{}", self).serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialization")]
|
|
||||||
impl Deserialize for Method {
|
|
||||||
fn deserialize<D>(deserializer: &mut D) -> Result<Method, D::Error> where D: Deserializer {
|
|
||||||
let string_representation: String = try!(Deserialize::deserialize(deserializer));
|
|
||||||
Ok(FromStr::from_str(&string_representation[..]).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|||||||
65
src/mock.rs
65
src/mock.rs
@@ -1,6 +1,9 @@
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use futures::Async;
|
||||||
|
use tokio::io::Io;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Buf {
|
pub struct Buf {
|
||||||
vec: Vec<u8>
|
vec: Vec<u8>
|
||||||
@@ -8,8 +11,12 @@ pub struct Buf {
|
|||||||
|
|
||||||
impl Buf {
|
impl Buf {
|
||||||
pub fn new() -> Buf {
|
pub fn new() -> Buf {
|
||||||
|
Buf::wrap(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap(vec: Vec<u8>) -> Buf {
|
||||||
Buf {
|
Buf {
|
||||||
vec: vec![]
|
vec: vec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,27 +65,41 @@ impl ::vecio::Writev for Buf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Async<T> {
|
pub struct AsyncIo<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
bytes_until_block: usize,
|
bytes_until_block: usize,
|
||||||
|
error: Option<io::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Async<T> {
|
impl<T> AsyncIo<T> {
|
||||||
pub fn new(inner: T, bytes: usize) -> Async<T> {
|
pub fn new(inner: T, bytes: usize) -> AsyncIo<T> {
|
||||||
Async {
|
AsyncIo {
|
||||||
inner: inner,
|
inner: inner,
|
||||||
bytes_until_block: bytes
|
bytes_until_block: bytes,
|
||||||
|
error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block_in(&mut self, bytes: usize) {
|
pub fn block_in(&mut self, bytes: usize) {
|
||||||
self.bytes_until_block = bytes;
|
self.bytes_until_block = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn error(&mut self, err: io::Error) {
|
||||||
|
self.error = Some(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Read> Read for Async<T> {
|
impl AsyncIo<Buf> {
|
||||||
|
pub fn new_buf<T: Into<Vec<u8>>>(buf: T, bytes: usize) -> AsyncIo<Buf> {
|
||||||
|
AsyncIo::new(Buf::wrap(buf.into()), bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Read> Read for AsyncIo<T> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
if self.bytes_until_block == 0 {
|
if let Some(err) = self.error.take() {
|
||||||
|
Err(err)
|
||||||
|
} else if self.bytes_until_block == 0 {
|
||||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
||||||
} else {
|
} else {
|
||||||
let n = cmp::min(self.bytes_until_block, buf.len());
|
let n = cmp::min(self.bytes_until_block, buf.len());
|
||||||
@@ -89,9 +110,11 @@ impl<T: Read> Read for Async<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Write> Write for Async<T> {
|
impl<T: Write> Write for AsyncIo<T> {
|
||||||
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
if self.bytes_until_block == 0 {
|
if let Some(err) = self.error.take() {
|
||||||
|
Err(err)
|
||||||
|
} else if self.bytes_until_block == 0 {
|
||||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
Err(io::Error::new(io::ErrorKind::WouldBlock, "mock block"))
|
||||||
} else {
|
} else {
|
||||||
let n = cmp::min(self.bytes_until_block, data.len());
|
let n = cmp::min(self.bytes_until_block, data.len());
|
||||||
@@ -106,7 +129,25 @@ impl<T: Write> Write for Async<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Write> ::vecio::Writev for Async<T> {
|
impl<T: Read + Write> Io for AsyncIo<T> {
|
||||||
|
fn poll_read(&mut self) -> Async<()> {
|
||||||
|
if self.bytes_until_block == 0 {
|
||||||
|
Async::NotReady
|
||||||
|
} else {
|
||||||
|
Async::Ready(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_write(&mut self) -> Async<()> {
|
||||||
|
if self.bytes_until_block == 0 {
|
||||||
|
Async::NotReady
|
||||||
|
} else {
|
||||||
|
Async::Ready(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Write> ::vecio::Writev for AsyncIo<T> {
|
||||||
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
||||||
let cap = bufs.iter().map(|buf| buf.len()).fold(0, |total, next| total + next);
|
let cap = bufs.iter().map(|buf| buf.len()).fold(0, |total, next| total + next);
|
||||||
let mut vec = Vec::with_capacity(cap);
|
let mut vec = Vec::with_capacity(cap);
|
||||||
@@ -118,7 +159,7 @@ impl<T: Write> ::vecio::Writev for Async<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::ops::Deref for Async<Buf> {
|
impl ::std::ops::Deref for AsyncIo<Buf> {
|
||||||
type Target = [u8];
|
type Target = [u8];
|
||||||
|
|
||||||
fn deref(&self) -> &[u8] {
|
fn deref(&self) -> &[u8] {
|
||||||
|
|||||||
605
src/net.rs
605
src/net.rs
@@ -3,77 +3,12 @@ use std::io::{self, Read, Write};
|
|||||||
use std::net::{SocketAddr};
|
use std::net::{SocketAddr};
|
||||||
use std::option;
|
use std::option;
|
||||||
|
|
||||||
use rotor::mio::tcp::{TcpStream, TcpListener};
|
use std::net::{TcpStream, TcpListener};
|
||||||
use rotor::mio::{Selector, Token, Evented, EventSet, PollOpt, TryAccept};
|
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
pub use self::openssl::{Openssl, OpensslStream};
|
|
||||||
|
|
||||||
#[cfg(feature = "security-framework")]
|
|
||||||
pub use self::security_framework::{SecureTransport, SecureTransportClient, SecureTransportServer};
|
|
||||||
|
|
||||||
/// A trait representing a socket transport that can be used in a Client or Server.
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub trait Transport: Read + Write + Evented + ::vecio::Writev {
|
|
||||||
/// Takes a socket error when event polling notices an `events.is_error()`.
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()>;
|
|
||||||
|
|
||||||
/// Returns if the this transport is blocked on read or write.
|
|
||||||
///
|
|
||||||
/// By default, the user will declare whether they wish to wait on read
|
|
||||||
/// or write events. However, some transports, such as those protected by
|
|
||||||
/// TLS, may be blocked on reading before it can write, or vice versa.
|
|
||||||
fn blocked(&self) -> Option<Blocked> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait representing a socket transport that can be used in a Client or Server.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub trait Transport: Read + Write + Evented {
|
|
||||||
/// Takes a socket error when event polling notices an `events.is_error()`.
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()>;
|
|
||||||
|
|
||||||
/// Returns if the this transport is blocked on read or write.
|
|
||||||
///
|
|
||||||
/// By default, the user will declare whether they wish to wait on read
|
|
||||||
/// or write events. However, some transports, such as those protected by
|
|
||||||
/// TLS, may be blocked on reading before it can write, or vice versa.
|
|
||||||
fn blocked(&self) -> Option<Blocked> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Declares when a transport is blocked from any further action, until the
|
|
||||||
/// corresponding event has occured.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Blocked {
|
|
||||||
/// Blocked on reading
|
|
||||||
Read,
|
|
||||||
/// blocked on writing
|
|
||||||
Write,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Accepts sockets asynchronously.
|
|
||||||
pub trait Accept: Evented {
|
|
||||||
/// The transport type that is accepted.
|
|
||||||
type Output: Transport;
|
|
||||||
/// Accept a socket from the listener, if it doesn not block.
|
|
||||||
fn accept(&self) -> io::Result<Option<Self::Output>>;
|
|
||||||
/// Return the local `SocketAddr` of this listener.
|
|
||||||
fn local_addr(&self) -> io::Result<SocketAddr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An alias to `mio::tcp::TcpStream`.
|
/// An alias to `mio::tcp::TcpStream`.
|
||||||
#[derive(Debug)]
|
//#[derive(Debug)]
|
||||||
pub struct HttpStream(pub TcpStream);
|
pub struct HttpStream(pub ::tokio::net::TcpStream);
|
||||||
|
|
||||||
impl Transport for HttpStream {
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()> {
|
|
||||||
self.0.take_socket_error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for HttpStream {
|
impl Read for HttpStream {
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -94,23 +29,7 @@ impl Write for HttpStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evented for HttpStream {
|
/*
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.register(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.reregister(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
self.0.deregister(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
impl ::vecio::Writev for HttpStream {
|
impl ::vecio::Writev for HttpStream {
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -119,6 +38,7 @@ impl ::vecio::Writev for HttpStream {
|
|||||||
self.0.writev(bufs)
|
self.0.writev(bufs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/// An alias to `mio::tcp::TcpListener`.
|
/// An alias to `mio::tcp::TcpListener`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -137,59 +57,7 @@ impl HttpListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
impl Accept for HttpListener {
|
|
||||||
type Output = HttpStream;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn accept(&self) -> io::Result<Option<HttpStream>> {
|
|
||||||
TryAccept::accept(&self.0).map(|ok| ok.map(HttpStream))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
|
||||||
self.0.local_addr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Evented for HttpListener {
|
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.register(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.reregister(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
self.0.deregister(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for HttpListener {
|
|
||||||
type Item = Self;
|
|
||||||
type IntoIter = option::IntoIter<Self>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
Some(self).into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deprecated
|
|
||||||
///
|
|
||||||
/// Use `SslClient` and `SslServer` instead.
|
|
||||||
pub trait Ssl {
|
|
||||||
/// The protected stream.
|
|
||||||
type Stream: Transport;
|
|
||||||
/// Wrap a client stream with SSL.
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream>;
|
|
||||||
/// Wrap a server stream with SSL.
|
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An abstraction to allow any SSL implementation to be used with client-side `HttpsStream`s.
|
/// An abstraction to allow any SSL implementation to be used with client-side `HttpsStream`s.
|
||||||
pub trait SslClient {
|
pub trait SslClient {
|
||||||
/// The protected stream.
|
/// The protected stream.
|
||||||
@@ -206,21 +74,6 @@ pub trait SslServer {
|
|||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
|
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Ssl> SslClient for S {
|
|
||||||
type Stream = <S as Ssl>::Stream;
|
|
||||||
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
|
||||||
Ssl::wrap_client(self, stream, host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Ssl> SslServer for S {
|
|
||||||
type Stream = <S as Ssl>::Stream;
|
|
||||||
|
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
|
||||||
Ssl::wrap_server(self, stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A stream over the HTTP protocol, possibly protected by TLS.
|
/// A stream over the HTTP protocol, possibly protected by TLS.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -259,6 +112,7 @@ impl<S: Transport> Write for HttpsStream<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
impl<S: Transport> ::vecio::Writev for HttpsStream<S> {
|
impl<S: Transport> ::vecio::Writev for HttpsStream<S> {
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -269,8 +123,10 @@ impl<S: Transport> ::vecio::Writev for HttpsStream<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
impl ::std::os::unix::io::AsRawFd for HttpStream {
|
impl ::std::os::unix::io::AsRawFd for HttpStream {
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -289,50 +145,7 @@ impl<S: Transport + ::std::os::unix::io::AsRawFd> ::std::os::unix::io::AsRawFd f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
impl<S: Transport> Evented for HttpsStream<S> {
|
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
HttpsStream::Http(ref s) => s.register(selector, token, interest, opts),
|
|
||||||
HttpsStream::Https(ref s) => s.register(selector, token, interest, opts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
HttpsStream::Http(ref s) => s.reregister(selector, token, interest, opts),
|
|
||||||
HttpsStream::Https(ref s) => s.reregister(selector, token, interest, opts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
HttpsStream::Http(ref s) => s.deregister(selector),
|
|
||||||
HttpsStream::Https(ref s) => s.deregister(selector),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Transport> Transport for HttpsStream<S> {
|
|
||||||
#[inline]
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
HttpsStream::Http(ref mut s) => s.take_socket_error(),
|
|
||||||
HttpsStream::Https(ref mut s) => s.take_socket_error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn blocked(&self) -> Option<Blocked> {
|
|
||||||
match *self {
|
|
||||||
HttpsStream::Http(ref s) => s.blocked(),
|
|
||||||
HttpsStream::Https(ref s) => s.blocked(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An `HttpListener` over SSL.
|
/// An `HttpListener` over SSL.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -360,6 +173,7 @@ impl<S: SslServer> HttpsListener<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
impl<S: SslServer> Accept for HttpsListener<S> {
|
impl<S: SslServer> Accept for HttpsListener<S> {
|
||||||
type Output = S::Stream;
|
type Output = S::Stream;
|
||||||
|
|
||||||
@@ -382,401 +196,6 @@ impl<S: SslServer> Accept for HttpsListener<S> {
|
|||||||
self.listener.local_addr()
|
self.listener.local_addr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SslServer> Evented for HttpsListener<S> {
|
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.listener.register(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.listener.reregister(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
self.listener.deregister(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: SslServer> IntoIterator for HttpsListener<S> {
|
|
||||||
type Item = Self;
|
|
||||||
type IntoIter = option::IntoIter<Self>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
Some(self).into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _assert_transport() {
|
|
||||||
fn _assert<T: Transport>() {}
|
|
||||||
_assert::<HttpsStream<HttpStream>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(all(not(feature = "openssl"), not(feature = "security-framework")))]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type DefaultConnector = HttpConnector;
|
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type DefaultConnector = HttpsConnector<self::openssl::OpensslClient>;
|
|
||||||
|
|
||||||
#[cfg(all(feature = "security-framework", not(feature = "openssl")))]
|
|
||||||
pub type DefaultConnector = HttpsConnector<self::security_framework::ClientWrapper>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type DefaultTransport = <DefaultConnector as Connect>::Output;
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
*/
|
||||||
mod openssl {
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use rotor::mio::{Selector, Token, Evented, EventSet, PollOpt};
|
|
||||||
|
|
||||||
use openssl::ssl::{Ssl, SslContext, SslStream, SslMethod, SSL_VERIFY_PEER, SSL_OP_NO_SSLV2, SSL_OP_NO_SSLV3, SSL_OP_NO_COMPRESSION};
|
|
||||||
use openssl::ssl::error::StreamError as SslIoError;
|
|
||||||
use openssl::ssl::error::SslError;
|
|
||||||
use openssl::ssl::error::Error as OpensslError;
|
|
||||||
use openssl::x509::X509FileType;
|
|
||||||
|
|
||||||
use super::{HttpStream, Blocked};
|
|
||||||
|
|
||||||
/// An implementation of `Ssl` for OpenSSL.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// use hyper::Server;
|
|
||||||
/// use hyper::net::Openssl;
|
|
||||||
///
|
|
||||||
/// let ssl = Openssl::with_cert_and_key("/home/foo/cert", "/home/foo/key").unwrap();
|
|
||||||
/// Server::https(&"0.0.0.0:443".parse().unwrap(), ssl).unwrap();
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// For complete control, create a `SslContext` with the options you desire
|
|
||||||
/// and then create `Openssl { context: ctx }
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Openssl {
|
|
||||||
/// The `SslContext` from openssl crate.
|
|
||||||
pub context: SslContext
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A client-specific implementation of OpenSSL.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OpensslClient(SslContext);
|
|
||||||
|
|
||||||
impl Default for OpensslClient {
|
|
||||||
fn default() -> OpensslClient {
|
|
||||||
let mut ctx = SslContext::new(SslMethod::Sslv23).unwrap();
|
|
||||||
ctx.set_default_verify_paths().unwrap();
|
|
||||||
ctx.set_options(SSL_OP_NO_SSLV2 | SSL_OP_NO_SSLV3 | SSL_OP_NO_COMPRESSION);
|
|
||||||
// cipher list taken from curl:
|
|
||||||
// https://github.com/curl/curl/blob/5bf5f6ebfcede78ef7c2b16daa41c4b7ba266087/lib/vtls/openssl.h#L120
|
|
||||||
ctx.set_cipher_list("ALL!EXPORT!EXPORT40!EXPORT56!aNULL!LOW!RC4@STRENGTH").unwrap();
|
|
||||||
OpensslClient::new(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpensslClient {
|
|
||||||
/// Creates a new OpensslClient with a custom SslContext
|
|
||||||
pub fn new(ctx: SslContext) -> OpensslClient {
|
|
||||||
OpensslClient(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::SslClient for OpensslClient {
|
|
||||||
type Stream = OpensslStream<HttpStream>;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
|
||||||
let mut ssl = try!(Ssl::new(&self.0));
|
|
||||||
try!(ssl.set_hostname(host));
|
|
||||||
let host = host.to_owned();
|
|
||||||
ssl.set_verify_callback(SSL_VERIFY_PEER, move |p, x| ::openssl_verify::verify_callback(&host, p, x));
|
|
||||||
SslStream::connect(ssl, stream)
|
|
||||||
.map(openssl_stream)
|
|
||||||
.map_err(From::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
|
||||||
let mut ssl = try!(Ssl::new(&self.0));
|
|
||||||
try!(ssl.set_hostname(host));
|
|
||||||
SslStream::connect(ssl, stream)
|
|
||||||
.map(openssl_stream)
|
|
||||||
.map_err(From::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Openssl {
|
|
||||||
fn default() -> Openssl {
|
|
||||||
Openssl {
|
|
||||||
context: SslContext::new(SslMethod::Sslv23).unwrap_or_else(|e| {
|
|
||||||
// if we cannot create a SslContext, that's because of a
|
|
||||||
// serious problem. just crash.
|
|
||||||
panic!("{}", e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Openssl {
|
|
||||||
/// Ease creating an `Openssl` with a certificate and key.
|
|
||||||
pub fn with_cert_and_key<C, K>(cert: C, key: K) -> Result<Openssl, SslError>
|
|
||||||
where C: AsRef<Path>, K: AsRef<Path> {
|
|
||||||
let mut ctx = try!(SslContext::new(SslMethod::Sslv23));
|
|
||||||
try!(ctx.set_cipher_list("ALL!EXPORT!EXPORT40!EXPORT56!aNULL!LOW!RC4@STRENGTH"));
|
|
||||||
try!(ctx.set_certificate_file(cert.as_ref(), X509FileType::PEM));
|
|
||||||
try!(ctx.set_private_key_file(key.as_ref(), X509FileType::PEM));
|
|
||||||
Ok(Openssl { context: ctx })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Ssl for Openssl {
|
|
||||||
type Stream = OpensslStream<HttpStream>;
|
|
||||||
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
|
||||||
let ssl = try!(Ssl::new(&self.context));
|
|
||||||
try!(ssl.set_hostname(host));
|
|
||||||
SslStream::connect(ssl, stream)
|
|
||||||
.map(openssl_stream)
|
|
||||||
.map_err(From::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
|
||||||
match SslStream::accept(&self.context, stream) {
|
|
||||||
Ok(ssl_stream) => Ok(openssl_stream(ssl_stream)),
|
|
||||||
Err(SslIoError(e)) => {
|
|
||||||
Err(io::Error::new(io::ErrorKind::ConnectionAborted, e).into())
|
|
||||||
},
|
|
||||||
Err(e) => Err(e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A transport protected by OpenSSL.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OpensslStream<T> {
|
|
||||||
stream: SslStream<T>,
|
|
||||||
blocked: Option<Blocked>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn openssl_stream<T>(inner: SslStream<T>) -> OpensslStream<T> {
|
|
||||||
OpensslStream {
|
|
||||||
stream: inner,
|
|
||||||
blocked: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: super::Transport> io::Read for OpensslStream<T> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.blocked = None;
|
|
||||||
self.stream.ssl_read(buf).or_else(|e| match e {
|
|
||||||
OpensslError::ZeroReturn => Ok(0),
|
|
||||||
OpensslError::WantWrite(e) => {
|
|
||||||
self.blocked = Some(Blocked::Write);
|
|
||||||
Err(e)
|
|
||||||
},
|
|
||||||
OpensslError::WantRead(e) | OpensslError::Stream(e) => Err(e),
|
|
||||||
e => Err(io::Error::new(io::ErrorKind::Other, e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: super::Transport> io::Write for OpensslStream<T> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.blocked = None;
|
|
||||||
self.stream.ssl_write(buf).or_else(|e| match e {
|
|
||||||
OpensslError::ZeroReturn => Ok(0),
|
|
||||||
OpensslError::WantRead(e) => {
|
|
||||||
self.blocked = Some(Blocked::Read);
|
|
||||||
Err(e)
|
|
||||||
},
|
|
||||||
OpensslError::WantWrite(e) | OpensslError::Stream(e) => Err(e),
|
|
||||||
e => Err(io::Error::new(io::ErrorKind::Other, e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.stream.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<T: super::Transport> Evented for OpensslStream<T> {
|
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.stream.get_ref().register(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.stream.get_ref().reregister(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
self.stream.get_ref().deregister(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: super::Transport> ::vecio::Writev for OpensslStream<T> {
|
|
||||||
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
|
||||||
let vec = bufs.concat();
|
|
||||||
self.write(&vec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: super::Transport> super::Transport for OpensslStream<T> {
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()> {
|
|
||||||
self.stream.get_mut().take_socket_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blocked(&self) -> Option<super::Blocked> {
|
|
||||||
self.blocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "security-framework")]
|
|
||||||
mod security_framework {
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
|
|
||||||
use error::Error;
|
|
||||||
use net::{SslClient, SslServer, HttpStream, Transport, Blocked};
|
|
||||||
|
|
||||||
use security_framework::secure_transport::SslStream;
|
|
||||||
pub use security_framework::secure_transport::{ClientBuilder as SecureTransportClient, ServerBuilder as SecureTransportServer};
|
|
||||||
use rotor::mio::{Selector, Token, Evented, EventSet, PollOpt};
|
|
||||||
|
|
||||||
impl SslClient for SecureTransportClient {
|
|
||||||
type Stream = SecureTransport<HttpStream>;
|
|
||||||
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
|
||||||
match self.handshake(host, journal(stream)) {
|
|
||||||
Ok(s) => Ok(SecureTransport(s)),
|
|
||||||
Err(e) => Err(Error::Ssl(e.into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SslServer for SecureTransportServer {
|
|
||||||
type Stream = SecureTransport<HttpStream>;
|
|
||||||
|
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
|
||||||
match self.handshake(journal(stream)) {
|
|
||||||
Ok(s) => Ok(SecureTransport(s)),
|
|
||||||
Err(e) => Err(Error::Ssl(e.into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A transport protected by Security Framework.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SecureTransport<T>(SslStream<Journal<T>>);
|
|
||||||
|
|
||||||
impl<T: Transport> io::Read for SecureTransport<T> {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.0.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Transport> io::Write for SecureTransport<T> {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.0.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<T: Transport> Evented for SecureTransport<T> {
|
|
||||||
#[inline]
|
|
||||||
fn register(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.get_ref().inner.register(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reregister(&self, selector: &mut Selector, token: Token, interest: EventSet, opts: PollOpt) -> io::Result<()> {
|
|
||||||
self.0.get_ref().inner.reregister(selector, token, interest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deregister(&self, selector: &mut Selector) -> io::Result<()> {
|
|
||||||
self.0.get_ref().inner.deregister(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Transport> ::vecio::Writev for SecureTransport<T> {
|
|
||||||
fn writev(&mut self, bufs: &[&[u8]]) -> io::Result<usize> {
|
|
||||||
let vec = bufs.concat();
|
|
||||||
self.write(&vec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Transport> Transport for SecureTransport<T> {
|
|
||||||
fn take_socket_error(&mut self) -> io::Result<()> {
|
|
||||||
self.0.get_mut().inner.take_socket_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blocked(&self) -> Option<super::Blocked> {
|
|
||||||
self.0.get_ref().blocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Records if this object was blocked on reading or writing.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Journal<T> {
|
|
||||||
inner: T,
|
|
||||||
blocked: Option<Blocked>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn journal<T: Read + Write>(inner: T) -> Journal<T> {
|
|
||||||
Journal {
|
|
||||||
inner: inner,
|
|
||||||
blocked: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Read> Read for Journal<T> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.blocked = None;
|
|
||||||
self.inner.read(buf).map_err(|e| match e.kind() {
|
|
||||||
io::ErrorKind::WouldBlock => {
|
|
||||||
self.blocked = Some(Blocked::Read);
|
|
||||||
e
|
|
||||||
},
|
|
||||||
_ => e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Write> Write for Journal<T> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.blocked = None;
|
|
||||||
self.inner.write(buf).map_err(|e| match e.kind() {
|
|
||||||
io::ErrorKind::WouldBlock => {
|
|
||||||
self.blocked = Some(Blocked::Write);
|
|
||||||
e
|
|
||||||
},
|
|
||||||
_ => e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
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, transport: &T) -> Next {
|
|
||||||
trace!("on_incoming {:?}", head);
|
|
||||||
let req = request::new(head, transport);
|
|
||||||
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,65 +1,55 @@
|
|||||||
//! HTTP Server
|
//! HTTP Server
|
||||||
//!
|
//!
|
||||||
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
|
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
|
||||||
//! them off to a `Handler`.
|
//! them off to a `Service`.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::SocketAddr;
|
use std::io;
|
||||||
use std::sync::Arc;
|
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use rotor::mio::{EventSet, PollOpt};
|
use futures::{Future, Map};
|
||||||
use rotor::{self, Scope};
|
use futures::stream::{Stream};
|
||||||
|
use futures::sync::oneshot;
|
||||||
|
|
||||||
|
use tokio::io::Io;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tokio::reactor::{Core, Handle};
|
||||||
|
use tokio_proto::BindServer;
|
||||||
|
use tokio_proto::streaming::Message;
|
||||||
|
use tokio_proto::streaming::pipeline::ServerProto;
|
||||||
|
pub use tokio_service::{NewService, Service};
|
||||||
|
|
||||||
|
pub use self::accept::Accept;
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
||||||
use http::{self, Next, ReadyResult};
|
use http;
|
||||||
|
|
||||||
pub use net::{Accept, HttpListener, HttpsListener};
|
|
||||||
use net::{SslServer, Transport};
|
|
||||||
|
|
||||||
|
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
mod message;
|
|
||||||
|
|
||||||
/// A configured `Server` ready to run.
|
type HttpIncoming = ::tokio::net::Incoming;
|
||||||
pub struct ServerLoop<A, H> where A: Accept, H: HandlerFactory<A::Output> {
|
|
||||||
inner: Option<(rotor::Loop<ServerFsm<A, H>>, Context<H>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Accept, H: HandlerFactory<A::Output>> fmt::Debug for ServerLoop<A, H> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.pad("ServerLoop")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Server that can accept incoming network requests.
|
/// A Server that can accept incoming network requests.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Server<A> {
|
pub struct Server<A> {
|
||||||
lead_listener: A,
|
accepter: A,
|
||||||
other_listeners: Vec<A>,
|
addr: SocketAddr,
|
||||||
keep_alive: bool,
|
keep_alive: bool,
|
||||||
idle_timeout: Option<Duration>,
|
//idle_timeout: Option<Duration>,
|
||||||
max_sockets: usize,
|
//max_sockets: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Accept> Server<A> {
|
impl<A: Accept> Server<A> {
|
||||||
/// Creates a new Server from one or more Listeners.
|
/// Creates a new Server from a Stream of Ios.
|
||||||
///
|
///
|
||||||
/// Panics if listeners is an empty iterator.
|
/// The addr is the socket address the accepter is listening on.
|
||||||
pub fn new<I: IntoIterator<Item = A>>(listeners: I) -> Server<A> {
|
pub fn new(accepter: A, addr: SocketAddr) -> Server<A> {
|
||||||
let mut listeners = listeners.into_iter();
|
|
||||||
let lead_listener = listeners.next().expect("Server::new requires at least 1 listener");
|
|
||||||
let other_listeners = listeners.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Server {
|
Server {
|
||||||
lead_listener: lead_listener,
|
accepter: accepter,
|
||||||
other_listeners: other_listeners,
|
addr: addr,
|
||||||
keep_alive: true,
|
keep_alive: true,
|
||||||
idle_timeout: Some(Duration::from_secs(10)),
|
//idle_timeout: Some(Duration::from_secs(75)),
|
||||||
max_sockets: 4096,
|
//max_sockets: 4096,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,14 +61,17 @@ impl<A: Accept> Server<A> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Sets how long an idle connection will be kept before closing.
|
/// Sets how long an idle connection will be kept before closing.
|
||||||
///
|
///
|
||||||
/// Default is 10 seconds.
|
/// Default is 75 seconds.
|
||||||
pub fn idle_timeout(mut self, val: Option<Duration>) -> Server<A> {
|
pub fn idle_timeout(mut self, val: Option<Duration>) -> Server<A> {
|
||||||
self.idle_timeout = val;
|
self.idle_timeout = val;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
/// Sets the maximum open sockets for this Server.
|
/// Sets the maximum open sockets for this Server.
|
||||||
///
|
///
|
||||||
/// Default is 4096, but most servers can handle much more than this.
|
/// Default is 4096, but most servers can handle much more than this.
|
||||||
@@ -86,20 +79,21 @@ impl<A: Accept> Server<A> {
|
|||||||
self.max_sockets = val;
|
self.max_sockets = val;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server<HttpListener> { //<H: HandlerFactory<<HttpListener as Accept>::Output>> Server<HttpListener, H> {
|
impl Server<HttpIncoming> {
|
||||||
/// Creates a new HTTP server config listening on the provided address.
|
/// Creates a new HTTP server config listening on the provided address.
|
||||||
pub fn http(addr: &SocketAddr) -> ::Result<Server<HttpListener>> {
|
pub fn http(addr: &SocketAddr, handle: &Handle) -> ::Result<Server<HttpIncoming>> {
|
||||||
use ::rotor::mio::tcp::TcpListener;
|
let listener = try!(StdTcpListener::bind(addr));
|
||||||
TcpListener::bind(addr)
|
let addr = try!(listener.local_addr());
|
||||||
.map(HttpListener)
|
let listener = try!(TcpListener::from_listener(listener, &addr, handle));
|
||||||
.map(Server::new)
|
Ok(Server::new(listener.incoming(), addr))
|
||||||
.map_err(From::from)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
impl<S: SslServer> Server<HttpsListener<S>> {
|
impl<S: SslServer> Server<HttpsListener<S>> {
|
||||||
/// Creates a new server config that will handle `HttpStream`s over SSL.
|
/// Creates a new server config that will handle `HttpStream`s over SSL.
|
||||||
///
|
///
|
||||||
@@ -110,304 +104,227 @@ impl<S: SslServer> Server<HttpsListener<S>> {
|
|||||||
.map_err(From::from)
|
.map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
impl<A: Accept> Server<A> {
|
impl<A: Accept> Server<A> {
|
||||||
/// Binds to a socket and starts handling connections.
|
/// Binds to a socket and starts handling connections.
|
||||||
pub fn handle<H>(self, factory: H) -> ::Result<(Listening, ServerLoop<A, H>)>
|
pub fn handle<H>(self, factory: H, handle: &Handle) -> ::Result<SocketAddr>
|
||||||
where H: HandlerFactory<A::Output> {
|
where H: NewService<Request=Request, Response=Response, Error=::Error> + 'static {
|
||||||
let shutdown = Arc::new(AtomicBool::new(false));
|
let binder = HttpServer {
|
||||||
|
keep_alive: self.keep_alive,
|
||||||
let mut config = rotor::Config::new();
|
|
||||||
config.slab_capacity(self.max_sockets);
|
|
||||||
config.mio().notify_capacity(self.max_sockets);
|
|
||||||
let keep_alive = self.keep_alive;
|
|
||||||
let idle_timeout = self.idle_timeout;
|
|
||||||
let mut loop_ = rotor::Loop::new(&config).unwrap();
|
|
||||||
|
|
||||||
let mut addrs = Vec::with_capacity(1 + self.other_listeners.len());
|
|
||||||
|
|
||||||
// Add the lead listener. This one handles shutdown messages.
|
|
||||||
let mut notifier = None;
|
|
||||||
{
|
|
||||||
let notifier = &mut notifier;
|
|
||||||
let listener = self.lead_listener;
|
|
||||||
addrs.push(try!(listener.local_addr()));
|
|
||||||
let shutdown_rx = shutdown.clone();
|
|
||||||
loop_.add_machine_with(move |scope| {
|
|
||||||
*notifier = Some(scope.notifier());
|
|
||||||
rotor_try!(scope.register(&listener, EventSet::readable(), PollOpt::level()));
|
|
||||||
rotor::Response::ok(ServerFsm::Listener(listener, shutdown_rx))
|
|
||||||
}).unwrap();
|
|
||||||
}
|
|
||||||
let notifier = notifier.expect("loop.add_machine failed");
|
|
||||||
|
|
||||||
// Add the other listeners.
|
|
||||||
for listener in self.other_listeners {
|
|
||||||
addrs.push(try!(listener.local_addr()));
|
|
||||||
let shutdown_rx = shutdown.clone();
|
|
||||||
loop_.add_machine_with(move |scope| {
|
|
||||||
rotor_try!(scope.register(&listener, EventSet::readable(), PollOpt::level()));
|
|
||||||
rotor::Response::ok(ServerFsm::Listener(listener, shutdown_rx))
|
|
||||||
}).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let listening = Listening {
|
|
||||||
addrs: addrs,
|
|
||||||
shutdown: (shutdown, notifier),
|
|
||||||
};
|
};
|
||||||
let server = ServerLoop {
|
let inner_handle = handle.clone();
|
||||||
inner: Some((loop_, Context {
|
handle.spawn(self.accepter.accept().for_each(move |(socket, remote_addr)| {
|
||||||
factory: factory,
|
let service = HttpService {
|
||||||
idle_timeout: idle_timeout,
|
inner: try!(factory.new_service()),
|
||||||
keep_alive: keep_alive,
|
remote_addr: remote_addr,
|
||||||
}))
|
};
|
||||||
};
|
binder.bind_server(&inner_handle, socket, service);
|
||||||
Ok((listening, server))
|
Ok(())
|
||||||
|
}).map_err(|e| {
|
||||||
|
error!("listener io error: {:?}", e);
|
||||||
|
()
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(self.addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Server<()> {
|
||||||
|
/// Create a server that owns its event loop.
|
||||||
|
///
|
||||||
|
/// The returned `ServerLoop` can be used to run the loop forever in the
|
||||||
|
/// thread. The returned `Listening` can be sent to another thread, and
|
||||||
|
/// used to shutdown the `ServerLoop`.
|
||||||
|
pub fn standalone<F>(closure: F) -> ::Result<(Listening, ServerLoop)>
|
||||||
|
where F: FnOnce(&Handle) -> ::Result<SocketAddr> {
|
||||||
|
let core = try!(Core::new());
|
||||||
|
let handle = core.handle();
|
||||||
|
let addr = try!(closure(&handle));
|
||||||
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
|
Ok((
|
||||||
|
Listening {
|
||||||
|
addr: addr,
|
||||||
|
shutdown: shutdown_tx,
|
||||||
|
},
|
||||||
|
ServerLoop {
|
||||||
|
inner: Some((core, shutdown_rx)),
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
impl<A: Accept, H: HandlerFactory<A::Output>> ServerLoop<A, H> {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A configured `Server` ready to run.
|
||||||
|
pub struct ServerLoop {
|
||||||
|
inner: Option<(Core, oneshot::Receiver<()>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ServerLoop {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad("ServerLoop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerLoop {
|
||||||
/// Runs the server forever in this loop.
|
/// Runs the server forever in this loop.
|
||||||
///
|
///
|
||||||
/// This will block the current thread.
|
/// This will block the current thread.
|
||||||
pub fn run(self) {
|
pub fn run(self) {
|
||||||
// drop will take care of it.
|
// drop will take care of it.
|
||||||
|
trace!("ServerLoop::run()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Accept, H: HandlerFactory<A::Output>> Drop for ServerLoop<A, H> {
|
impl Drop for ServerLoop {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.inner.take().map(|(loop_, ctx)| {
|
self.inner.take().map(|(mut loop_, shutdown)| {
|
||||||
let _ = loop_.run(ctx);
|
debug!("ServerLoop::drop running");
|
||||||
|
let _ = loop_.run(shutdown.or_else(|_dropped| ::futures::future::empty::<(), oneshot::Canceled>()));
|
||||||
|
debug!("Server closed");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Context<F> {
|
|
||||||
factory: F,
|
|
||||||
idle_timeout: Option<Duration>,
|
|
||||||
keep_alive: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<()>) -> Option<Self::Output> {
|
|
||||||
Some(message::Message::new(self.factory.create(seed.control())))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keep_alive_interest(&self) -> Next {
|
|
||||||
if let Some(dur) = self.idle_timeout {
|
|
||||||
Next::read().timeout(dur)
|
|
||||||
} else {
|
|
||||||
Next::read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, Next::read(), scope.notifier(), scope.now())
|
|
||||||
.keep_alive(scope.keep_alive)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ready(self, events: EventSet, scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
match self {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ServerFsm::Conn(conn) => {
|
|
||||||
let mut conn = Some(conn);
|
|
||||||
loop {
|
|
||||||
match conn.take().unwrap().ready(events, scope) {
|
|
||||||
ReadyResult::Continue(c) => conn = Some(c),
|
|
||||||
ReadyResult::Done(res) => {
|
|
||||||
return match res {
|
|
||||||
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 spawned(self, _scope: &mut Scope<Self::Context>) -> rotor::Response<Self, Self::Seed> {
|
|
||||||
match self {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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 handle of the running server.
|
/// A handle of the running server.
|
||||||
pub struct Listening {
|
pub struct Listening {
|
||||||
addrs: Vec<SocketAddr>,
|
addr: SocketAddr,
|
||||||
shutdown: (Arc<AtomicBool>, rotor::Notifier),
|
shutdown: ::futures::sync::oneshot::Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
f.debug_struct("Listening")
|
f.debug_struct("Listening")
|
||||||
.field("addrs", &self.addrs)
|
.field("addr", &self.addr)
|
||||||
.field("closed", &self.shutdown.0.load(Ordering::Relaxed))
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Listening {
|
impl fmt::Display for Listening {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
for (i, addr) in self.addrs().iter().enumerate() {
|
fmt::Display::fmt(&self.addr, f)
|
||||||
if i > 1 {
|
|
||||||
try!(f.write_str(", "));
|
|
||||||
}
|
|
||||||
try!(fmt::Display::fmt(addr, f));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Listening {
|
impl Listening {
|
||||||
/// The addresses this server is listening on.
|
/// The addresses this server is listening on.
|
||||||
pub fn addrs(&self) -> &[SocketAddr] {
|
pub fn addr(&self) -> &SocketAddr {
|
||||||
&self.addrs
|
&self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the server from listening to its socket address.
|
/// Stop the server from listening to its socket address.
|
||||||
pub fn close(self) {
|
pub fn close(self) {
|
||||||
debug!("closing server {}", self);
|
debug!("closing server {}", self);
|
||||||
self.shutdown.0.store(true, Ordering::Release);
|
self.shutdown.complete(());
|
||||||
self.shutdown.1.wakeup().unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait to react to server events that happen for each message.
|
struct HttpServer {
|
||||||
///
|
keep_alive: bool,
|
||||||
/// Each event handler returns its desired `Next` action.
|
}
|
||||||
pub trait Handler<T: Transport> {
|
|
||||||
/// This event occurs first, triggering when a `Request` has been parsed.
|
|
||||||
fn on_request(&mut self, request: Request<T>) -> 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;
|
|
||||||
|
|
||||||
/// This event occurs whenever an `Error` occurs outside of the other events.
|
impl<T: Io + 'static> ServerProto<T> for HttpServer {
|
||||||
|
type Request = http::RequestHead;
|
||||||
|
type RequestBody = http::Chunk;
|
||||||
|
type Response = ResponseHead;
|
||||||
|
type ResponseBody = http::Chunk;
|
||||||
|
type Error = ::Error;
|
||||||
|
type Transport = http::Conn<T, http::ServerTransaction>;
|
||||||
|
type BindTransport = io::Result<http::Conn<T, http::ServerTransaction>>;
|
||||||
|
|
||||||
|
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||||
|
let ka = if self.keep_alive {
|
||||||
|
http::KA::Busy
|
||||||
|
} else {
|
||||||
|
http::KA::Disabled
|
||||||
|
};
|
||||||
|
Ok(http::Conn::new(io, ka))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HttpService<T> {
|
||||||
|
inner: T,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_response_to_message(res: Response) -> Message<ResponseHead, http::TokioBody> {
|
||||||
|
let (head, body) = response::split(res);
|
||||||
|
if let Some(body) = body {
|
||||||
|
Message::WithBody(head, body.into())
|
||||||
|
} else {
|
||||||
|
Message::WithoutBody(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseHead = http::MessageHead<::StatusCode>;
|
||||||
|
|
||||||
|
impl<T> Service for HttpService<T>
|
||||||
|
where T: Service<Request=Request, Response=Response, Error=::Error>,
|
||||||
|
{
|
||||||
|
type Request = Message<http::RequestHead, http::TokioBody>;
|
||||||
|
type Response = Message<ResponseHead, http::TokioBody>;
|
||||||
|
type Error = ::Error;
|
||||||
|
type Future = Map<T::Future, fn(Response) -> Message<ResponseHead, http::TokioBody>>;
|
||||||
|
|
||||||
|
fn call(&self, message: Self::Request) -> Self::Future {
|
||||||
|
let (head, body) = match message {
|
||||||
|
Message::WithoutBody(head) => (head, http::Body::empty()),
|
||||||
|
Message::WithBody(head, body) => (head, body.into()),
|
||||||
|
};
|
||||||
|
let req = request::new(self.remote_addr, head, body);
|
||||||
|
self.inner.call(req).map(map_response_to_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//private so the `Acceptor` type can stay internal
|
||||||
|
mod accept {
|
||||||
|
use std::io;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use futures::{Stream, Poll};
|
||||||
|
use tokio::io::Io;
|
||||||
|
|
||||||
|
/// An Acceptor is an incoming Stream of Io.
|
||||||
///
|
///
|
||||||
/// This could IO errors while waiting for events, or a timeout, etc.
|
/// This trait is not implemented directly, and only exists to make the
|
||||||
fn on_error(&mut self, err: ::Error) -> Next where Self: Sized {
|
/// intent clearer. A `Stream<Item=(Io, SocketAddr), Error=io::Error>`
|
||||||
debug!("default Handler.on_error({:?})", err);
|
/// should be implemented instead.
|
||||||
http::Next::remove()
|
pub trait Accept: Stream<Error=io::Error> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
type Output: Io + 'static;
|
||||||
|
#[doc(hidden)]
|
||||||
|
type Stream: Stream<Item=(Self::Output, SocketAddr), Error=io::Error> + 'static;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn accept(self) -> Accepter<Self::Stream, Self::Output>
|
||||||
|
where Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This event occurs when this Handler has requested to remove the Transport.
|
#[allow(missing_debug_implementations)]
|
||||||
fn on_remove(self, _transport: T) where Self: Sized {
|
pub struct Accepter<T: Stream<Item=(I, SocketAddr), Error=io::Error> + 'static, I: Io + 'static>(T, ::std::marker::PhantomData<I>);
|
||||||
debug!("default Handler.on_remove");
|
|
||||||
}
|
impl<T, I> Stream for Accepter<T, I>
|
||||||
}
|
where T: Stream<Item=(I, SocketAddr), Error=io::Error>,
|
||||||
|
I: Io + 'static,
|
||||||
|
{
|
||||||
/// Used to create a `Handler` when a new message is received by the server.
|
type Item = T::Item;
|
||||||
pub trait HandlerFactory<T: Transport> {
|
type Error = io::Error;
|
||||||
/// The `Handler` to use for the incoming message.
|
|
||||||
type Output: Handler<T>;
|
#[inline]
|
||||||
/// Creates the associated `Handler`.
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
fn create(&mut self, ctrl: http::Control) -> Self::Output;
|
self.0.poll()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
impl<F, H, T> HandlerFactory<T> for F
|
|
||||||
where F: FnMut(http::Control) -> H, H: Handler<T>, T: Transport {
|
impl<T, I> Accept for T
|
||||||
type Output = H;
|
where T: Stream<Item=(I, SocketAddr), Error=io::Error> + 'static,
|
||||||
fn create(&mut self, ctrl: http::Control) -> H {
|
I: Io + 'static,
|
||||||
self(ctrl)
|
{
|
||||||
|
type Output = I;
|
||||||
|
type Stream = T;
|
||||||
|
|
||||||
|
fn accept(self) -> Accepter<Self, I> {
|
||||||
|
Accepter(self, ::std::marker::PhantomData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,48 +4,25 @@
|
|||||||
//! target URI, headers, and message body.
|
//! target URI, headers, and message body.
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use version::HttpVersion;
|
use version::HttpVersion;
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use http::{RequestHead, MessageHead, RequestLine};
|
use http::{RequestHead, MessageHead, RequestLine, Body};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
|
|
||||||
pub fn new<'a, T>(incoming: RequestHead, transport: &'a T) -> Request<'a, T> {
|
|
||||||
let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming;
|
|
||||||
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
|
||||||
debug!("{:#?}", headers);
|
|
||||||
|
|
||||||
Request {
|
|
||||||
method: method,
|
|
||||||
uri: uri,
|
|
||||||
headers: headers,
|
|
||||||
version: version,
|
|
||||||
transport: transport,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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, T: 'a> {
|
pub struct Request {
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: RequestUri,
|
uri: RequestUri,
|
||||||
version: HttpVersion,
|
version: HttpVersion,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
transport: &'a T,
|
remote_addr: SocketAddr,
|
||||||
|
body: Body,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> fmt::Debug for Request<'a, T> {
|
impl Request {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Request")
|
|
||||||
.field("method", &self.method)
|
|
||||||
.field("uri", &self.uri)
|
|
||||||
.field("version", &self.version)
|
|
||||||
.field("headers", &self.headers)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Request<'a, T> {
|
|
||||||
/// The `Method`, such as `Get`, `Post`, etc.
|
/// The `Method`, such as `Get`, `Post`, etc.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&self) -> &Method { &self.method }
|
pub fn method(&self) -> &Method { &self.method }
|
||||||
@@ -54,10 +31,6 @@ impl<'a, T> Request<'a, T> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &Headers { &self.headers }
|
pub fn headers(&self) -> &Headers { &self.headers }
|
||||||
|
|
||||||
/// The underlying `Transport` of this request.
|
|
||||||
#[inline]
|
|
||||||
pub fn transport(&self) -> &'a T { self.transport }
|
|
||||||
|
|
||||||
/// The target request-uri for this request.
|
/// The target request-uri for this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri(&self) -> &RequestUri { &self.uri }
|
pub fn uri(&self) -> &RequestUri { &self.uri }
|
||||||
@@ -66,6 +39,10 @@ impl<'a, T> Request<'a, T> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> &HttpVersion { &self.version }
|
pub fn version(&self) -> &HttpVersion { &self.version }
|
||||||
|
|
||||||
|
/// The remote socket address of this request
|
||||||
|
#[inline]
|
||||||
|
pub fn remote_addr(&self) -> &SocketAddr { &self.remote_addr }
|
||||||
|
|
||||||
/// The target path of this Request.
|
/// The target path of this Request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> Option<&str> {
|
pub fn path(&self) -> Option<&str> {
|
||||||
@@ -86,12 +63,44 @@ impl<'a, T> Request<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the `Body` of this `Request`.
|
||||||
|
#[inline]
|
||||||
|
pub fn body(self) -> Body {
|
||||||
|
self.body
|
||||||
|
}
|
||||||
|
|
||||||
/// Deconstruct this Request into its pieces.
|
/// Deconstruct this Request into its pieces.
|
||||||
///
|
///
|
||||||
/// Modifying these pieces will have no effect on how hyper behaves.
|
/// Modifying these pieces will have no effect on how hyper behaves.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn deconstruct(self) -> (Method, RequestUri, HttpVersion, Headers) {
|
pub fn deconstruct(self) -> (Method, RequestUri, HttpVersion, Headers, Body) {
|
||||||
(self.method, self.uri, self.version, self.headers)
|
(self.method, self.uri, self.version, self.headers, self.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Request {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Request")
|
||||||
|
.field("method", &self.method)
|
||||||
|
.field("uri", &self.uri)
|
||||||
|
.field("version", &self.version)
|
||||||
|
.field("remote_addr", &self.remote_addr)
|
||||||
|
.field("headers", &self.headers)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(addr: SocketAddr, incoming: RequestHead, body: Body) -> Request {
|
||||||
|
let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming;
|
||||||
|
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
||||||
|
debug!("{:#?}", headers);
|
||||||
|
|
||||||
|
Request {
|
||||||
|
method: method,
|
||||||
|
uri: uri,
|
||||||
|
headers: headers,
|
||||||
|
version: version,
|
||||||
|
remote_addr: addr,
|
||||||
|
body: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
//! Server Responses
|
use std::fmt;
|
||||||
//!
|
|
||||||
//! These are responses sent by a `hyper::Server` to clients, after
|
|
||||||
//! receiving a request.
|
|
||||||
use header;
|
use header;
|
||||||
use http;
|
use http::{self, Body};
|
||||||
use status::StatusCode;
|
use status::StatusCode;
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
|
/// The Response sent to a client after receiving a Request in a Service.
|
||||||
/// 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`.
|
||||||
#[derive(Debug)]
|
#[derive(Default)]
|
||||||
pub struct Response<'a> {
|
pub struct Response {
|
||||||
head: &'a mut http::MessageHead<StatusCode>,
|
head: http::MessageHead<StatusCode>,
|
||||||
|
body: Option<Body>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Response<'a> {
|
impl Response {
|
||||||
|
/// Create a new Response.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Response {
|
||||||
|
Response::default()
|
||||||
|
}
|
||||||
|
|
||||||
/// The headers of this response.
|
/// The headers of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &header::Headers { &self.head.headers }
|
pub fn headers(&self) -> &header::Headers { &self.head.headers }
|
||||||
@@ -35,16 +39,65 @@ impl<'a> Response<'a> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut header::Headers { &mut self.head.headers }
|
pub fn headers_mut(&mut self) -> &mut header::Headers { &mut self.head.headers }
|
||||||
|
|
||||||
/// Set the status of this response.
|
/// Set the `StatusCode` for this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_status(&mut self, status: StatusCode) {
|
pub fn set_status(&mut self, status: StatusCode) {
|
||||||
self.head.subject = status;
|
self.head.subject = status;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new Response that can be used to write to a network stream.
|
/// Set the body.
|
||||||
pub fn new(head: &mut http::MessageHead<StatusCode>) -> Response {
|
#[inline]
|
||||||
Response {
|
pub fn set_body<T: Into<Body>>(&mut self, body: T) {
|
||||||
head: head
|
self.body = Some(body.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the status and move the Response.
|
||||||
|
///
|
||||||
|
/// Useful for the "builder-style" pattern.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_status(mut self, status: StatusCode) -> Self {
|
||||||
|
self.set_status(status);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a header and move the Response.
|
||||||
|
///
|
||||||
|
/// Useful for the "builder-style" pattern.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_header<H: header::Header>(mut self, header: H) -> Self {
|
||||||
|
self.head.headers.set(header);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the headers and move the Response.
|
||||||
|
///
|
||||||
|
/// Useful for the "builder-style" pattern.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_headers(mut self, headers: header::Headers) -> Self {
|
||||||
|
self.head.headers = headers;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the body and move the Response.
|
||||||
|
///
|
||||||
|
/// Useful for the "builder-style" pattern.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_body<T: Into<Body>>(mut self, body: T) -> Self {
|
||||||
|
self.set_body(body);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Response {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Response")
|
||||||
|
.field("status", &self.head.subject)
|
||||||
|
.field("version", &self.head.version)
|
||||||
|
.field("headers", &self.head.headers)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split(res: Response) -> (http::MessageHead<StatusCode>, Option<Body>) {
|
||||||
|
(res.head, res.body)
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ impl FromStr for RequestUri {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<RequestUri, Error> {
|
fn from_str(s: &str) -> Result<RequestUri, Error> {
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
if bytes == [] {
|
if bytes.len() == 0 {
|
||||||
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
|
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
|
||||||
} else if bytes == b"*" {
|
} else if bytes == b"*" {
|
||||||
Ok(RequestUri::Star)
|
Ok(RequestUri::Star)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ pub enum HttpVersion {
|
|||||||
H2,
|
H2,
|
||||||
/// `HTTP/2.0` over cleartext
|
/// `HTTP/2.0` over cleartext
|
||||||
H2c,
|
H2c,
|
||||||
|
#[doc(hidden)]
|
||||||
|
__DontMatchMe,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HttpVersion {
|
impl fmt::Display for HttpVersion {
|
||||||
@@ -29,6 +31,7 @@ impl fmt::Display for HttpVersion {
|
|||||||
Http11 => "HTTP/1.1",
|
Http11 => "HTTP/1.1",
|
||||||
H2 => "h2",
|
H2 => "h2",
|
||||||
H2c => "h2c",
|
H2c => "h2c",
|
||||||
|
HttpVersion::__DontMatchMe => unreachable!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
354
tests/client.rs
354
tests/client.rs
@@ -1,189 +1,27 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate tokio_core;
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::sync::mpsc;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hyper::client::{Handler, Request, Response, HttpConnector};
|
use hyper::client::{Client, Request, HttpConnector};
|
||||||
use hyper::{Method, StatusCode, Next, Encoder, Decoder};
|
use hyper::{Method, StatusCode};
|
||||||
use hyper::header::Headers;
|
|
||||||
use hyper::net::HttpStream;
|
|
||||||
|
|
||||||
fn s(bytes: &[u8]) -> &str {
|
use futures::Future;
|
||||||
::std::str::from_utf8(bytes.as_ref()).unwrap()
|
use futures::sync::oneshot;
|
||||||
|
|
||||||
|
use tokio_core::reactor::{Core, Handle};
|
||||||
|
|
||||||
|
fn client(handle: &Handle) -> Client<HttpConnector> {
|
||||||
|
Client::new(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn s(buf: &[u8]) -> &str {
|
||||||
struct TestHandler {
|
::std::str::from_utf8(buf).unwrap()
|
||||||
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());
|
|
||||||
req.headers_mut().extend(self.opts.headers.iter());
|
|
||||||
if self.opts.body.is_some() {
|
|
||||||
Next::write()
|
|
||||||
} else {
|
|
||||||
read(&self.opts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_request_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
|
||||||
if let Some(ref mut body) = self.opts.body {
|
|
||||||
let n = encoder.write(body).unwrap();
|
|
||||||
*body = &body[n..];
|
|
||||||
|
|
||||||
if !body.is_empty() {
|
|
||||||
return Next::write()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encoder.close();
|
|
||||||
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 {
|
|
||||||
body: Option<&'static [u8]>,
|
|
||||||
method: Method,
|
|
||||||
headers: Headers,
|
|
||||||
read_timeout: Option<Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Opts {
|
|
||||||
fn default() -> Opts {
|
|
||||||
Opts {
|
|
||||||
body: None,
|
|
||||||
method: Method::Get,
|
|
||||||
headers: Headers::new(),
|
|
||||||
read_timeout: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn opts() -> Opts {
|
|
||||||
Opts::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opts {
|
|
||||||
fn method(mut self, method: Method) -> Opts {
|
|
||||||
self.method = method;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn header<H: ::hyper::header::Header>(mut self, header: H) -> Opts {
|
|
||||||
self.headers.set(header);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body(mut self, body: Option<&'static [u8]>) -> Opts {
|
|
||||||
self.body = body;
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
@@ -206,43 +44,50 @@ macro_rules! test {
|
|||||||
) => (
|
) => (
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
#[allow(unused)]
|
#![allow(unused)]
|
||||||
use hyper::header::*;
|
use hyper::header::*;
|
||||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
let addr = server.local_addr().unwrap();
|
let addr = server.local_addr().unwrap();
|
||||||
let client = client();
|
let mut core = Core::new().unwrap();
|
||||||
let opts = opts()
|
let client = client(&core.handle());
|
||||||
.method(Method::$client_method)
|
let mut req = Request::new(Method::$client_method, format!($client_url, addr=addr).parse().unwrap());
|
||||||
.body($request_body);
|
|
||||||
$(
|
$(
|
||||||
let opts = opts.header($request_headers);
|
req.headers_mut().set($request_headers);
|
||||||
)*
|
)*
|
||||||
let res = client.request(format!($client_url, addr=addr), opts);
|
|
||||||
|
|
||||||
let mut inc = server.accept().unwrap().0;
|
if let Some(body) = $request_body {
|
||||||
inc.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
let body: &'static str = body;
|
||||||
inc.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
req.set_body(body);
|
||||||
let expected = format!($server_expected, addr=addr);
|
|
||||||
let mut buf = [0; 4096];
|
|
||||||
let mut n = 0;
|
|
||||||
while n < buf.len() && n < expected.len() {
|
|
||||||
n += inc.read(&mut buf[n..]).unwrap();
|
|
||||||
}
|
}
|
||||||
assert_eq!(s(&buf[..n]), expected);
|
let res = client.request(req);
|
||||||
|
|
||||||
inc.write_all($server_reply.as_ref()).unwrap();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
if let Msg::Head(head) = res.recv().unwrap() {
|
thread::spawn(move || {
|
||||||
assert_eq!(head.status(), &StatusCode::$client_status);
|
let mut inc = server.accept().unwrap().0;
|
||||||
$(
|
inc.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
assert_eq!(head.headers().get(), Some(&$response_headers));
|
inc.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
)*
|
let expected = format!($server_expected, addr=addr);
|
||||||
} else {
|
let mut buf = [0; 4096];
|
||||||
panic!("we lost the head!");
|
let mut n = 0;
|
||||||
}
|
while n < buf.len() && n < expected.len() {
|
||||||
//drop(inc);
|
n += inc.read(&mut buf[n..]).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(s(&buf[..n]), expected);
|
||||||
|
|
||||||
assert!(res.recv().is_err());
|
inc.write_all($server_reply.as_ref()).unwrap();
|
||||||
|
tx.complete(());
|
||||||
|
});
|
||||||
|
|
||||||
|
let rx = rx.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||||
|
|
||||||
|
let work = res.join(rx).map(|r| r.0);
|
||||||
|
|
||||||
|
let res = core.run(work).unwrap();
|
||||||
|
assert_eq!(res.status(), &StatusCode::$client_status);
|
||||||
|
$(
|
||||||
|
assert_eq!(res.headers().get(), Some(&$response_headers));
|
||||||
|
)*
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -311,7 +156,7 @@ test! {
|
|||||||
headers: [
|
headers: [
|
||||||
ContentLength(7),
|
ContentLength(7),
|
||||||
],
|
],
|
||||||
body: Some(b"foo bar"),
|
body: Some("foo bar"),
|
||||||
response:
|
response:
|
||||||
status: Ok,
|
status: Ok,
|
||||||
headers: [],
|
headers: [],
|
||||||
@@ -340,51 +185,86 @@ test! {
|
|||||||
headers: [
|
headers: [
|
||||||
TransferEncoding::chunked(),
|
TransferEncoding::chunked(),
|
||||||
],
|
],
|
||||||
body: Some(b"foo bar baz"),
|
body: Some("foo bar baz"),
|
||||||
response:
|
response:
|
||||||
status: Ok,
|
status: Ok,
|
||||||
headers: [],
|
headers: [],
|
||||||
body: None,
|
body: None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn client_keep_alive() {
|
fn client_keep_alive() {
|
||||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
let addr = server.local_addr().unwrap();
|
let addr = server.local_addr().unwrap();
|
||||||
let client = client();
|
let mut core = Core::new().unwrap();
|
||||||
let res = client.request(format!("http://{}/a", addr), opts());
|
let client = client(&core.handle());
|
||||||
|
|
||||||
let mut sock = server.accept().unwrap().0;
|
|
||||||
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
|
||||||
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
|
||||||
let mut buf = [0; 4096];
|
|
||||||
sock.read(&mut buf).expect("read 1");
|
|
||||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
|
||||||
|
|
||||||
while let Ok(_) = res.recv() {}
|
let (tx1, rx1) = oneshot::channel();
|
||||||
|
let (tx2, rx2) = oneshot::channel();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut sock = server.accept().unwrap().0;
|
||||||
|
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
sock.read(&mut buf).expect("read 1");
|
||||||
|
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
||||||
|
tx1.complete(());
|
||||||
|
|
||||||
let res = client.request(format!("http://{}/b", addr), opts());
|
sock.read(&mut buf).expect("read 2");
|
||||||
sock.read(&mut buf).expect("read 2");
|
let second_get = b"GET /b HTTP/1.1\r\n";
|
||||||
let second_get = b"GET /b HTTP/1.1\r\n";
|
assert_eq!(&buf[..second_get.len()], second_get);
|
||||||
assert_eq!(&buf[..second_get.len()], second_get);
|
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
tx2.complete(());
|
||||||
|
});
|
||||||
|
|
||||||
while let Ok(_) = res.recv() {}
|
|
||||||
|
|
||||||
|
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||||
|
let res = client.get(format!("http://{}/a", addr).parse().unwrap());
|
||||||
|
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||||
|
|
||||||
|
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||||
|
let res = client.get(format!("http://{}/b", addr).parse().unwrap());
|
||||||
|
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_pooled_socket_disconnected() {
|
||||||
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = server.local_addr().unwrap();
|
||||||
|
let mut core = Core::new().unwrap();
|
||||||
|
let client = client(&core.handle());
|
||||||
|
|
||||||
|
|
||||||
|
let (tx1, rx1) = oneshot::channel();
|
||||||
|
let (tx2, rx2) = oneshot::channel();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut sock = server.accept().unwrap().0;
|
||||||
|
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
sock.read(&mut buf).expect("read 1");
|
||||||
|
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
||||||
|
drop(sock);
|
||||||
|
tx1.complete(());
|
||||||
|
|
||||||
|
let mut sock = server.accept().unwrap().0;
|
||||||
|
sock.read(&mut buf).expect("read 2");
|
||||||
|
let second_get = b"GET /b HTTP/1.1\r\n";
|
||||||
|
assert_eq!(&buf[..second_get.len()], second_get);
|
||||||
|
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||||
|
tx2.complete(());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||||
|
let res = client.get(format!("http://{}/a", addr).parse().unwrap());
|
||||||
|
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||||
|
|
||||||
|
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||||
|
let res = client.get(format!("http://{}/b", addr).parse().unwrap());
|
||||||
|
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
314
tests/server.rs
314
tests/server.rs
@@ -1,30 +1,28 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate spmc;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
use futures::stream::Stream;
|
||||||
|
|
||||||
use std::net::{TcpStream, SocketAddr};
|
use std::net::{TcpStream, SocketAddr};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hyper::{Next, Encoder, Decoder};
|
use hyper::server::{Server, Request, Response, Service, NewService};
|
||||||
use hyper::net::{HttpListener, HttpStream};
|
|
||||||
use hyper::server::{Server, Handler, Request, Response};
|
|
||||||
|
|
||||||
struct Serve {
|
struct Serve {
|
||||||
listening: Option<hyper::server::Listening>,
|
listening: Option<hyper::server::Listening>,
|
||||||
msg_rx: mpsc::Receiver<Msg>,
|
msg_rx: mpsc::Receiver<Msg>,
|
||||||
reply_tx: mpsc::Sender<Reply>,
|
reply_tx: spmc::Sender<Reply>,
|
||||||
|
spawn_rx: mpsc::Receiver<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serve {
|
impl Serve {
|
||||||
fn addrs(&self) -> &[SocketAddr] {
|
|
||||||
self.listening.as_ref().unwrap().addrs()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addr(&self) -> &SocketAddr {
|
fn addr(&self) -> &SocketAddr {
|
||||||
let addrs = self.addrs();
|
self.listening.as_ref().unwrap().addr()
|
||||||
assert!(addrs.len() == 1);
|
|
||||||
&addrs[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -49,7 +47,7 @@ impl Serve {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ReplyBuilder<'a> {
|
struct ReplyBuilder<'a> {
|
||||||
tx: &'a mpsc::Sender<Reply>,
|
tx: &'a spmc::Sender<Reply>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ReplyBuilder<'a> {
|
impl<'a> ReplyBuilder<'a> {
|
||||||
@@ -73,16 +71,18 @@ impl<'a> ReplyBuilder<'a> {
|
|||||||
impl Drop for Serve {
|
impl Drop for Serve {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.listening.take().unwrap().close();
|
self.listening.take().unwrap().close();
|
||||||
|
self.spawn_rx.recv().expect("server thread should shutdown cleanly");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestHandler {
|
#[derive(Clone)]
|
||||||
|
struct TestService {
|
||||||
tx: mpsc::Sender<Msg>,
|
tx: mpsc::Sender<Msg>,
|
||||||
reply: Vec<Reply>,
|
reply: spmc::Receiver<Reply>,
|
||||||
peeked: Option<Vec<u8>>,
|
_timeout: Option<Duration>,
|
||||||
timeout: Option<Duration>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
enum Reply {
|
enum Reply {
|
||||||
Status(hyper::StatusCode),
|
Status(hyper::StatusCode),
|
||||||
Headers(hyper::Headers),
|
Headers(hyper::Headers),
|
||||||
@@ -94,72 +94,57 @@ enum Msg {
|
|||||||
Chunk(Vec<u8>),
|
Chunk(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestHandler {
|
impl NewService for TestService {
|
||||||
fn next(&self, next: Next) -> Next {
|
type Request = Request;
|
||||||
if let Some(dur) = self.timeout {
|
type Response = Response;
|
||||||
next.timeout(dur)
|
type Error = hyper::Error;
|
||||||
} else {
|
|
||||||
next
|
type Instance = TestService;
|
||||||
}
|
|
||||||
|
fn new_service(&self) -> std::io::Result<TestService> {
|
||||||
|
Ok(self.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<HttpStream> for TestHandler {
|
impl Service for TestService {
|
||||||
fn on_request(&mut self, _req: Request<HttpStream>) -> Next {
|
type Request = Request;
|
||||||
//self.tx.send(Msg::Head(req)).unwrap();
|
type Response = Response;
|
||||||
self.next(Next::read())
|
type Error = hyper::Error;
|
||||||
|
type Future = Box<Future<Item=Response, Error=hyper::Error>>;
|
||||||
|
fn call(&self, req: Request) -> Self::Future {
|
||||||
|
let tx = self.tx.clone();
|
||||||
|
let replies = self.reply.clone();
|
||||||
|
req.body().for_each(move |chunk| {
|
||||||
|
tx.send(Msg::Chunk(chunk.to_vec())).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}).map(move |_| {
|
||||||
|
let mut res = Response::new();
|
||||||
|
while let Ok(reply) = replies.try_recv() {
|
||||||
|
match reply {
|
||||||
|
Reply::Status(s) => {
|
||||||
|
res.set_status(s);
|
||||||
|
},
|
||||||
|
Reply::Headers(headers) => {
|
||||||
|
*res.headers_mut() = headers;
|
||||||
|
},
|
||||||
|
Reply::Body(body) => {
|
||||||
|
res.set_body(body);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
fn connect(addr: &SocketAddr) -> TcpStream {
|
||||||
for reply in self.reply.drain(..) {
|
let req = TcpStream::connect(addr).unwrap();
|
||||||
match reply {
|
req.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||||
Reply::Status(s) => {
|
req.set_write_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||||
res.set_status(s);
|
req
|
||||||
},
|
|
||||||
Reply::Headers(headers) => {
|
|
||||||
use std::iter::Extend;
|
|
||||||
res.headers_mut().extend(headers.iter());
|
|
||||||
},
|
|
||||||
Reply::Body(body) => {
|
|
||||||
self.peeked = Some(body);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
fn serve() -> Serve {
|
||||||
@@ -167,45 +152,37 @@ fn serve() -> Serve {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn serve_with_timeout(dur: Option<Duration>) -> Serve {
|
fn serve_with_timeout(dur: Option<Duration>) -> Serve {
|
||||||
serve_n_with_timeout(1, dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve_n(n: u32) -> Serve {
|
|
||||||
serve_n_with_timeout(n, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve_n_with_timeout(n: u32, dur: Option<Duration>) -> Serve {
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
let (thread_tx, thread_rx) = mpsc::channel();
|
||||||
|
let (spawn_tx, spawn_rx) = mpsc::channel();
|
||||||
let (msg_tx, msg_rx) = mpsc::channel();
|
let (msg_tx, msg_rx) = mpsc::channel();
|
||||||
let (reply_tx, reply_rx) = mpsc::channel();
|
let (reply_tx, reply_rx) = spmc::channel();
|
||||||
|
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
let listeners = (0..n).map(|_| HttpListener::bind(&addr).unwrap());
|
|
||||||
let (listening, server) = Server::new(listeners)
|
|
||||||
.handle(move |_| {
|
|
||||||
let mut replies = Vec::new();
|
|
||||||
while let Ok(reply) = reply_rx.try_recv() {
|
|
||||||
replies.push(reply);
|
|
||||||
}
|
|
||||||
TestHandler {
|
|
||||||
tx: msg_tx.clone(),
|
|
||||||
timeout: dur,
|
|
||||||
reply: replies,
|
|
||||||
peeked: None,
|
|
||||||
}
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
|
let thread_name = format!("test-server-{:?}", dur);
|
||||||
let thread_name = format!("test-server-{}: {:?}", listening, dur);
|
|
||||||
thread::Builder::new().name(thread_name).spawn(move || {
|
thread::Builder::new().name(thread_name).spawn(move || {
|
||||||
|
let (listening, server) = Server::standalone(move |tokio| {
|
||||||
|
Server::http(&addr, tokio).unwrap()
|
||||||
|
.handle(TestService {
|
||||||
|
tx: msg_tx.clone(),
|
||||||
|
_timeout: dur,
|
||||||
|
reply: reply_rx,
|
||||||
|
}, tokio)
|
||||||
|
}).unwrap();
|
||||||
|
thread_tx.send(listening).unwrap();
|
||||||
server.run();
|
server.run();
|
||||||
|
spawn_tx.send(()).unwrap();
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
let listening = thread_rx.recv().unwrap();
|
||||||
|
|
||||||
Serve {
|
Serve {
|
||||||
listening: Some(listening),
|
listening: Some(listening),
|
||||||
msg_rx: msg_rx,
|
msg_rx: msg_rx,
|
||||||
reply_tx: reply_tx,
|
reply_tx: reply_tx,
|
||||||
|
spawn_rx: spawn_rx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,10 +190,12 @@ fn serve_n_with_timeout(n: u32, dur: Option<Duration>) -> Serve {
|
|||||||
fn server_get_should_ignore_body() {
|
fn server_get_should_ignore_body() {
|
||||||
let server = serve();
|
let server = serve();
|
||||||
|
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
|
// Connection: close = don't try to parse the body as a new request
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n
|
||||||
\r\n\
|
\r\n\
|
||||||
I shouldn't be read.\r\n\
|
I shouldn't be read.\r\n\
|
||||||
").unwrap();
|
").unwrap();
|
||||||
@@ -228,7 +207,7 @@ fn server_get_should_ignore_body() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn server_get_with_body() {
|
fn server_get_with_body() {
|
||||||
let server = serve();
|
let server = serve();
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
@@ -244,17 +223,18 @@ fn server_get_with_body() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn server_get_fixed_response() {
|
fn server_get_fixed_response() {
|
||||||
|
|
||||||
let foo_bar = b"foo bar baz";
|
let foo_bar = b"foo bar baz";
|
||||||
let server = serve();
|
let server = serve();
|
||||||
server.reply()
|
server.reply()
|
||||||
.status(hyper::Ok)
|
.status(hyper::Ok)
|
||||||
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
||||||
.body(foo_bar);
|
.body(foo_bar);
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Connection: close\r\n
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
").unwrap();
|
").unwrap();
|
||||||
let mut body = String::new();
|
let mut body = String::new();
|
||||||
@@ -272,7 +252,7 @@ fn server_get_chunked_response() {
|
|||||||
.status(hyper::Ok)
|
.status(hyper::Ok)
|
||||||
.header(hyper::header::TransferEncoding::chunked())
|
.header(hyper::header::TransferEncoding::chunked())
|
||||||
.body(foo_bar);
|
.body(foo_bar);
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
@@ -286,10 +266,67 @@ fn server_get_chunked_response() {
|
|||||||
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_get_chunked_response_with_ka() {
|
||||||
|
let foo_bar = b"foo bar baz";
|
||||||
|
let foo_bar_chunk = b"\r\nfoo bar baz\r\n0\r\n\r\n";
|
||||||
|
let server = serve();
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.header(hyper::header::TransferEncoding::chunked())
|
||||||
|
.body(foo_bar);
|
||||||
|
let mut req = connect(server.addr());
|
||||||
|
req.write_all(b"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: keep-alive\r\n\
|
||||||
|
\r\n\
|
||||||
|
").expect("writing 1");
|
||||||
|
|
||||||
|
let mut buf = [0; 1024 * 4];
|
||||||
|
let mut ntotal = 0;
|
||||||
|
loop {
|
||||||
|
let n = req.read(&mut buf[ntotal..]).expect("reading 1");
|
||||||
|
ntotal = ntotal + n;
|
||||||
|
assert!(ntotal < buf.len());
|
||||||
|
if &buf[ntotal - foo_bar_chunk.len()..ntotal] == foo_bar_chunk {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// try again!
|
||||||
|
|
||||||
|
let quux = b"zar quux";
|
||||||
|
server.reply()
|
||||||
|
.status(hyper::Ok)
|
||||||
|
.header(hyper::header::ContentLength(quux.len() as u64))
|
||||||
|
.body(quux);
|
||||||
|
req.write_all(b"\
|
||||||
|
GET /quux HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
").expect("writing 2");
|
||||||
|
|
||||||
|
let mut buf = [0; 1024 * 8];
|
||||||
|
loop {
|
||||||
|
let n = req.read(&mut buf[..]).expect("reading 2");
|
||||||
|
assert!(n > 0, "n = {}", n);
|
||||||
|
if n < buf.len() && n > 0 {
|
||||||
|
if &buf[n - quux.len()..n] == quux {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn server_post_with_chunked_body() {
|
fn server_post_with_chunked_body() {
|
||||||
let server = serve();
|
let server = serve();
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
@@ -302,56 +339,31 @@ fn server_post_with_chunked_body() {
|
|||||||
2\r\n\
|
2\r\n\
|
||||||
rt\r\n\
|
rt\r\n\
|
||||||
0\r\n\
|
0\r\n\
|
||||||
\r\n
|
\r\n\
|
||||||
").unwrap();
|
").unwrap();
|
||||||
req.read(&mut [0; 256]).unwrap();
|
req.read(&mut [0; 256]).unwrap();
|
||||||
|
|
||||||
assert_eq!(server.body(), b"qwert");
|
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]
|
#[test]
|
||||||
fn server_empty_response_chunked() {
|
fn server_empty_response_chunked() {
|
||||||
let server = serve();
|
let server = serve();
|
||||||
|
|
||||||
server.reply()
|
server.reply()
|
||||||
.status(hyper::Ok)
|
.status(hyper::Ok)
|
||||||
.body("");
|
.body("");
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
|
||||||
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
|
Content-Length: 0\r\n
|
||||||
Connection: close\r\n
|
Connection: close\r\n
|
||||||
\r\n\
|
\r\n\
|
||||||
").unwrap();
|
").unwrap();
|
||||||
|
|
||||||
|
|
||||||
let mut response = String::new();
|
let mut response = String::new();
|
||||||
req.read_to_string(&mut response).unwrap();
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
|
||||||
@@ -369,49 +381,44 @@ fn server_empty_response_chunked() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn server_empty_response_chunked_without_calling_write() {
|
fn server_empty_response_chunked_without_body_should_set_content_length() {
|
||||||
|
extern crate pretty_env_logger;
|
||||||
|
let _ = pretty_env_logger::init();
|
||||||
let server = serve();
|
let server = serve();
|
||||||
server.reply()
|
server.reply()
|
||||||
.status(hyper::Ok)
|
.status(hyper::Ok)
|
||||||
.header(hyper::header::TransferEncoding::chunked());
|
.header(hyper::header::TransferEncoding::chunked());
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Connection: close\r\n
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
").unwrap();
|
").unwrap();
|
||||||
|
|
||||||
let mut response = String::new();
|
let mut response = String::new();
|
||||||
req.read_to_string(&mut response).unwrap();
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
|
||||||
assert!(response.contains("Transfer-Encoding: chunked\r\n"));
|
assert!(!response.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
|
assert!(response.contains("Content-Length: 0\r\n"));
|
||||||
|
|
||||||
let mut lines = response.lines();
|
let mut lines = response.lines();
|
||||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||||
|
|
||||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||||
assert_eq!(lines.next(), Some(""));
|
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);
|
assert_eq!(lines.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn server_keep_alive() {
|
fn server_keep_alive() {
|
||||||
extern crate env_logger;
|
|
||||||
env_logger::init().unwrap();
|
|
||||||
|
|
||||||
let foo_bar = b"foo bar baz";
|
let foo_bar = b"foo bar baz";
|
||||||
let server = serve();
|
let server = serve();
|
||||||
server.reply()
|
server.reply()
|
||||||
.status(hyper::Ok)
|
.status(hyper::Ok)
|
||||||
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
||||||
.body(foo_bar);
|
.body(foo_bar);
|
||||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
let mut req = connect(server.addr());
|
||||||
req.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
|
||||||
req.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
|
||||||
req.write_all(b"\
|
req.write_all(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
@@ -426,7 +433,6 @@ fn server_keep_alive() {
|
|||||||
if &buf[n - foo_bar.len()..n] == foo_bar {
|
if &buf[n - foo_bar.len()..n] == foo_bar {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
println!("{:?}", ::std::str::from_utf8(&buf[..n]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,6 +463,7 @@ fn server_keep_alive() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn server_get_with_body_three_listeners() {
|
fn server_get_with_body_three_listeners() {
|
||||||
let server = serve_n(3);
|
let server = serve_n(3);
|
||||||
@@ -479,3 +486,4 @@ fn server_get_with_body_three_listeners() {
|
|||||||
assert_eq!(server.body(), comparison);
|
assert_eq!(server.body(), comparison);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user