mirror of https://github.com/rwf2/Rocket.git
Add MsgPack implementation to contrib.
This commit is contained in:
parent
4f704e95f2
commit
d43678c35e
|
@ -22,6 +22,7 @@ members = [
|
||||||
"examples/from_request",
|
"examples/from_request",
|
||||||
"examples/stream",
|
"examples/stream",
|
||||||
"examples/json",
|
"examples/json",
|
||||||
|
"examples/msgpack",
|
||||||
"examples/handlebars_templates",
|
"examples/handlebars_templates",
|
||||||
"examples/form_kitchen_sink",
|
"examples/form_kitchen_sink",
|
||||||
"examples/config",
|
"examples/config",
|
||||||
|
|
|
@ -13,6 +13,7 @@ license = "MIT/Apache-2.0"
|
||||||
[features]
|
[features]
|
||||||
default = ["json"]
|
default = ["json"]
|
||||||
json = ["serde", "serde_json"]
|
json = ["serde", "serde_json"]
|
||||||
|
msgpack = ["serde", "rmp-serde"]
|
||||||
tera_templates = ["tera", "templates"]
|
tera_templates = ["tera", "templates"]
|
||||||
handlebars_templates = ["handlebars", "templates"]
|
handlebars_templates = ["handlebars", "templates"]
|
||||||
|
|
||||||
|
@ -27,9 +28,10 @@ log = "^0.3"
|
||||||
# UUID dependencies.
|
# UUID dependencies.
|
||||||
uuid = { version = "^0.4", optional = true }
|
uuid = { version = "^0.4", optional = true }
|
||||||
|
|
||||||
# JSON and templating dependencies.
|
# Serialization and templating dependencies.
|
||||||
serde = { version = "^0.9", optional = true }
|
serde = { version = "^0.9", optional = true }
|
||||||
serde_json = { version = "^0.9.3", optional = true }
|
serde_json = { version = "^0.9.3", optional = true }
|
||||||
|
rmp-serde = { version = "^0.12", optional = true }
|
||||||
|
|
||||||
# Templating dependencies only.
|
# Templating dependencies only.
|
||||||
handlebars = { version = "^0.25", optional = true, features = ["serde_type"] }
|
handlebars = { version = "^0.25", optional = true, features = ["serde_type"] }
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
//! an asterisk next to the features that are enabled by default:
|
//! an asterisk next to the features that are enabled by default:
|
||||||
//!
|
//!
|
||||||
//! * [json*](struct.JSON.html)
|
//! * [json*](struct.JSON.html)
|
||||||
|
//! * [msgpack](struct.MsgPack.html)
|
||||||
//! * [handlebars_templates](struct.Template.html)
|
//! * [handlebars_templates](struct.Template.html)
|
||||||
//! * [tera_templates](struct.Template.html)
|
//! * [tera_templates](struct.Template.html)
|
||||||
//! * [uuid](struct.UUID.html)
|
//! * [uuid](struct.UUID.html)
|
||||||
|
@ -55,6 +56,13 @@ pub mod json;
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
pub use json::{JSON, SerdeError, Value};
|
pub use json::{JSON, SerdeError, Value};
|
||||||
|
|
||||||
|
#[cfg(feature = "msgpack")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod msgpack;
|
||||||
|
|
||||||
|
#[cfg(feature = "msgpack")]
|
||||||
|
pub use msgpack::{MsgPack, MsgPackError};
|
||||||
|
|
||||||
#[cfg(feature = "templates")]
|
#[cfg(feature = "templates")]
|
||||||
mod templates;
|
mod templates;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
extern crate rmp_serde;
|
||||||
|
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
|
use rocket::outcome::Outcome;
|
||||||
|
use rocket::request::Request;
|
||||||
|
use rocket::data::{self, Data, FromData};
|
||||||
|
use rocket::response::{self, Responder, Response};
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
pub use self::rmp_serde::decode::Error as MsgPackError;
|
||||||
|
|
||||||
|
/// The `MsgPack` type: implements `FromData` and `Responder`, allowing you to easily
|
||||||
|
/// consume and respond with MessagePack data.
|
||||||
|
///
|
||||||
|
/// If you're receiving MessagePack data, simply add a `data` parameter to your route
|
||||||
|
/// arguments and ensure the type of the parameter is a `MsgPack<T>`, where `T` is
|
||||||
|
/// some type you'd like to parse from MessagePack. `T` must implement `Deserialize`
|
||||||
|
/// from [Serde](https://github.com/serde-rs/serde). The data is parsed from the
|
||||||
|
/// HTTP request body.
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[post("/users/", format = "application/msgpack", data = "<user>")]
|
||||||
|
/// fn new_user(user: MsgPack<User>) {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You don't _need_ to use `format = "application/msgpack"`, but it _may_ be what
|
||||||
|
/// you want. Using `format = application/msgpack` means that any request that
|
||||||
|
/// doesn't specify "application/msgpack" as its first `Content-Type:` header
|
||||||
|
/// parameter will not be routed to this handler. By default, Rocket will accept a
|
||||||
|
/// Content Type of any of the following for MessagePack data:
|
||||||
|
/// `application/msgpack`, `application/x-msgpack`, `bin/msgpack`, or `bin/x-msgpack`.
|
||||||
|
///
|
||||||
|
/// If you're responding with MessagePack data, return a `MsgPack<T>` type, where `T`
|
||||||
|
/// implements `Serialize` from [Serde](https://github.com/serde-rs/serde). The
|
||||||
|
/// content type of the response is set to `application/msgpack` automatically.
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[get("/users/<id>")]
|
||||||
|
/// fn user(id: usize) -> MsgPack<User> {
|
||||||
|
/// let user_from_id = User::from(id);
|
||||||
|
/// ...
|
||||||
|
/// MsgPack(user_from_id)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MsgPack<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> MsgPack<T> {
|
||||||
|
/// Consumes the `MsgPack` wrapper and returns the wrapped item.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket_contrib::MsgPack;
|
||||||
|
/// let string = "Hello".to_string();
|
||||||
|
/// let my_msgpack = MsgPack(string);
|
||||||
|
/// assert_eq!(my_msgpack.into_inner(), "Hello".to_string());
|
||||||
|
/// ```
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum size of MessagePack data is 1MB.
|
||||||
|
/// TODO: Determine this size from some configuration parameter.
|
||||||
|
const MAX_SIZE: u64 = 1048576;
|
||||||
|
|
||||||
|
/// Accepted content types are:
|
||||||
|
/// `application/msgpack`, `application/x-msgpack`, `bin/msgpack`, and `bin/x-msgpack`
|
||||||
|
fn is_msgpack_content_type(ct: &ContentType) -> bool {
|
||||||
|
(ct.ttype == "application" || ct.ttype == "bin")
|
||||||
|
&& (ct.subtype == "msgpack" || ct.subtype == "x-msgpack")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Deserialize> FromData for MsgPack<T> {
|
||||||
|
type Error = MsgPackError;
|
||||||
|
|
||||||
|
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
||||||
|
if !request.content_type().map_or(false, |ct| is_msgpack_content_type(&ct)) {
|
||||||
|
error_!("Content-Type is not MessagePack.");
|
||||||
|
return Outcome::Forward(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
if let Err(e) = data.open().take(MAX_SIZE).read_to_end(&mut buf) {
|
||||||
|
let e = MsgPackError::InvalidDataRead(e);
|
||||||
|
error_!("Couldn't read request data: {:?}", e);
|
||||||
|
return Outcome::Failure((Status::BadRequest, e));
|
||||||
|
};
|
||||||
|
|
||||||
|
match rmp_serde::from_slice(&buf).map(|val| MsgPack(val)) {
|
||||||
|
Ok(value) => Outcome::Success(value),
|
||||||
|
Err(e) => {
|
||||||
|
error_!("Couldn't parse MessagePack body: {:?}", e);
|
||||||
|
Outcome::Failure((Status::BadRequest, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the wrapped value into MessagePack. Returns a response with Content-Type
|
||||||
|
/// MessagePack and a fixed-size body with the serialization. If serialization fails, an
|
||||||
|
/// `Err` of `Status::InternalServerError` is returned.
|
||||||
|
impl<T: Serialize> Responder<'static> for MsgPack<T> {
|
||||||
|
fn respond(self) -> response::Result<'static> {
|
||||||
|
rmp_serde::to_vec(&self.0).map_err(|e| {
|
||||||
|
error_!("MsgPack failed to serialize: {:?}", e);
|
||||||
|
Status::InternalServerError
|
||||||
|
}).and_then(|buf| {
|
||||||
|
Response::build()
|
||||||
|
.sized_body(Cursor::new(buf))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for MsgPack<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref<'a>(&'a self) -> &'a T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for MsgPack<T> {
|
||||||
|
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "msgpack"
|
||||||
|
version = "0.0.1"
|
||||||
|
workspace = "../../"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
serde = "0.9"
|
||||||
|
serde_derive = "0.9"
|
||||||
|
|
||||||
|
[dependencies.rocket_contrib]
|
||||||
|
path = "../../contrib"
|
||||||
|
default-features = false
|
||||||
|
features = ["msgpack"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rocket = { path = "../../lib", features = ["testing"] }
|
|
@ -0,0 +1,38 @@
|
||||||
|
#![feature(plugin)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
extern crate rocket_contrib;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
|
#[cfg(test)] mod tests;
|
||||||
|
|
||||||
|
use rocket_contrib::MsgPack;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Message {
|
||||||
|
id: usize,
|
||||||
|
contents: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<id>", format = "application/msgpack")]
|
||||||
|
fn get(id: usize) -> MsgPack<Message> {
|
||||||
|
MsgPack(Message {
|
||||||
|
id: id,
|
||||||
|
contents: "Hello, world!".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/", data = "<data>", format = "application/msgpack")]
|
||||||
|
fn create(data: MsgPack<Message>) -> Result<String, ()> {
|
||||||
|
Ok(data.into_inner().contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rocket() -> rocket::Rocket {
|
||||||
|
rocket::ignite()
|
||||||
|
.mount("/message", routes![get, create])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket().launch();
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
use rocket;
|
||||||
|
use rocket::testing::MockRequest;
|
||||||
|
use rocket::http::Method::*;
|
||||||
|
use rocket::http::{Status, ContentType};
|
||||||
|
use rocket::Response;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Message {
|
||||||
|
id: usize,
|
||||||
|
contents: String
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! run_test {
|
||||||
|
($rocket: expr, $req:expr, $test_fn:expr) => ({
|
||||||
|
let mut req = $req;
|
||||||
|
$test_fn(req.dispatch_with($rocket));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn msgpack_get() {
|
||||||
|
let rocket = rocket();
|
||||||
|
let req = MockRequest::new(Get, "/message/1").header(ContentType::MsgPack);
|
||||||
|
run_test!(&rocket, req, |mut response: Response| {
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
let body = response.body().unwrap().into_bytes().unwrap();
|
||||||
|
// Represents a message of `[1, "Hello, world!"]`
|
||||||
|
assert_eq!(&body, &[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111,
|
||||||
|
114, 108, 100, 33]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn msgpack_post() {
|
||||||
|
let rocket = rocket();
|
||||||
|
let req = MockRequest::new(Post, "/message")
|
||||||
|
.header(ContentType::MsgPack)
|
||||||
|
// Represents a message of `[2, "Goodbye, world!"]`
|
||||||
|
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33]);
|
||||||
|
run_test!(&rocket, req, |mut response: Response| {
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
assert_eq!(response.body().unwrap().into_string().unwrap(), "Goodbye, world!");
|
||||||
|
});
|
||||||
|
}
|
|
@ -104,6 +104,7 @@ impl ContentType {
|
||||||
"HTML", HTML, is_html => "text", "html" ; "charset=utf-8",
|
"HTML", HTML, is_html => "text", "html" ; "charset=utf-8",
|
||||||
"Plain", Plain, is_plain => "text", "plain" ; "charset=utf-8",
|
"Plain", Plain, is_plain => "text", "plain" ; "charset=utf-8",
|
||||||
"JSON", JSON, is_json => "application", "json",
|
"JSON", JSON, is_json => "application", "json",
|
||||||
|
"MsgPack", MsgPack, is_msgpack => "application", "msgpack",
|
||||||
"form", Form, is_form => "application", "x-www-form-urlencoded",
|
"form", Form, is_form => "application", "x-www-form-urlencoded",
|
||||||
"JavaScript", JavaScript, is_javascript => "application", "javascript",
|
"JavaScript", JavaScript, is_javascript => "application", "javascript",
|
||||||
"CSS", CSS, is_css => "text", "css" ; "charset=utf-8",
|
"CSS", CSS, is_css => "text", "css" ; "charset=utf-8",
|
||||||
|
|
|
@ -82,6 +82,7 @@ macro_rules! ctrs {
|
||||||
ctrs! {
|
ctrs! {
|
||||||
JSON: "JSON", "application/json",
|
JSON: "JSON", "application/json",
|
||||||
XML: "XML", "text/xml",
|
XML: "XML", "text/xml",
|
||||||
|
MsgPack: "MessagePack", "application/msgpack",
|
||||||
HTML: "HTML", "text/html",
|
HTML: "HTML", "text/html",
|
||||||
Plain: "plain text", "text/plain",
|
Plain: "plain text", "text/plain",
|
||||||
CSS: "CSS", "text/css",
|
CSS: "CSS", "text/css",
|
||||||
|
|
|
@ -224,8 +224,8 @@ impl<'r> MockRequest<'r> {
|
||||||
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
|
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn body<S: AsRef<str>>(mut self, body: S) -> Self {
|
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
|
||||||
self.data = Data::new(body.as_ref().as_bytes().into());
|
self.data = Data::new(body.as_ref().into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue