mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 16:02:59 +01:00
add simple http client
This commit is contained in:
parent
537144f0b9
commit
550c5f55b6
136
src/body.rs
136
src/body.rs
@ -1,13 +1,39 @@
|
|||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use futures::Stream;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, mem};
|
use std::{fmt, mem};
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use futures::{Async, Poll, Stream};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
/// Type represent streaming body
|
/// Type represent streaming body
|
||||||
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
|
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
|
||||||
|
|
||||||
|
/// Different type of bory
|
||||||
|
pub enum BodyType {
|
||||||
|
None,
|
||||||
|
Zero,
|
||||||
|
Sized(usize),
|
||||||
|
Unsized,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type that provides this trait can be streamed to a peer.
|
||||||
|
pub trait MessageBody {
|
||||||
|
fn tp(&self) -> BodyType;
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for () {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents various types of http message body.
|
/// Represents various types of http message body.
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
/// Empty response. `Content-Length` header is set to `0`
|
/// Empty response. `Content-Length` header is set to `0`
|
||||||
@ -241,6 +267,112 @@ impl AsRef<[u8]> for Binary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MessageBody for Bytes {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Sized(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(Some(mem::replace(self, Bytes::new()))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for &'static str {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Sized(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(Some(Bytes::from_static(
|
||||||
|
mem::replace(self, "").as_ref(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for &'static [u8] {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Sized(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(Some(Bytes::from_static(mem::replace(
|
||||||
|
self, b"",
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for Vec<u8> {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Sized(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(Some(Bytes::from(mem::replace(
|
||||||
|
self,
|
||||||
|
Vec::new(),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for String {
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Sized(self.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(Some(Bytes::from(
|
||||||
|
mem::replace(self, String::new()).into_bytes(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct MessageBodyStream<S> {
|
||||||
|
stream: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> MessageBodyStream<S>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Bytes, Error = Error>,
|
||||||
|
{
|
||||||
|
pub fn new(stream: S) -> Self {
|
||||||
|
MessageBodyStream { stream }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> MessageBody for MessageBodyStream<S>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Bytes, Error = Error>,
|
||||||
|
{
|
||||||
|
fn tp(&self) -> BodyType {
|
||||||
|
BodyType::Unsized
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||||
|
self.stream.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -14,8 +14,13 @@ pub struct Connect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Connect {
|
impl Connect {
|
||||||
|
/// Create `Connect` message for specified `Uri`
|
||||||
|
pub fn new(uri: Uri) -> Connect {
|
||||||
|
Connect { uri }
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct `Uri` instance and create `Connect` message.
|
/// Construct `Uri` instance and create `Connect` message.
|
||||||
pub fn new<U>(uri: U) -> Result<Connect, HttpError>
|
pub fn try_from<U>(uri: U) -> Result<Connect, HttpError>
|
||||||
where
|
where
|
||||||
Uri: HttpTryFrom<U>,
|
Uri: HttpTryFrom<U>,
|
||||||
{
|
{
|
||||||
@ -24,11 +29,6 @@ impl Connect {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `Connect` message for specified `Uri`
|
|
||||||
pub fn with(uri: Uri) -> Connect {
|
|
||||||
Connect { uri }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_secure(&self) -> bool {
|
pub(crate) fn is_secure(&self) -> bool {
|
||||||
if let Some(scheme) = self.uri.scheme_part() {
|
if let Some(scheme) = self.uri.scheme_part() {
|
||||||
scheme.as_str() == "https"
|
scheme.as_str() == "https"
|
||||||
|
@ -6,7 +6,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
|||||||
use super::pool::Acquired;
|
use super::pool::Acquired;
|
||||||
|
|
||||||
/// HTTP client connection
|
/// HTTP client connection
|
||||||
pub struct Connection<T: AsyncRead + AsyncWrite + 'static> {
|
pub struct Connection<T> {
|
||||||
io: T,
|
io: T,
|
||||||
created: time::Instant,
|
created: time::Instant,
|
||||||
pool: Option<Acquired<T>>,
|
pool: Option<Acquired<T>>,
|
||||||
@ -14,7 +14,7 @@ pub struct Connection<T: AsyncRead + AsyncWrite + 'static> {
|
|||||||
|
|
||||||
impl<T> fmt::Debug for Connection<T>
|
impl<T> fmt::Debug for Connection<T>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + fmt::Debug + 'static,
|
T: fmt::Debug,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Connection {:?}", self.io)
|
write!(f, "Connection {:?}", self.io)
|
||||||
|
@ -130,7 +130,7 @@ impl Connector {
|
|||||||
self,
|
self,
|
||||||
) -> impl Service<
|
) -> impl Service<
|
||||||
Request = Connect,
|
Request = Connect,
|
||||||
Response = impl AsyncRead + AsyncWrite + fmt::Debug,
|
Response = Connection<impl AsyncRead + AsyncWrite + fmt::Debug>,
|
||||||
Error = ConnectorError,
|
Error = ConnectorError,
|
||||||
> + Clone {
|
> + Clone {
|
||||||
#[cfg(not(feature = "ssl"))]
|
#[cfg(not(feature = "ssl"))]
|
||||||
|
@ -17,6 +17,8 @@ use native_tls::Error as SslError;
|
|||||||
))]
|
))]
|
||||||
use std::io::Error as SslError;
|
use std::io::Error as SslError;
|
||||||
|
|
||||||
|
use error::{Error, ParseError};
|
||||||
|
|
||||||
/// A set of errors that can occur while connecting to an HTTP host
|
/// A set of errors that can occur while connecting to an HTTP host
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum ConnectorError {
|
pub enum ConnectorError {
|
||||||
@ -75,3 +77,41 @@ impl From<ResolveError> for ConnectorError {
|
|||||||
ConnectorError::Resolver(err)
|
ConnectorError::Resolver(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur during request sending and response reading
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SendRequestError {
|
||||||
|
/// Failed to connect to host
|
||||||
|
// #[fail(display = "Failed to connect to host: {}", _0)]
|
||||||
|
Connector(ConnectorError),
|
||||||
|
/// Error sending request
|
||||||
|
Send(io::Error),
|
||||||
|
/// Error parsing response
|
||||||
|
Response(ParseError),
|
||||||
|
/// Error sending request body
|
||||||
|
Body(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for SendRequestError {
|
||||||
|
fn from(err: io::Error) -> SendRequestError {
|
||||||
|
SendRequestError::Send(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ConnectorError> for SendRequestError {
|
||||||
|
fn from(err: ConnectorError) -> SendRequestError {
|
||||||
|
SendRequestError::Connector(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseError> for SendRequestError {
|
||||||
|
fn from(err: ParseError) -> SendRequestError {
|
||||||
|
SendRequestError::Response(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for SendRequestError {
|
||||||
|
fn from(err: Error) -> SendRequestError {
|
||||||
|
SendRequestError::Body(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,12 +3,14 @@ mod connect;
|
|||||||
mod connection;
|
mod connection;
|
||||||
mod connector;
|
mod connector;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod pipeline;
|
||||||
mod pool;
|
mod pool;
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
pub use self::connect::Connect;
|
pub use self::connect::Connect;
|
||||||
|
pub use self::connection::Connection;
|
||||||
pub use self::connector::Connector;
|
pub use self::connector::Connector;
|
||||||
pub use self::error::{ConnectorError, InvalidUrlKind};
|
pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError};
|
||||||
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
pub use self::request::{ClientRequest, ClientRequestBuilder, RequestHead};
|
||||||
pub use self::response::ClientResponse;
|
pub use self::response::ClientResponse;
|
||||||
|
174
src/client/pipeline.rs
Normal file
174
src/client/pipeline.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use actix_net::codec::Framed;
|
||||||
|
use actix_net::service::Service;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::future::{err, ok, Either};
|
||||||
|
use futures::{Async, Future, Poll, Sink, Stream};
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
use super::error::{ConnectorError, SendRequestError};
|
||||||
|
use super::request::RequestHead;
|
||||||
|
use super::response::ClientResponse;
|
||||||
|
use super::{Connect, Connection};
|
||||||
|
use body::{BodyStream, BodyType, MessageBody};
|
||||||
|
use error::Error;
|
||||||
|
use h1;
|
||||||
|
|
||||||
|
pub fn send_request<T, Io, B>(
|
||||||
|
head: RequestHead,
|
||||||
|
body: B,
|
||||||
|
connector: &mut T,
|
||||||
|
) -> impl Future<Item = ClientResponse, Error = SendRequestError>
|
||||||
|
where
|
||||||
|
T: Service<Request = Connect, Response = Connection<Io>, Error = ConnectorError>,
|
||||||
|
B: MessageBody,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
let tp = body.tp();
|
||||||
|
|
||||||
|
connector
|
||||||
|
.call(Connect::new(head.uri.clone()))
|
||||||
|
.from_err()
|
||||||
|
.map(|io| Framed::new(io, h1::ClientCodec::default()))
|
||||||
|
.and_then(|framed| framed.send((head, tp).into()).from_err())
|
||||||
|
.and_then(move |framed| match body.tp() {
|
||||||
|
BodyType::None | BodyType::Zero => Either::A(ok(framed)),
|
||||||
|
_ => Either::B(SendBody::new(body, framed)),
|
||||||
|
}).and_then(|framed| {
|
||||||
|
framed
|
||||||
|
.into_future()
|
||||||
|
.map_err(|(e, _)| SendRequestError::from(e))
|
||||||
|
.and_then(|(item, framed)| {
|
||||||
|
if let Some(item) = item {
|
||||||
|
let mut res = item.into_item().unwrap();
|
||||||
|
match framed.get_codec().message_type() {
|
||||||
|
h1::MessageType::None => release_connection(framed),
|
||||||
|
_ => res.payload = Some(Payload::stream(framed)),
|
||||||
|
}
|
||||||
|
ok(res)
|
||||||
|
} else {
|
||||||
|
err(ConnectorError::Disconnected.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SendBody<Io, B> {
|
||||||
|
body: Option<B>,
|
||||||
|
framed: Option<Framed<Connection<Io>, h1::ClientCodec>>,
|
||||||
|
write_buf: VecDeque<h1::Message<(RequestHead, BodyType)>>,
|
||||||
|
flushed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, B> SendBody<Io, B>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
B: MessageBody,
|
||||||
|
{
|
||||||
|
fn new(body: B, framed: Framed<Connection<Io>, h1::ClientCodec>) -> Self {
|
||||||
|
SendBody {
|
||||||
|
body: Some(body),
|
||||||
|
framed: Some(framed),
|
||||||
|
write_buf: VecDeque::new(),
|
||||||
|
flushed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, B> Future for SendBody<Io, B>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
B: MessageBody,
|
||||||
|
{
|
||||||
|
type Item = Framed<Connection<Io>, h1::ClientCodec>;
|
||||||
|
type Error = SendRequestError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
let mut body_ready = true;
|
||||||
|
loop {
|
||||||
|
while body_ready
|
||||||
|
&& self.body.is_some()
|
||||||
|
&& !self.framed.as_ref().unwrap().is_write_buf_full()
|
||||||
|
{
|
||||||
|
match self.body.as_mut().unwrap().poll_next()? {
|
||||||
|
Async::Ready(None) => {
|
||||||
|
self.flushed = false;
|
||||||
|
self.framed
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.start_send(h1::Message::Chunk(None))?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Async::Ready(Some(chunk)) => {
|
||||||
|
self.flushed = false;
|
||||||
|
self.framed
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.start_send(h1::Message::Chunk(Some(chunk)))?;
|
||||||
|
}
|
||||||
|
Async::NotReady => body_ready = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.flushed {
|
||||||
|
match self.framed.as_mut().unwrap().poll_complete()? {
|
||||||
|
Async::Ready(_) => {
|
||||||
|
self.flushed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Async::NotReady => return Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.body.is_none() {
|
||||||
|
return Ok(Async::Ready(self.framed.take().unwrap()));
|
||||||
|
}
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Payload<Io> {
|
||||||
|
framed: Option<Framed<Connection<Io>, h1::ClientCodec>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io: AsyncRead + AsyncWrite + 'static> Payload<Io> {
|
||||||
|
fn stream(framed: Framed<Connection<Io>, h1::ClientCodec>) -> BodyStream {
|
||||||
|
Box::new(Payload {
|
||||||
|
framed: Some(framed),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io: AsyncRead + AsyncWrite + 'static> Stream for Payload<Io> {
|
||||||
|
type Item = Bytes;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Error> {
|
||||||
|
match self.framed.as_mut().unwrap().poll()? {
|
||||||
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
|
Async::Ready(Some(chunk)) => match chunk {
|
||||||
|
h1::Message::Chunk(Some(chunk)) => Ok(Async::Ready(Some(chunk))),
|
||||||
|
h1::Message::Chunk(None) => {
|
||||||
|
release_connection(self.framed.take().unwrap());
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
}
|
||||||
|
h1::Message::Item(_) => unreachable!(),
|
||||||
|
},
|
||||||
|
Async::Ready(None) => Ok(Async::Ready(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_connection<Io>(framed: Framed<Connection<Io>, h1::ClientCodec>)
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
let parts = framed.into_parts();
|
||||||
|
if parts.read_buf.is_empty() && parts.write_buf.is_empty() {
|
||||||
|
parts.io.release()
|
||||||
|
} else {
|
||||||
|
parts.io.close()
|
||||||
|
}
|
||||||
|
}
|
@ -327,10 +327,7 @@ enum Acquire<T> {
|
|||||||
NotAvailable,
|
NotAvailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Inner<Io>
|
pub(crate) struct Inner<Io> {
|
||||||
where
|
|
||||||
Io: AsyncRead + AsyncWrite + 'static,
|
|
||||||
{
|
|
||||||
conn_lifetime: Duration,
|
conn_lifetime: Duration,
|
||||||
conn_keep_alive: Duration,
|
conn_keep_alive: Duration,
|
||||||
disconnect_timeout: Option<Duration>,
|
disconnect_timeout: Option<Duration>,
|
||||||
@ -345,6 +342,33 @@ where
|
|||||||
task: AtomicTask,
|
task: AtomicTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Io> Inner<Io> {
|
||||||
|
fn reserve(&mut self) {
|
||||||
|
self.acquired += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&mut self) {
|
||||||
|
self.acquired -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_waiter(&mut self, key: &Key, token: usize) {
|
||||||
|
self.waiters.remove(token);
|
||||||
|
self.waiters_queue.remove(&(key.clone(), token));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_conn(&mut self, key: &Key, io: Io, created: Instant) {
|
||||||
|
self.acquired -= 1;
|
||||||
|
self.available
|
||||||
|
.entry(key.clone())
|
||||||
|
.or_insert_with(VecDeque::new)
|
||||||
|
.push_back(AvailableConnection {
|
||||||
|
io,
|
||||||
|
created,
|
||||||
|
used: Instant::now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Io> Inner<Io>
|
impl<Io> Inner<Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + 'static,
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
@ -367,11 +391,6 @@ where
|
|||||||
(rx, token)
|
(rx, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release_waiter(&mut self, key: &Key, token: usize) {
|
|
||||||
self.waiters.remove(token);
|
|
||||||
self.waiters_queue.remove(&(key.clone(), token));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acquire(&mut self, key: &Key) -> Acquire<Io> {
|
fn acquire(&mut self, key: &Key) -> Acquire<Io> {
|
||||||
// check limits
|
// check limits
|
||||||
if self.limit > 0 && self.acquired >= self.limit {
|
if self.limit > 0 && self.acquired >= self.limit {
|
||||||
@ -412,26 +431,6 @@ where
|
|||||||
Acquire::Available
|
Acquire::Available
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserve(&mut self) {
|
|
||||||
self.acquired += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release(&mut self) {
|
|
||||||
self.acquired -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release_conn(&mut self, key: &Key, io: Io, created: Instant) {
|
|
||||||
self.acquired -= 1;
|
|
||||||
self.available
|
|
||||||
.entry(key.clone())
|
|
||||||
.or_insert_with(VecDeque::new)
|
|
||||||
.push_back(AvailableConnection {
|
|
||||||
io,
|
|
||||||
created,
|
|
||||||
used: Instant::now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release_close(&mut self, io: Io) {
|
fn release_close(&mut self, io: Io) {
|
||||||
self.acquired -= 1;
|
self.acquired -= 1;
|
||||||
if let Some(timeout) = self.disconnect_timeout {
|
if let Some(timeout) = self.disconnect_timeout {
|
||||||
@ -541,10 +540,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Acquired<T: AsyncRead + AsyncWrite + 'static>(
|
pub(crate) struct Acquired<T>(Key, Option<Rc<RefCell<Inner<T>>>>);
|
||||||
Key,
|
|
||||||
Option<Rc<RefCell<Inner<T>>>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<T> Acquired<T>
|
impl<T> Acquired<T>
|
||||||
where
|
where
|
||||||
@ -567,10 +563,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Drop for Acquired<T>
|
impl<T> Drop for Acquired<T> {
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + 'static,
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(inner) = self.1.take() {
|
if let Some(inner) = self.1.take() {
|
||||||
inner.as_ref().borrow_mut().release();
|
inner.as_ref().borrow_mut().release();
|
||||||
|
@ -2,17 +2,25 @@ use std::fmt;
|
|||||||
use std::fmt::Write as FmtWrite;
|
use std::fmt::Write as FmtWrite;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use bytes::{BufMut, BytesMut};
|
use actix_net::service::Service;
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use cookie::{Cookie, CookieJar};
|
use cookie::{Cookie, CookieJar};
|
||||||
|
use futures::{Future, Stream};
|
||||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use urlcrate::Url;
|
use urlcrate::Url;
|
||||||
|
|
||||||
|
use body::{MessageBody, MessageBodyStream};
|
||||||
|
use error::Error;
|
||||||
use header::{self, Header, IntoHeaderValue};
|
use header::{self, Header, IntoHeaderValue};
|
||||||
use http::{
|
use http::{
|
||||||
uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method,
|
uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method,
|
||||||
Uri, Version,
|
Uri, Version,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::response::ClientResponse;
|
||||||
|
use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError};
|
||||||
|
|
||||||
/// An HTTP Client Request
|
/// An HTTP Client Request
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -38,29 +46,40 @@ use http::{
|
|||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct ClientRequest {
|
pub struct ClientRequest<B: MessageBody = ()> {
|
||||||
uri: Uri,
|
head: RequestHead,
|
||||||
method: Method,
|
body: B,
|
||||||
version: Version,
|
|
||||||
headers: HeaderMap,
|
|
||||||
chunked: bool,
|
|
||||||
upgrade: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ClientRequest {
|
pub struct RequestHead {
|
||||||
fn default() -> ClientRequest {
|
pub uri: Uri,
|
||||||
ClientRequest {
|
pub method: Method,
|
||||||
|
pub version: Version,
|
||||||
|
pub headers: HeaderMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RequestHead {
|
||||||
|
fn default() -> RequestHead {
|
||||||
|
RequestHead {
|
||||||
uri: Uri::default(),
|
uri: Uri::default(),
|
||||||
method: Method::default(),
|
method: Method::default(),
|
||||||
version: Version::HTTP_11,
|
version: Version::HTTP_11,
|
||||||
headers: HeaderMap::with_capacity(16),
|
headers: HeaderMap::with_capacity(16),
|
||||||
chunked: false,
|
|
||||||
upgrade: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientRequest {
|
impl ClientRequest<()> {
|
||||||
|
/// Create client request builder
|
||||||
|
pub fn build() -> ClientRequestBuilder {
|
||||||
|
ClientRequestBuilder {
|
||||||
|
head: Some(RequestHead::default()),
|
||||||
|
err: None,
|
||||||
|
cookies: None,
|
||||||
|
default_headers: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create request builder for `GET` request
|
/// Create request builder for `GET` request
|
||||||
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||||
let mut builder = ClientRequest::build();
|
let mut builder = ClientRequest::build();
|
||||||
@ -97,87 +116,90 @@ impl ClientRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientRequest {
|
impl<B> ClientRequest<B>
|
||||||
/// Create client request builder
|
where
|
||||||
pub fn build() -> ClientRequestBuilder {
|
B: MessageBody,
|
||||||
ClientRequestBuilder {
|
{
|
||||||
request: Some(ClientRequest::default()),
|
|
||||||
err: None,
|
|
||||||
cookies: None,
|
|
||||||
default_headers: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the request URI
|
/// Get the request URI
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri(&self) -> &Uri {
|
pub fn uri(&self) -> &Uri {
|
||||||
&self.uri
|
&self.head.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set client request URI
|
/// Set client request URI
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_uri(&mut self, uri: Uri) {
|
pub fn set_uri(&mut self, uri: Uri) {
|
||||||
self.uri = uri
|
self.head.uri = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the request method
|
/// Get the request method
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&self) -> &Method {
|
pub fn method(&self) -> &Method {
|
||||||
&self.method
|
&self.head.method
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP `Method` for the request
|
/// Set HTTP `Method` for the request
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_method(&mut self, method: Method) {
|
pub fn set_method(&mut self, method: Method) {
|
||||||
self.method = method
|
self.head.method = method
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get HTTP version for the request
|
/// Get HTTP version for the request
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> Version {
|
pub fn version(&self) -> Version {
|
||||||
self.version
|
self.head.version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set http `Version` for the request
|
/// Set http `Version` for the request
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_version(&mut self, version: Version) {
|
pub fn set_version(&mut self, version: Version) {
|
||||||
self.version = version
|
self.head.version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the headers from the request
|
/// Get the headers from the request
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.headers
|
&self.head.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the headers
|
/// Get a mutable reference to the headers
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.headers
|
&mut self.head.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// is chunked encoding enabled
|
/// Deconstruct ClientRequest to a RequestHead and body tuple
|
||||||
#[inline]
|
pub fn into_parts(self) -> (RequestHead, B) {
|
||||||
pub fn chunked(&self) -> bool {
|
(self.head, self.body)
|
||||||
self.chunked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// is upgrade request
|
// Send request
|
||||||
#[inline]
|
///
|
||||||
pub fn upgrade(&self) -> bool {
|
/// This method returns a future that resolves to a ClientResponse
|
||||||
self.upgrade
|
pub fn send<T, Io>(
|
||||||
|
self,
|
||||||
|
connector: &mut T,
|
||||||
|
) -> impl Future<Item = ClientResponse, Error = SendRequestError>
|
||||||
|
where
|
||||||
|
T: Service<Request = Connect, Response = Connection<Io>, Error = ConnectorError>,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
pipeline::send_request(self.head, self.body, connector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ClientRequest {
|
impl<B> fmt::Debug for ClientRequest<B>
|
||||||
|
where
|
||||||
|
B: MessageBody,
|
||||||
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"\nClientRequest {:?} {}:{}",
|
"\nClientRequest {:?} {}:{}",
|
||||||
self.version, self.method, self.uri
|
self.head.version, self.head.method, self.head.uri
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, " headers:")?;
|
writeln!(f, " headers:")?;
|
||||||
for (key, val) in self.headers.iter() {
|
for (key, val) in self.head.headers.iter() {
|
||||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -189,7 +211,7 @@ impl fmt::Debug for ClientRequest {
|
|||||||
/// This type can be used to construct an instance of `ClientRequest` through a
|
/// This type can be used to construct an instance of `ClientRequest` through a
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
pub struct ClientRequestBuilder {
|
pub struct ClientRequestBuilder {
|
||||||
request: Option<ClientRequest>,
|
head: Option<RequestHead>,
|
||||||
err: Option<HttpError>,
|
err: Option<HttpError>,
|
||||||
cookies: Option<CookieJar>,
|
cookies: Option<CookieJar>,
|
||||||
default_headers: bool,
|
default_headers: bool,
|
||||||
@ -208,7 +230,7 @@ impl ClientRequestBuilder {
|
|||||||
fn _uri(&mut self, url: &str) -> &mut Self {
|
fn _uri(&mut self, url: &str) -> &mut Self {
|
||||||
match Uri::try_from(url) {
|
match Uri::try_from(url) {
|
||||||
Ok(uri) => {
|
Ok(uri) => {
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.uri = uri;
|
parts.uri = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +242,7 @@ impl ClientRequestBuilder {
|
|||||||
/// Set HTTP method of this request.
|
/// Set HTTP method of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&mut self, method: Method) -> &mut Self {
|
pub fn method(&mut self, method: Method) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.method = method;
|
parts.method = method;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -229,7 +251,7 @@ impl ClientRequestBuilder {
|
|||||||
/// Set HTTP method of this request.
|
/// Set HTTP method of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_method(&mut self) -> &Method {
|
pub fn get_method(&mut self) -> &Method {
|
||||||
let parts = self.request.as_ref().expect("cannot reuse request builder");
|
let parts = self.head.as_ref().expect("cannot reuse request builder");
|
||||||
&parts.method
|
&parts.method
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +260,7 @@ impl ClientRequestBuilder {
|
|||||||
/// By default requests's HTTP version depends on network stream
|
/// By default requests's HTTP version depends on network stream
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&mut self, version: Version) -> &mut Self {
|
pub fn version(&mut self, version: Version) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.version = version;
|
parts.version = version;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -263,7 +285,7 @@ impl ClientRequestBuilder {
|
|||||||
/// ```
|
/// ```
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match hdr.try_into() {
|
match hdr.try_into() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(H::name(), value);
|
parts.headers.insert(H::name(), value);
|
||||||
@ -299,7 +321,7 @@ impl ClientRequestBuilder {
|
|||||||
HeaderName: HttpTryFrom<K>,
|
HeaderName: HttpTryFrom<K>,
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -319,7 +341,7 @@ impl ClientRequestBuilder {
|
|||||||
HeaderName: HttpTryFrom<K>,
|
HeaderName: HttpTryFrom<K>,
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -339,7 +361,7 @@ impl ClientRequestBuilder {
|
|||||||
HeaderName: HttpTryFrom<K>,
|
HeaderName: HttpTryFrom<K>,
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => if !parts.headers.contains_key(&key) {
|
Ok(key) => if !parts.headers.contains_key(&key) {
|
||||||
match value.try_into() {
|
match value.try_into() {
|
||||||
@ -357,11 +379,12 @@ impl ClientRequestBuilder {
|
|||||||
|
|
||||||
/// Enable connection upgrade
|
/// Enable connection upgrade
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade(&mut self) -> &mut Self {
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
where
|
||||||
parts.upgrade = true;
|
V: IntoHeaderValue,
|
||||||
}
|
{
|
||||||
self
|
self.set_header(header::UPGRADE, value)
|
||||||
|
.set_header(header::CONNECTION, "upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request's content type
|
/// Set request's content type
|
||||||
@ -370,7 +393,7 @@ impl ClientRequestBuilder {
|
|||||||
where
|
where
|
||||||
HeaderValue: HttpTryFrom<V>,
|
HeaderValue: HttpTryFrom<V>,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderValue::try_from(value) {
|
match HeaderValue::try_from(value) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||||
@ -454,14 +477,17 @@ impl ClientRequestBuilder {
|
|||||||
/// Set a body and generate `ClientRequest`.
|
/// Set a body and generate `ClientRequest`.
|
||||||
///
|
///
|
||||||
/// `ClientRequestBuilder` can not be used after this call.
|
/// `ClientRequestBuilder` can not be used after this call.
|
||||||
pub fn finish(&mut self) -> Result<ClientRequest, HttpError> {
|
pub fn body<B: MessageBody>(
|
||||||
|
&mut self,
|
||||||
|
body: B,
|
||||||
|
) -> Result<ClientRequest<B>, HttpError> {
|
||||||
if let Some(e) = self.err.take() {
|
if let Some(e) = self.err.take() {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.default_headers {
|
if self.default_headers {
|
||||||
// enable br only for https
|
// enable br only for https
|
||||||
let https = if let Some(parts) = parts(&mut self.request, &self.err) {
|
let https = if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts
|
parts
|
||||||
.uri
|
.uri
|
||||||
.scheme_part()
|
.scheme_part()
|
||||||
@ -478,7 +504,7 @@ impl ClientRequestBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set request host header
|
// set request host header
|
||||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
if let Some(host) = parts.uri.host() {
|
if let Some(host) = parts.uri.host() {
|
||||||
if !parts.headers.contains_key(header::HOST) {
|
if !parts.headers.contains_key(header::HOST) {
|
||||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||||
@ -505,7 +531,7 @@ impl ClientRequestBuilder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut request = self.request.take().expect("cannot reuse request builder");
|
let mut head = self.head.take().expect("cannot reuse request builder");
|
||||||
|
|
||||||
// set cookies
|
// set cookies
|
||||||
if let Some(ref mut jar) = self.cookies {
|
if let Some(ref mut jar) = self.cookies {
|
||||||
@ -515,18 +541,38 @@ impl ClientRequestBuilder {
|
|||||||
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
|
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
|
||||||
let _ = write!(&mut cookie, "; {}={}", name, value);
|
let _ = write!(&mut cookie, "; {}={}", name, value);
|
||||||
}
|
}
|
||||||
request.headers.insert(
|
head.headers.insert(
|
||||||
header::COOKIE,
|
header::COOKIE,
|
||||||
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
|
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(request)
|
Ok(ClientRequest { head, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set an streaming body and generate `ClientRequest`.
|
||||||
|
///
|
||||||
|
/// `ClientRequestBuilder` can not be used after this call.
|
||||||
|
pub fn stream<S>(
|
||||||
|
&mut self,
|
||||||
|
stream: S,
|
||||||
|
) -> Result<ClientRequest<impl MessageBody>, HttpError>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Bytes, Error = Error>,
|
||||||
|
{
|
||||||
|
self.body(MessageBodyStream::new(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set an empty body and generate `ClientRequest`.
|
||||||
|
///
|
||||||
|
/// `ClientRequestBuilder` can not be used after this call.
|
||||||
|
pub fn finish(&mut self) -> Result<ClientRequest<()>, HttpError> {
|
||||||
|
self.body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method construct new `ClientRequestBuilder`
|
/// This method construct new `ClientRequestBuilder`
|
||||||
pub fn take(&mut self) -> ClientRequestBuilder {
|
pub fn take(&mut self) -> ClientRequestBuilder {
|
||||||
ClientRequestBuilder {
|
ClientRequestBuilder {
|
||||||
request: self.request.take(),
|
head: self.head.take(),
|
||||||
err: self.err.take(),
|
err: self.err.take(),
|
||||||
cookies: self.cookies.take(),
|
cookies: self.cookies.take(),
|
||||||
default_headers: self.default_headers,
|
default_headers: self.default_headers,
|
||||||
@ -536,9 +582,9 @@ impl ClientRequestBuilder {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parts<'a>(
|
fn parts<'a>(
|
||||||
parts: &'a mut Option<ClientRequest>,
|
parts: &'a mut Option<RequestHead>,
|
||||||
err: &Option<HttpError>,
|
err: &Option<HttpError>,
|
||||||
) -> Option<&'a mut ClientRequest> {
|
) -> Option<&'a mut RequestHead> {
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -547,7 +593,7 @@ fn parts<'a>(
|
|||||||
|
|
||||||
impl fmt::Debug for ClientRequestBuilder {
|
impl fmt::Debug for ClientRequestBuilder {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if let Some(ref parts) = self.request {
|
if let Some(ref parts) = self.head {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"\nClientRequestBuilder {:?} {}:{}",
|
"\nClientRequestBuilder {:?} {}:{}",
|
||||||
|
@ -2,35 +2,38 @@ use std::cell::{Cell, Ref, RefCell, RefMut};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::{Async, Poll, Stream};
|
||||||
use http::{HeaderMap, Method, StatusCode, Version};
|
use http::{HeaderMap, Method, StatusCode, Version};
|
||||||
|
|
||||||
|
use body::BodyStream;
|
||||||
|
use error::Error;
|
||||||
use extensions::Extensions;
|
use extensions::Extensions;
|
||||||
use httpmessage::HttpMessage;
|
|
||||||
use payload::Payload;
|
|
||||||
use request::{Message, MessageFlags, MessagePool};
|
use request::{Message, MessageFlags, MessagePool};
|
||||||
use uri::Url;
|
use uri::Url;
|
||||||
|
|
||||||
/// Client Response
|
/// Client Response
|
||||||
pub struct ClientResponse {
|
pub struct ClientResponse {
|
||||||
pub(crate) inner: Rc<Message>,
|
pub(crate) inner: Rc<Message>,
|
||||||
|
pub(crate) payload: Option<BodyStream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpMessage for ClientResponse {
|
// impl HttpMessage for ClientResponse {
|
||||||
type Stream = Payload;
|
// type Stream = Payload;
|
||||||
|
|
||||||
fn headers(&self) -> &HeaderMap {
|
// fn headers(&self) -> &HeaderMap {
|
||||||
&self.inner.headers
|
// &self.inner.headers
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[inline]
|
// #[inline]
|
||||||
fn payload(&self) -> Payload {
|
// fn payload(&self) -> Payload {
|
||||||
if let Some(payload) = self.inner.payload.borrow_mut().take() {
|
// if let Some(payload) = self.inner.payload.borrow_mut().take() {
|
||||||
payload
|
// payload
|
||||||
} else {
|
// } else {
|
||||||
Payload::empty()
|
// Payload::empty()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl ClientResponse {
|
impl ClientResponse {
|
||||||
/// Create new Request instance
|
/// Create new Request instance
|
||||||
@ -52,6 +55,7 @@ impl ClientResponse {
|
|||||||
payload: RefCell::new(None),
|
payload: RefCell::new(None),
|
||||||
extensions: RefCell::new(Extensions::new()),
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}),
|
}),
|
||||||
|
payload: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +112,19 @@ impl ClientResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Stream for ClientResponse {
|
||||||
|
type Item = Bytes;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Error> {
|
||||||
|
if let Some(ref mut payload) = self.payload {
|
||||||
|
payload.poll()
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for ClientResponse {
|
impl Drop for ClientResponse {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if Rc::strong_count(&self.inner) == 1 {
|
if Rc::strong_count(&self.inner) == 1 {
|
||||||
|
@ -7,12 +7,14 @@ use tokio_codec::{Decoder, Encoder};
|
|||||||
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder};
|
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder};
|
||||||
use super::encoder::{RequestEncoder, ResponseLength};
|
use super::encoder::{RequestEncoder, ResponseLength};
|
||||||
use super::{Message, MessageType};
|
use super::{Message, MessageType};
|
||||||
use body::{Binary, Body};
|
use body::{Binary, Body, BodyType};
|
||||||
use client::{ClientRequest, ClientResponse};
|
use client::{ClientResponse, RequestHead};
|
||||||
use config::ServiceConfig;
|
use config::ServiceConfig;
|
||||||
use error::ParseError;
|
use error::ParseError;
|
||||||
use helpers;
|
use helpers;
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
use http::header::{
|
||||||
|
HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
|
||||||
|
};
|
||||||
use http::{Method, Version};
|
use http::{Method, Version};
|
||||||
use request::MessagePool;
|
use request::MessagePool;
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ bitflags! {
|
|||||||
const UPGRADE = 0b0000_0010;
|
const UPGRADE = 0b0000_0010;
|
||||||
const KEEPALIVE = 0b0000_0100;
|
const KEEPALIVE = 0b0000_0100;
|
||||||
const KEEPALIVE_ENABLED = 0b0000_1000;
|
const KEEPALIVE_ENABLED = 0b0000_1000;
|
||||||
const UNHANDLED = 0b0001_0000;
|
const STREAM = 0b0001_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +88,8 @@ impl ClientCodec {
|
|||||||
|
|
||||||
/// Check last request's message type
|
/// Check last request's message type
|
||||||
pub fn message_type(&self) -> MessageType {
|
pub fn message_type(&self) -> MessageType {
|
||||||
if self.flags.contains(Flags::UNHANDLED) {
|
if self.flags.contains(Flags::STREAM) {
|
||||||
MessageType::Unhandled
|
MessageType::Stream
|
||||||
} else if self.payload.is_none() {
|
} else if self.payload.is_none() {
|
||||||
MessageType::None
|
MessageType::None
|
||||||
} else {
|
} else {
|
||||||
@ -96,38 +98,31 @@ impl ClientCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// prepare transfer encoding
|
/// prepare transfer encoding
|
||||||
pub fn prepare_te(&mut self, res: &mut ClientRequest) {
|
pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) {
|
||||||
self.te
|
self.te
|
||||||
.update(res, self.flags.contains(Flags::HEAD), self.version);
|
.update(head, self.flags.contains(Flags::HEAD), self.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_response(
|
fn encode_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
msg: ClientRequest,
|
msg: RequestHead,
|
||||||
|
btype: BodyType,
|
||||||
buffer: &mut BytesMut,
|
buffer: &mut BytesMut,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
// Connection upgrade
|
|
||||||
if msg.upgrade() {
|
|
||||||
self.flags.insert(Flags::UPGRADE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// render message
|
// render message
|
||||||
{
|
{
|
||||||
// status line
|
// status line
|
||||||
writeln!(
|
writeln!(
|
||||||
Writer(buffer),
|
Writer(buffer),
|
||||||
"{} {} {:?}\r",
|
"{} {} {:?}\r",
|
||||||
msg.method(),
|
msg.method,
|
||||||
msg.uri()
|
msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
|
||||||
.path_and_query()
|
msg.version
|
||||||
.map(|u| u.as_str())
|
|
||||||
.unwrap_or("/"),
|
|
||||||
msg.version()
|
|
||||||
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
// write headers
|
// write headers
|
||||||
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
|
buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE);
|
||||||
for (key, value) in msg.headers() {
|
for (key, value) in &msg.headers {
|
||||||
let v = value.as_ref();
|
let v = value.as_ref();
|
||||||
let k = key.as_str().as_bytes();
|
let k = key.as_str().as_bytes();
|
||||||
buffer.reserve(k.len() + v.len() + 4);
|
buffer.reserve(k.len() + v.len() + 4);
|
||||||
@ -135,10 +130,15 @@ impl ClientCodec {
|
|||||||
buffer.put_slice(b": ");
|
buffer.put_slice(b": ");
|
||||||
buffer.put_slice(v);
|
buffer.put_slice(v);
|
||||||
buffer.put_slice(b"\r\n");
|
buffer.put_slice(b"\r\n");
|
||||||
|
|
||||||
|
// Connection upgrade
|
||||||
|
if key == UPGRADE {
|
||||||
|
self.flags.insert(Flags::UPGRADE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set date header
|
// set date header
|
||||||
if !msg.headers().contains_key(DATE) {
|
if !msg.headers.contains_key(DATE) {
|
||||||
self.config.set_date(buffer);
|
self.config.set_date(buffer);
|
||||||
} else {
|
} else {
|
||||||
buffer.extend_from_slice(b"\r\n");
|
buffer.extend_from_slice(b"\r\n");
|
||||||
@ -160,8 +160,6 @@ impl Decoder for ClientCodec {
|
|||||||
Some(PayloadItem::Eof) => Some(Message::Chunk(None)),
|
Some(PayloadItem::Eof) => Some(Message::Chunk(None)),
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
} else if self.flags.contains(Flags::UNHANDLED) {
|
|
||||||
Ok(None)
|
|
||||||
} else if let Some((req, payload)) = self.decoder.decode(src)? {
|
} else if let Some((req, payload)) = self.decoder.decode(src)? {
|
||||||
self.flags
|
self.flags
|
||||||
.set(Flags::HEAD, req.inner.method == Method::HEAD);
|
.set(Flags::HEAD, req.inner.method == Method::HEAD);
|
||||||
@ -172,9 +170,9 @@ impl Decoder for ClientCodec {
|
|||||||
match payload {
|
match payload {
|
||||||
PayloadType::None => self.payload = None,
|
PayloadType::None => self.payload = None,
|
||||||
PayloadType::Payload(pl) => self.payload = Some(pl),
|
PayloadType::Payload(pl) => self.payload = Some(pl),
|
||||||
PayloadType::Unhandled => {
|
PayloadType::Stream(pl) => {
|
||||||
self.payload = None;
|
self.payload = Some(pl);
|
||||||
self.flags.insert(Flags::UNHANDLED);
|
self.flags.insert(Flags::STREAM);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Some(Message::Item(req)))
|
Ok(Some(Message::Item(req)))
|
||||||
@ -185,7 +183,7 @@ impl Decoder for ClientCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder for ClientCodec {
|
impl Encoder for ClientCodec {
|
||||||
type Item = Message<ClientRequest>;
|
type Item = Message<(RequestHead, BodyType)>;
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
|
|
||||||
fn encode(
|
fn encode(
|
||||||
@ -194,8 +192,8 @@ impl Encoder for ClientCodec {
|
|||||||
dst: &mut BytesMut,
|
dst: &mut BytesMut,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
match item {
|
match item {
|
||||||
Message::Item(res) => {
|
Message::Item((msg, btype)) => {
|
||||||
self.encode_response(res, dst)?;
|
self.encode_response(msg, btype, dst)?;
|
||||||
}
|
}
|
||||||
Message::Chunk(Some(bytes)) => {
|
Message::Chunk(Some(bytes)) => {
|
||||||
self.te.encode(bytes.as_ref(), dst)?;
|
self.te.encode(bytes.as_ref(), dst)?;
|
||||||
|
@ -23,7 +23,7 @@ bitflags! {
|
|||||||
const UPGRADE = 0b0000_0010;
|
const UPGRADE = 0b0000_0010;
|
||||||
const KEEPALIVE = 0b0000_0100;
|
const KEEPALIVE = 0b0000_0100;
|
||||||
const KEEPALIVE_ENABLED = 0b0000_1000;
|
const KEEPALIVE_ENABLED = 0b0000_1000;
|
||||||
const UNHANDLED = 0b0001_0000;
|
const STREAM = 0b0001_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,8 +93,8 @@ impl Codec {
|
|||||||
|
|
||||||
/// Check last request's message type
|
/// Check last request's message type
|
||||||
pub fn message_type(&self) -> MessageType {
|
pub fn message_type(&self) -> MessageType {
|
||||||
if self.flags.contains(Flags::UNHANDLED) {
|
if self.flags.contains(Flags::STREAM) {
|
||||||
MessageType::Unhandled
|
MessageType::Stream
|
||||||
} else if self.payload.is_none() {
|
} else if self.payload.is_none() {
|
||||||
MessageType::None
|
MessageType::None
|
||||||
} else {
|
} else {
|
||||||
@ -259,8 +259,6 @@ impl Decoder for Codec {
|
|||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
} else if self.flags.contains(Flags::UNHANDLED) {
|
|
||||||
Ok(None)
|
|
||||||
} else if let Some((req, payload)) = self.decoder.decode(src)? {
|
} else if let Some((req, payload)) = self.decoder.decode(src)? {
|
||||||
self.flags
|
self.flags
|
||||||
.set(Flags::HEAD, req.inner.method == Method::HEAD);
|
.set(Flags::HEAD, req.inner.method == Method::HEAD);
|
||||||
@ -271,9 +269,9 @@ impl Decoder for Codec {
|
|||||||
match payload {
|
match payload {
|
||||||
PayloadType::None => self.payload = None,
|
PayloadType::None => self.payload = None,
|
||||||
PayloadType::Payload(pl) => self.payload = Some(pl),
|
PayloadType::Payload(pl) => self.payload = Some(pl),
|
||||||
PayloadType::Unhandled => {
|
PayloadType::Stream(pl) => {
|
||||||
self.payload = None;
|
self.payload = Some(pl);
|
||||||
self.flags.insert(Flags::UNHANDLED);
|
self.flags.insert(Flags::STREAM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(Message::Item(req)))
|
Ok(Some(Message::Item(req)))
|
||||||
|
@ -25,7 +25,7 @@ pub struct ResponseDecoder(&'static MessagePool);
|
|||||||
pub enum PayloadType {
|
pub enum PayloadType {
|
||||||
None,
|
None,
|
||||||
Payload(PayloadDecoder),
|
Payload(PayloadDecoder),
|
||||||
Unhandled,
|
Stream(PayloadDecoder),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestDecoder {
|
impl RequestDecoder {
|
||||||
@ -174,7 +174,7 @@ impl Decoder for RequestDecoder {
|
|||||||
PayloadType::Payload(PayloadDecoder::length(len))
|
PayloadType::Payload(PayloadDecoder::length(len))
|
||||||
} else if has_upgrade || msg.inner.method == Method::CONNECT {
|
} else if has_upgrade || msg.inner.method == Method::CONNECT {
|
||||||
// upgrade(websocket) or connect
|
// upgrade(websocket) or connect
|
||||||
PayloadType::Unhandled
|
PayloadType::Stream(PayloadDecoder::eof())
|
||||||
} else if src.len() >= MAX_BUFFER_SIZE {
|
} else if src.len() >= MAX_BUFFER_SIZE {
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
return Err(ParseError::TooLarge);
|
return Err(ParseError::TooLarge);
|
||||||
@ -321,7 +321,7 @@ impl Decoder for ResponseDecoder {
|
|||||||
|| msg.inner.method == Method::CONNECT
|
|| msg.inner.method == Method::CONNECT
|
||||||
{
|
{
|
||||||
// switching protocol or connect
|
// switching protocol or connect
|
||||||
PayloadType::Unhandled
|
PayloadType::Stream(PayloadDecoder::eof())
|
||||||
} else if src.len() >= MAX_BUFFER_SIZE {
|
} else if src.len() >= MAX_BUFFER_SIZE {
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
return Err(ParseError::TooLarge);
|
return Err(ParseError::TooLarge);
|
||||||
@ -667,7 +667,7 @@ mod tests {
|
|||||||
|
|
||||||
fn is_unhandled(&self) -> bool {
|
fn is_unhandled(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
PayloadType::Unhandled => true,
|
PayloadType::Stream(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,7 +344,7 @@ where
|
|||||||
*req.inner.payload.borrow_mut() = Some(pl);
|
*req.inner.payload.borrow_mut() = Some(pl);
|
||||||
self.payload = Some(ps);
|
self.payload = Some(ps);
|
||||||
}
|
}
|
||||||
MessageType::Unhandled => {
|
MessageType::Stream => {
|
||||||
self.unhandled = Some(req);
|
self.unhandled = Some(req);
|
||||||
return Ok(updated);
|
return Ok(updated);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH};
|
|||||||
use http::{StatusCode, Version};
|
use http::{StatusCode, Version};
|
||||||
|
|
||||||
use body::{Binary, Body};
|
use body::{Binary, Body};
|
||||||
use client::ClientRequest;
|
use client::RequestHead;
|
||||||
use header::ContentEncoding;
|
use header::ContentEncoding;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
@ -196,7 +196,7 @@ impl RequestEncoder {
|
|||||||
self.te.encode_eof(buf)
|
self.te.encode_eof(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, resp: &mut ClientRequest, head: bool, version: Version) {
|
pub fn update(&mut self, resp: &mut RequestHead, head: bool, version: Version) {
|
||||||
self.head = head;
|
self.head = head;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,15 @@ pub enum Message<T> {
|
|||||||
Chunk(Option<Bytes>),
|
Chunk(Option<Bytes>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Message<T> {
|
||||||
|
pub fn into_item(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Message::Item(item) => Some(item),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Message<T> {
|
impl<T> From<T> for Message<T> {
|
||||||
fn from(item: T) -> Self {
|
fn from(item: T) -> Self {
|
||||||
Message::Item(item)
|
Message::Item(item)
|
||||||
@ -44,7 +53,7 @@ impl<T> From<T> for Message<T> {
|
|||||||
pub enum MessageType {
|
pub enum MessageType {
|
||||||
None,
|
None,
|
||||||
Payload,
|
Payload,
|
||||||
Unhandled,
|
Stream,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -240,7 +240,10 @@ impl MessagePool {
|
|||||||
if let Some(r) = Rc::get_mut(&mut msg) {
|
if let Some(r) = Rc::get_mut(&mut msg) {
|
||||||
r.reset();
|
r.reset();
|
||||||
}
|
}
|
||||||
return ClientResponse { inner: msg };
|
return ClientResponse {
|
||||||
|
inner: msg,
|
||||||
|
payload: None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ClientResponse::with_pool(pool)
|
ClientResponse::with_pool(pool)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use rand;
|
|||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
use body::BodyType;
|
||||||
use client::ClientResponse;
|
use client::ClientResponse;
|
||||||
use h1;
|
use h1;
|
||||||
use ws::Codec;
|
use ws::Codec;
|
||||||
@ -89,9 +90,7 @@ where
|
|||||||
req.request.set_header(header::ORIGIN, origin);
|
req.request.set_header(header::ORIGIN, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.request.upgrade();
|
req.request.upgrade("websocket");
|
||||||
req.request.set_header(header::UPGRADE, "websocket");
|
|
||||||
req.request.set_header(header::CONNECTION, "upgrade");
|
|
||||||
req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
|
req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
|
||||||
|
|
||||||
if let Some(protocols) = req.protocols.take() {
|
if let Some(protocols) = req.protocols.take() {
|
||||||
@ -142,7 +141,7 @@ where
|
|||||||
// h1 protocol
|
// h1 protocol
|
||||||
let framed = Framed::new(io, h1::ClientCodec::default());
|
let framed = Framed::new(io, h1::ClientCodec::default());
|
||||||
framed
|
framed
|
||||||
.send(request.into())
|
.send((request.into_parts().0, BodyType::None).into())
|
||||||
.map_err(ClientError::from)
|
.map_err(ClientError::from)
|
||||||
.and_then(|framed| {
|
.and_then(|framed| {
|
||||||
framed
|
framed
|
||||||
|
147
tests/test_client.rs
Normal file
147
tests/test_client.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_http;
|
||||||
|
extern crate actix_net;
|
||||||
|
extern crate bytes;
|
||||||
|
extern crate futures;
|
||||||
|
|
||||||
|
use std::{thread, time};
|
||||||
|
|
||||||
|
use actix::System;
|
||||||
|
use actix_net::server::Server;
|
||||||
|
use actix_net::service::NewServiceExt;
|
||||||
|
use futures::future::{self, lazy, ok};
|
||||||
|
|
||||||
|
use actix_http::{client, h1, test, Request, Response};
|
||||||
|
|
||||||
|
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_h1_v2() {
|
||||||
|
let addr = test::TestServer::unused_addr();
|
||||||
|
thread::spawn(move || {
|
||||||
|
Server::new()
|
||||||
|
.bind("test", addr, move || {
|
||||||
|
h1::H1Service::build()
|
||||||
|
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
|
||||||
|
.map(|_| ())
|
||||||
|
}).unwrap()
|
||||||
|
.run();
|
||||||
|
});
|
||||||
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
let mut sys = System::new("test");
|
||||||
|
let mut connector = sys
|
||||||
|
.block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service())))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = client::ClientRequest::get(format!("http://{}/", addr))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = sys.block_on(req.send(&mut connector)).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = client::ClientRequest::get(format!("http://{}/", addr))
|
||||||
|
.header("x-test", "111")
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
let repr = format!("{:?}", request);
|
||||||
|
assert!(repr.contains("ClientRequest"));
|
||||||
|
assert!(repr.contains("x-test"));
|
||||||
|
|
||||||
|
let response = sys.block_on(request.send(&mut connector)).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
// read response
|
||||||
|
// let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
|
||||||
|
let request = client::ClientRequest::post(format!("http://{}/", addr))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
let response = sys.block_on(request.send(&mut connector)).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
// read response
|
||||||
|
// let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_connection_close() {
|
||||||
|
let addr = test::TestServer::unused_addr();
|
||||||
|
thread::spawn(move || {
|
||||||
|
Server::new()
|
||||||
|
.bind("test", addr, move || {
|
||||||
|
h1::H1Service::build()
|
||||||
|
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
|
||||||
|
.map(|_| ())
|
||||||
|
}).unwrap()
|
||||||
|
.run();
|
||||||
|
});
|
||||||
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
let mut sys = System::new("test");
|
||||||
|
let mut connector = sys
|
||||||
|
.block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service())))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let request = client::ClientRequest::get(format!("http://{}/", addr))
|
||||||
|
.header("Connection", "close")
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
let response = sys.block_on(request.send(&mut connector)).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_query_parameter() {
|
||||||
|
let addr = test::TestServer::unused_addr();
|
||||||
|
thread::spawn(move || {
|
||||||
|
Server::new()
|
||||||
|
.bind("test", addr, move || {
|
||||||
|
h1::H1Service::build()
|
||||||
|
.finish(|req: Request| {
|
||||||
|
if req.uri().query().unwrap().contains("qp=") {
|
||||||
|
ok::<_, ()>(Response::Ok().finish())
|
||||||
|
} else {
|
||||||
|
ok::<_, ()>(Response::BadRequest().finish())
|
||||||
|
}
|
||||||
|
}).map(|_| ())
|
||||||
|
}).unwrap()
|
||||||
|
.run();
|
||||||
|
});
|
||||||
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
let mut sys = System::new("test");
|
||||||
|
let mut connector = sys
|
||||||
|
.block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service())))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let request = client::ClientRequest::get(format!("http://{}/?qp=5", addr))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = sys.block_on(request.send(&mut connector)).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user