1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

refactor http client

This commit is contained in:
Nikolay Kim 2018-02-19 03:11:11 -08:00
parent edd114f6e4
commit cb70d5ec3d
9 changed files with 483 additions and 256 deletions

View File

@ -2,10 +2,12 @@ mod connector;
mod parser;
mod request;
mod response;
mod pipeline;
mod writer;
pub(crate) use self::writer::HttpClientWriter;
pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::{ClientResponse, JsonResponse};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub use self::response::{ClientResponse, JsonResponse, UrlEncoded};
pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError};
pub(crate) use self::writer::HttpClientWriter;
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};

View File

@ -2,15 +2,13 @@ use std::mem;
use httparse;
use http::{Version, HttpTryFrom, HeaderMap, StatusCode};
use http::header::{self, HeaderName, HeaderValue};
use bytes::BytesMut;
use bytes::{Bytes, BytesMut};
use futures::{Poll, Async};
use error::{ParseError, PayloadError};
use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE};
use server::{utils, IoStream};
use server::h1::{Decoder, chunked};
use server::encoding::PayloadType;
use super::ClientResponse;
use super::response::ClientMessage;
@ -20,18 +18,7 @@ const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
payload: Option<PayloadInfo>,
}
enum Decoding {
Paused,
Ready,
NotReady,
}
struct PayloadInfo {
tx: PayloadType,
decoder: Decoder,
decoder: Option<Decoder>,
}
#[derive(Debug)]
@ -47,31 +34,6 @@ impl HttpResponseParser {
-> Poll<ClientResponse, HttpResponseParserError>
where T: IoStream
{
// read payload
if self.payload.is_some() {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
if let Some(ref mut payload) = self.payload {
payload.tx.set_error(PayloadError::Incomplete);
}
// http channel should not deal with payload errors
return Err(HttpResponseParserError::Payload)
},
Err(err) => {
if let Some(ref mut payload) = self.payload {
payload.tx.set_error(err.into());
}
// http channel should not deal with payload errors
return Err(HttpResponseParserError::Payload)
}
_ => (),
}
match self.decode(buf)? {
Decoding::Ready => self.payload = None,
Decoding::Paused | Decoding::NotReady => return Ok(Async::NotReady),
}
}
// if buf is empty parse_message will always return NotReady, let's avoid that
let read = if buf.is_empty() {
match utils::read_from_io(io, buf) {
@ -93,32 +55,19 @@ impl HttpResponseParser {
loop {
match HttpResponseParser::parse_message(buf).map_err(HttpResponseParserError::Error)? {
Async::Ready((msg, decoder)) => {
// process payload
if let Some(payload) = decoder {
self.payload = Some(payload);
match self.decode(buf)? {
Decoding::Paused | Decoding::NotReady => (),
Decoding::Ready => self.payload = None,
}
}
self.decoder = decoder;
return Ok(Async::Ready(msg));
},
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
if read {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
debug!("Ignored premature client disconnection");
return Err(HttpResponseParserError::Disconnect);
},
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) =>
return Ok(Async::NotReady),
Err(err) =>
return Err(HttpResponseParserError::Error(err.into()))
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
}
} else {
return Ok(Async::NotReady)
@ -128,35 +77,24 @@ impl HttpResponseParser {
}
}
fn decode(&mut self, buf: &mut BytesMut) -> Result<Decoding, HttpResponseParserError> {
if let Some(ref mut payload) = self.payload {
if payload.tx.capacity() > DEFAULT_BUFFER_SIZE {
return Ok(Decoding::Paused)
}
loop {
match payload.decoder.decode(buf) {
Ok(Async::Ready(Some(bytes))) => {
payload.tx.feed_data(bytes)
},
Ok(Async::Ready(None)) => {
payload.tx.feed_eof();
return Ok(Decoding::Ready)
},
Ok(Async::NotReady) => return Ok(Decoding::NotReady),
Err(err) => {
payload.tx.set_error(err.into());
return Err(HttpResponseParserError::Payload)
}
}
pub fn parse_payload<T>(&mut self, io: &mut T, buf: &mut BytesMut)
-> Poll<Option<Bytes>, PayloadError>
where T: IoStream
{
if let Some(ref mut decoder) = self.decoder {
// read payload
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(PayloadError::Incomplete),
Err(err) => return Err(err.into()),
_ => (),
}
decoder.decode(buf).map_err(|e| e.into())
} else {
return Ok(Decoding::Ready)
Ok(Async::Ready(None))
}
}
fn parse_message(buf: &mut BytesMut)
-> Poll<(ClientResponse, Option<PayloadInfo>), ParseError>
{
fn parse_message(buf: &mut BytesMut) -> Poll<(ClientResponse, Option<Decoder>), ParseError> {
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
@ -181,7 +119,6 @@ impl HttpResponseParser {
}
};
let slice = buf.split_to(len).freeze();
// convert headers
@ -221,22 +158,19 @@ impl HttpResponseParser {
};
if let Some(decoder) = decoder {
let (psender, payload) = Payload::new(false);
let info = PayloadInfo {
tx: PayloadType::new(&hdrs, psender),
decoder: decoder,
};
//let info = PayloadInfo {
//tx: PayloadType::new(&hdrs, psender),
// decoder: decoder,
//};
Ok(Async::Ready(
(ClientResponse::new(
ClientMessage{
status: status, version: version,
headers: hdrs, cookies: None, payload: Some(payload)}), Some(info))))
ClientMessage{status: status, version: version,
headers: hdrs, cookies: None}), Some(decoder))))
} else {
Ok(Async::Ready(
(ClientResponse::new(
ClientMessage{
status: status, version: version,
headers: hdrs, cookies: None, payload: None}), None)))
ClientMessage{status: status, version: version,
headers: hdrs, cookies: None}), None)))
}
}
}

126
src/client/pipeline.rs Normal file
View File

@ -0,0 +1,126 @@
use std::{io, mem};
use bytes::{Bytes, BytesMut};
use futures::{Async, Future, Poll};
use actix::prelude::*;
use error::PayloadError;
use server::shared::SharedBytes;
use super::{ClientRequest, ClientResponse};
use super::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::HttpClientWriter;
use super::{HttpResponseParser, HttpResponseParserError};
pub enum SendRequestError {
Connector(ClientConnectorError),
ParseError(HttpResponseParserError),
Io(io::Error),
}
impl From<io::Error> for SendRequestError {
fn from(err: io::Error) -> SendRequestError {
SendRequestError::Io(err)
}
}
enum State {
New,
Connect(actix::dev::Request<Unsync, ClientConnector, Connect>),
Send(Box<Pipeline>),
None,
}
/// `SendRequest` is a `Future` which represents asynchronous request sending process.
#[must_use = "SendRequest do nothing unless polled"]
pub struct SendRequest {
req: ClientRequest,
state: State,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest{
req: req,
state: State::New,
}
}
}
impl Future for SendRequest {
type Item = ClientResponse;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let state = mem::replace(&mut self.state, State::None);
match state {
State::New =>
self.state = State::Connect(
ClientConnector::from_registry().send(Connect(self.req.uri().clone()))),
State::Connect(mut conn) => match conn.poll() {
Ok(Async::NotReady) => {
self.state = State::Connect(conn);
return Ok(Async::NotReady);
},
Ok(Async::Ready(result)) => match result {
Ok(stream) => {
let mut pl = Box::new(Pipeline {
conn: stream,
writer: HttpClientWriter::new(SharedBytes::default()),
parser: HttpResponseParser::default(),
parser_buf: BytesMut::new(),
});
pl.writer.start(&mut self.req)?;
self.state = State::Send(pl);
},
Err(err) => return Err(SendRequestError::Connector(err)),
},
Err(_) =>
return Err(SendRequestError::Connector(ClientConnectorError::Disconnected))
},
State::Send(mut pl) => {
pl.poll_write()?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
resp.set_pipeline(pl);
return Ok(Async::Ready(resp))
},
Ok(Async::NotReady) => {
self.state = State::Send(pl);
return Ok(Async::NotReady)
},
Err(err) => return Err(SendRequestError::ParseError(err))
}
}
State::None => unreachable!(),
}
}
}
}
pub(crate) struct Pipeline {
conn: Connection,
writer: HttpClientWriter,
parser: HttpResponseParser,
parser_buf: BytesMut,
}
impl Pipeline {
#[inline]
pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
self.parser.parse(&mut self.conn, &mut self.parser_buf)
}
#[inline]
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.parser.parse_payload(&mut self.conn, &mut self.parser_buf)
}
#[inline]
pub fn poll_write(&mut self) -> Poll<(), io::Error> {
self.writer.poll_completed(&mut self.conn, false)
}
}

View File

@ -11,6 +11,7 @@ use serde::Serialize;
use body::Body;
use error::Error;
use headers::ContentEncoding;
use super::pipeline::SendRequest;
/// An HTTP Client Request
pub struct ClientRequest {
@ -19,7 +20,8 @@ pub struct ClientRequest {
version: Version,
headers: HeaderMap,
body: Body,
chunked: Option<bool>,
chunked: bool,
upgrade: bool,
encoding: ContentEncoding,
}
@ -32,7 +34,8 @@ impl Default for ClientRequest {
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
body: Body::Empty,
chunked: None,
chunked: false,
upgrade: false,
encoding: ContentEncoding::Auto,
}
}
@ -135,6 +138,24 @@ impl ClientRequest {
&mut self.headers
}
/// is chunked encoding enabled
#[inline]
pub fn chunked(&self) -> bool {
self.chunked
}
/// is upgrade request
#[inline]
pub fn upgrade(&self) -> bool {
self.upgrade
}
/// Content encoding
#[inline]
pub fn content_encoding(&self) -> ContentEncoding {
self.encoding
}
/// Get body os this response
#[inline]
pub fn body(&self) -> &Body {
@ -146,9 +167,14 @@ impl ClientRequest {
self.body = body.into();
}
/// Set a body and return previous body value
pub fn replace_body<B: Into<Body>>(&mut self, body: B) -> Body {
mem::replace(&mut self.body, body.into())
/// Extract body, replace it with Empty
pub(crate) fn replace_body(&mut self, body: Body) -> Body {
mem::replace(&mut self.body, body)
}
/// Send request
pub fn send(self) -> SendRequest {
SendRequest::new(self)
}
}
@ -288,7 +314,16 @@ impl ClientRequestBuilder {
#[inline]
pub fn chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.chunked = Some(true);
parts.chunked = true;
}
self
}
/// Enable connection upgrade
#[inline]
pub fn upgrade(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.upgrade = true;
}
self
}

View File

@ -1,21 +1,22 @@
use std::{fmt, str};
use std::rc::Rc;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use bytes::{Bytes, BytesMut};
use cookie::Cookie;
use futures::{Async, Future, Poll, Stream};
use http_range::HttpRange;
use http::{HeaderMap, StatusCode, Version};
use http::header::{self, HeaderValue};
use mime::Mime;
use serde_json;
use serde::de::DeserializeOwned;
use url::form_urlencoded;
use payload::{Payload, ReadAny};
use multipart::Multipart;
use httprequest::UrlEncoded;
use error::{CookieParseError, ParseError, PayloadError, JsonPayloadError, HttpRangeError};
// use multipart::Multipart;
use error::{CookieParseError, ParseError, PayloadError, JsonPayloadError, UrlencodedError};
use super::pipeline::Pipeline;
pub(crate) struct ClientMessage {
@ -23,7 +24,6 @@ pub(crate) struct ClientMessage {
pub version: Version,
pub headers: HeaderMap<HeaderValue>,
pub cookies: Option<Vec<Cookie<'static>>>,
pub payload: Option<Payload>,
}
impl Default for ClientMessage {
@ -34,18 +34,21 @@ impl Default for ClientMessage {
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
cookies: None,
payload: None,
}
}
}
/// An HTTP Client response
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>);
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(Rc::new(UnsafeCell::new(msg)))
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
self.1 = Some(pl);
}
#[inline]
@ -155,53 +158,12 @@ impl ClientResponse {
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
pub fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}
}
/// Returns reference to the associated http payload.
#[inline]
pub fn payload(&self) -> &Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_ref().unwrap()
}
/// Returns mutable reference to the associated http payload.
#[inline]
pub fn payload_mut(&mut self) -> &mut Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_mut().unwrap()
}
/// Load request body.
///
/// By default only 256Kb payload reads to a memory, then `ResponseBody`
/// resolves to an error. Use `RequestBody::limit()`
/// method to change upper limit.
pub fn body(&self) -> ResponseBody {
ResponseBody::from_response(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
pub fn multipart(&mut self) -> Multipart {
Multipart::from_response(self)
}
// /// Return stream to http payload processes as multipart.
// ///
// /// Content-type: multipart/form-data;
// pub fn multipart(mut self) -> Multipart {
// Multipart::from_response(&mut self)
// }
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
@ -212,10 +174,8 @@ impl ClientResponse {
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
pub fn urlencoded(&self) -> UrlEncoded {
UrlEncoded::from(self.payload().clone(),
self.headers(),
self.chunked().unwrap_or(false))
pub fn urlencoded(self) -> UrlEncoded {
UrlEncoded::new(self)
}
/// Parse `application/json` encoded body.
@ -225,7 +185,7 @@ impl ClientResponse {
///
/// * content type is not `application/json`
/// * content length is greater than 256k
pub fn json<T: DeserializeOwned>(&self) -> JsonResponse<T> {
pub fn json<T: DeserializeOwned>(self) -> JsonResponse<T> {
JsonResponse::from_response(self)
}
}
@ -247,77 +207,16 @@ impl fmt::Debug for ClientResponse {
}
}
impl Clone for ClientResponse {
fn clone(&self) -> ClientResponse {
ClientResponse(self.0.clone())
}
}
/// Future that resolves to a complete request body.
pub struct ResponseBody {
pl: ReadAny,
body: BytesMut,
limit: usize,
resp: Option<ClientResponse>,
}
impl ResponseBody {
/// Create `RequestBody` for request.
pub fn from_response(resp: &ClientResponse) -> ResponseBody {
let pl = resp.payload().readany();
ResponseBody {
pl: pl,
body: BytesMut::new(),
limit: 262_144,
resp: Some(resp.clone()),
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for ResponseBody {
impl Stream for ClientResponse {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(resp) = self.resp.take() {
if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::UnknownLength);
}
} else {
return Err(PayloadError::UnknownLength);
}
}
}
loop {
return match self.pl.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
Ok(Async::Ready(self.body.take().freeze()))
},
Ok(Async::Ready(Some(chunk))) => {
if (self.body.len() + chunk.len()) > self.limit {
Err(PayloadError::Overflow)
} else {
self.body.extend_from_slice(&chunk);
continue
}
},
Err(err) => Err(err),
}
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut pl) = self.1 {
pl.poll()
} else {
Ok(Async::Ready(None))
}
}
}
@ -328,6 +227,7 @@ impl Future for ResponseBody {
///
/// * content type is not `application/json`
/// * content length is greater than 256k
#[must_use = "JsonResponse does nothing unless polled"]
pub struct JsonResponse<T: DeserializeOwned>{
limit: usize,
ct: &'static str,
@ -338,12 +238,12 @@ pub struct JsonResponse<T: DeserializeOwned>{
impl<T: DeserializeOwned> JsonResponse<T> {
/// Create `JsonBody` for request.
pub fn from_response(resp: &ClientResponse) -> Self {
pub fn from_response(resp: ClientResponse) -> Self {
JsonResponse{
limit: 262_144,
resp: Some(resp.clone()),
fut: None,
resp: Some(resp),
ct: "application/json",
fut: None,
}
}
@ -386,8 +286,7 @@ impl<T: DeserializeOwned + 'static> Future for JsonResponse<T> {
}
let limit = self.limit;
let fut = resp.payload().readany()
.from_err()
let fut = resp.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
@ -397,9 +296,93 @@ impl<T: DeserializeOwned + 'static> Future for JsonResponse<T> {
}
})
.and_then(|body| Ok(serde_json::from_slice::<T>(&body)?));
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("JsonResponse could not be used second time").poll()
}
}
/// Future that resolves to a parsed urlencoded values.
#[must_use = "UrlEncoded does nothing unless polled"]
pub struct UrlEncoded {
resp: Option<ClientResponse>,
limit: usize,
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>,
}
impl UrlEncoded {
pub fn new(resp: ClientResponse) -> UrlEncoded {
UrlEncoded{resp: Some(resp),
limit: 262_144,
fut: None}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for UrlEncoded {
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(resp) = self.resp.take() {
if resp.chunked().unwrap_or(false) {
return Err(UrlencodedError::Chunked)
} else if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
}
// check content type
let mut encoding = false;
if let Some(content_type) = resp.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if content_type.to_lowercase() == "application/x-www-form-urlencoded" {
encoding = true;
}
}
}
if !encoding {
return Err(UrlencodedError::ContentType);
}
// urlencoded body
let limit = self.limit;
let fut = resp.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| {
let mut m = HashMap::new();
for (k, v) in form_urlencoded::parse(&body) {
m.insert(k.into(), v.into());
}
Ok(m)
});
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}

View File

@ -1,13 +1,20 @@
#![allow(dead_code)]
use std::io;
use std::fmt::Write;
use bytes::BufMut;
use bytes::{BytesMut, BufMut};
use futures::{Async, Poll};
use tokio_io::AsyncWrite;
use http::{Version, HttpTryFrom};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
use flate2::Compression;
use flate2::write::{GzEncoder, DeflateEncoder};
use brotli2::write::BrotliEncoder;
use body::Binary;
use body::{Body, Binary};
use headers::ContentEncoding;
use server::WriterState;
use server::shared::SharedBytes;
use server::encoding::{ContentEncoder, TransferEncoding};
use client::ClientRequest;
@ -30,6 +37,7 @@ pub(crate) struct HttpClientWriter {
written: u64,
headers_size: u32,
buffer: SharedBytes,
encoder: ContentEncoder,
low: usize,
high: usize,
}
@ -37,11 +45,13 @@ pub(crate) struct HttpClientWriter {
impl HttpClientWriter {
pub fn new(buf: SharedBytes) -> HttpClientWriter {
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buf.clone()));
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer: buf,
encoder: encoder,
low: LOW_WATERMARK,
high: HIGH_WATERMARK,
}
@ -87,18 +97,23 @@ impl HttpClientWriter {
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg);
// render message
{
let buffer = self.buffer.get_mut();
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE);
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE);
}
// status line
let _ = write!(buffer, "{} {} {:?}\r\n",
msg.method(), msg.uri().path(), msg.version());
msg.method(), msg.uri().path(), msg.version());
// write headers
for (key, value) in msg.headers() {
@ -119,7 +134,15 @@ impl HttpClientWriter {
buffer.extend_from_slice(b"\r\n");
//}
self.headers_size = buffer.len() as u32;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.encoder.write(bytes)?;
}
}
}
Ok(())
}
pub fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
@ -160,3 +183,127 @@ impl HttpClientWriter {
}
}
}
fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder {
let version = req.version();
let mut body = req.replace_body(Body::Empty);
let mut encoding = req.content_encoding();
let transfer = match body {
Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH);
TransferEncoding::length(0, buf)
},
Body::Binary(ref mut bytes) => {
if encoding.is_compression() {
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())),
ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!()
};
// TODO return error!
let _ = enc.write(bytes.clone());
let _ = enc.write_eof();
*bytes = Binary::from(tmp.take());
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut().insert(
CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
},
Body::Streaming(_) | Body::Actor(_) => {
if req.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
} else {
req.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
req.headers_mut().remove(CONTENT_ENCODING);
}
TransferEncoding::eof(buf)
} else {
streaming_encoding(buf, version, req)
}
}
};
req.replace_body(body);
match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())),
ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer),
}
}
fn streaming_encoding(buf: SharedBytes, version: Version, req: &mut ClientRequest)
-> TransferEncoding {
if req.chunked() {
// Enable transfer encoding
req.headers_mut().remove(CONTENT_LENGTH);
if version == Version::HTTP_2 {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
req.headers_mut().insert(
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
} else {
// if Content-Length is specified, then use it as length hint
let (len, chunked) =
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
(Some(len), false)
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
(None, true)
};
if !chunked {
if let Some(len) = len {
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
match version {
Version::HTTP_11 => {
req.headers_mut().insert(
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
},
_ => {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
}
}
}
}
}

View File

@ -14,7 +14,6 @@ use futures::task::{Task, current as current_task};
use error::{ParseError, PayloadError, MultipartError};
use payload::Payload;
use client::ClientResponse;
use httprequest::HttpRequest;
const MAX_HEADERS: usize = 32;
@ -98,18 +97,18 @@ impl Multipart {
}
}
/// Create multipart instance for client response.
pub fn from_response(resp: &mut ClientResponse) -> Multipart {
match Multipart::boundary(resp.headers()) {
Ok(boundary) => Multipart::new(boundary, resp.payload().clone()),
Err(err) =>
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
}
// /// Create multipart instance for client response.
// pub fn from_response(resp: &mut ClientResponse) -> Multipart {
// match Multipart::boundary(resp.headers()) {
// Ok(boundary) => Multipart::new(boundary, resp.payload().clone()),
// Err(err) =>
// Multipart {
// error: Some(err),
// safety: Safety::new(),
// inner: None,
// }
// }
// }
/// Extract boundary info from headers.
pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {

View File

@ -26,7 +26,7 @@ use super::shared::SharedBytes;
impl ContentEncoding {
#[inline]
fn is_compression(&self) -> bool {
pub fn is_compression(&self) -> bool {
match *self {
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true
@ -546,7 +546,7 @@ impl PayloadEncoder {
}
}
enum ContentEncoder {
pub(crate) enum ContentEncoder {
Deflate(DeflateEncoder<TransferEncoding>),
Gzip(GzEncoder<TransferEncoding>),
Br(BrotliEncoder<TransferEncoding>),

View File

@ -178,6 +178,7 @@ impl WsClient {
self.request.set_header(header::ORIGIN, origin);
}
self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade");
self.request.set_header("SEC-WEBSOCKET-VERSION", "13");
@ -265,7 +266,7 @@ impl Future for WsHandshake {
if !self.sent {
self.sent = true;
inner.writer.start(&mut self.request);
inner.writer.start(&mut self.request)?;
}
if let Err(err) = inner.writer.poll_completed(&mut inner.conn, false) {
return Err(err.into())