Skip to content

Commit

Permalink
multipart implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
fafhrd91 committed Oct 19, 2017
1 parent 264380b commit aaef550
Show file tree
Hide file tree
Showing 15 changed files with 781 additions and 87 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/target/

target/
Cargo.lock
/gh-pages
__pycache__
Expand All @@ -10,7 +9,6 @@ __pycache__
*.pid
*.sock
*~
target/
*.egg-info/

# These are backup files generated by rustfmt
Expand Down
11 changes: 2 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,25 @@ codecov = { repository = "fafhrd91/actix-web", branch = "master", service = "git
name = "actix_web"
path = "src/lib.rs"

[[bin]]
name = "test"
path = "src/main.rs"

[features]
default = ["nightly"]

# Enable nightly features
nightly = []

[dependencies]
log = "0.3"
time = "0.1"
http = "0.1"
httparse = "0.1"
http-range = "0.1"
mime = "0.3"
mime_guess = "1.8"
cookie = { version="0.10", features=["percent-encode"] }
regex = "0.2"
slab = "0.4"
sha1 = "0.2"
url = "1.5"
multipart-async = { version = "0.*", features=["server"]}

# tokio
bytes = "0.4"
Expand All @@ -51,10 +48,6 @@ tokio-core = "0.1"
tokio-io = "0.1"
tokio-proto = "0.1"

# other
log = "0.3"
env_logger = "*"

[dependencies.actix]
#path = "../actix"
#git = "https://github.com/fafhrd91/actix.git"
Expand Down
13 changes: 13 additions & 0 deletions examples/multipart/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "multipart-example"
version = "0.1.0"
authors = ["Nikolay Kim <[email protected]>"]

[[bin]]
name = "multipart"
path = "src/main.rs"

[dependencies]
env_logger = "*"
actix = "0.2"
actix-web = { path = "../../" }
18 changes: 18 additions & 0 deletions examples/multipart/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import asyncio
import aiohttp


def client():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})

resp = yield from aiohttp.request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status


loop = asyncio.get_event_loop()
loop.run_until_complete(client())
70 changes: 70 additions & 0 deletions examples/multipart/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;

use actix::*;
use actix_web::*;

struct MyRoute;

impl Actor for MyRoute {
type Context = HttpContext<Self>;
}

impl Route for MyRoute {
type State = ();

fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
println!("{:?}", req);
match req.multipart(payload) {
Ok(multipart) => {
ctx.add_stream(multipart);
Reply::async(MyRoute)
},
// can not read multipart
Err(_) => {
Reply::reply(httpcodes::HTTPBadRequest)
}
}
}
}

impl ResponseType<multipart::MultipartItem> for MyRoute {
type Item = ();
type Error = ();
}

impl StreamHandler<multipart::MultipartItem, PayloadError> for MyRoute {
fn finished(&mut self, ctx: &mut Self::Context) {
println!("FINISHED");
ctx.start(httpcodes::HTTPOk);
ctx.write_eof();
}
}

impl Handler<multipart::MultipartItem, PayloadError> for MyRoute {
fn handle(&mut self, msg: multipart::MultipartItem, ctx: &mut HttpContext<Self>)
-> Response<Self, multipart::MultipartItem>
{
println!("==== FIELD ==== {:?}", msg);
//if let Some(req) = self.req.take() {
Self::empty()
}
}

fn main() {
let _ = env_logger::init();
let sys = actix::System::new("multipart-example");

HttpServer::new(
RoutingMap::default()
.app("/", Application::default()
.resource("/multipart", |r| {
r.post::<MyRoute>();
})
.finish())
.finish())
.serve::<_, ()>("127.0.0.1:8080").unwrap();

let _ = sys.run();
}
File renamed without changes.
5 changes: 4 additions & 1 deletion src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ impl Decoder {
if *remaining == 0 {
Ok(Async::Ready(None))
} else {
if body.is_empty() {
return Ok(Async::NotReady)
}
let len = body.len() as u64;
let buf;
if *remaining > len {
Expand All @@ -106,7 +109,7 @@ impl Decoder {
// advances the chunked state
*state = try_ready!(state.step(body, size, &mut buf));
if *state == ChunkedState::End {
trace!("end of chunked");
trace!("End of chunked stream");
return Ok(Async::Ready(None));
}
if let Some(buf) = buf {
Expand Down
83 changes: 21 additions & 62 deletions src/httprequest.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
//! HTTP Request message related code.
use std::{io, str};
use std::{str, fmt};
use std::collections::HashMap;
use bytes::{Bytes, BytesMut};
use bytes::BytesMut;
use futures::{Async, Future, Stream, Poll};
use url::form_urlencoded;
use multipart_async::server::BodyChunk;
use http::{header, Method, Version, Uri, HeaderMap};

use {Cookie, CookieParseError};
use {HttpRange, HttpRangeParseError};
use error::ParseError;
use recognizer::Params;
use multipart::Multipart;
use payload::{Payload, PayloadError};
use multipart::{Multipart, MultipartError};


#[derive(Debug)]
/// An HTTP Request
pub struct HttpRequest {
version: Version,
Expand Down Expand Up @@ -179,26 +177,13 @@ impl HttpRequest {
/// Return stream to process BODY as multipart.
///
/// Content-type: multipart/form-data;
pub fn multipart(&self, payload: Payload) -> Result<Multipart<Req>, Payload> {
const BOUNDARY: &'static str = "boundary=";

if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Some(start) = content_type.find(BOUNDARY) {
let start = start + BOUNDARY.len();
let end = content_type[start..].find(';')
.map_or(content_type.len(), |end| start + end);
let boundary = &content_type[start .. end];

return Ok(Multipart::with_body(Req{pl: payload}, boundary))
}
}
}
Err(payload)
pub fn multipart(&self, payload: Payload) -> Result<Multipart, MultipartError> {
Multipart::new(self, payload)
}

/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>`.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
Expand Down Expand Up @@ -238,51 +223,25 @@ impl HttpRequest {
}
}


#[doc(hidden)]
pub struct Req {
pl: Payload,
}

#[doc(hidden)]
pub struct Chunk(Bytes);

impl BodyChunk for Chunk {
#[inline]
fn split_at(mut self, idx: usize) -> (Self, Self) {
(Chunk(self.0.split_to(idx)), self)
}

#[inline]
fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}
}

impl Stream for Req {
type Item = Chunk;
type Error = io::Error;

fn poll(&mut self) -> Poll<Option<Chunk>, io::Error> {
match self.pl.poll() {
Err(_) =>
Err(io::Error::new(io::ErrorKind::InvalidData, "incomplete")),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::Ready(Some(item))) => match item {
Ok(bytes) => Ok(Async::Ready(Some(Chunk(bytes)))),
Err(err) => match err {
PayloadError::Incomplete =>
Err(io::Error::new(io::ErrorKind::InvalidData, "incomplete")),
PayloadError::ParseError(err) =>
Err(err.into())
}
impl fmt::Debug for HttpRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpRequest {:?} {}:{}\n", self.version, self.method, self.uri);
if !self.params.is_empty() {
let _ = write!(f, " params: {:?}\n", self.params);
}
let _ = write!(f, " headers:\n");
for key in self.headers.keys() {
let vals: Vec<_> = self.headers.get_all(key).iter().collect();
if vals.len() > 1 {
let _ = write!(f, " {:?}: {:?}\n", key, vals);
} else {
let _ = write!(f, " {:?}: {:?}\n", key, vals[0]);
}
}
res
}
}


/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded {
pl: Payload,
Expand Down
9 changes: 2 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ extern crate cookie;
extern crate http;
extern crate httparse;
extern crate http_range;
extern crate mime;
extern crate mime_guess;
extern crate multipart_async;
extern crate url;
extern crate actix;

Expand All @@ -47,6 +47,7 @@ mod wsproto;
pub mod ws;
pub mod dev;
pub mod httpcodes;
pub mod multipart;
pub use error::ParseError;
pub use application::{Application, ApplicationBuilder};
pub use httprequest::{HttpRequest, UrlEncoded};
Expand All @@ -65,9 +66,3 @@ pub use http::{Method, StatusCode};
pub use cookie::{Cookie, CookieBuilder};
pub use cookie::{ParseError as CookieParseError};
pub use http_range::{HttpRange, HttpRangeParseError};

/// Multipart support
pub mod multipart {
pub use multipart_async::server::{
Field, FieldData, FieldHeaders, Multipart, ReadTextField, TextField};
}
Loading

0 comments on commit aaef550

Please sign in to comment.