api: Replace bytes::Buf by AsRef<u8> for reading

This allows us to switch back to serde_json::from_slice instead of
serde_json::from_reader, because the latter is significantly slower.

See https://github.com/serde-rs/json/issues/160
This commit is contained in:
Jonas Platte 2021-04-13 21:57:23 +02:00
parent e4ae2a40ee
commit c1693569f1
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
18 changed files with 57 additions and 66 deletions

View File

@ -10,7 +10,6 @@ impl Request {
error_ty: &TokenStream,
ruma_api: &TokenStream,
) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http };
let percent_encoding = quote! { #ruma_api::exports::percent_encoding };
let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
@ -159,15 +158,17 @@ impl Request {
RequestBody #body_lifetimes
as #ruma_serde::Outgoing
>::Incoming = {
let body = request.into_body();
if #bytes::Buf::has_remaining(&body) {
#serde_json::from_reader(#bytes::Buf::reader(body))?
} else {
let body = ::std::convert::AsRef::<[::std::primitive::u8]>::as_ref(
request.body(),
);
#serde_json::from_slice(match body {
// If the request body is completely empty, pretend it is an empty JSON
// object instead. This allows requests with only optional body parameters
// to be deserialized in that case.
#serde_json::from_str("{}")?
}
[] => b"{}",
b => b,
})?
};
}
} else {
@ -184,13 +185,8 @@ impl Request {
} else if let Some(field) = self.newtype_raw_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier");
let parse = quote! {
let #field_name = {
let mut reader = #bytes::Buf::reader(request.into_body());
let mut vec = ::std::vec::Vec::new();
::std::io::Read::read_to_end(&mut reader, &mut vec)
.expect("reading from a bytes::Buf never fails");
vec
};
let #field_name =
::std::convert::AsRef::<[u8]>::as_ref(request.body()).to_vec();
};
(parse, quote! { #field_name, })

View File

@ -5,7 +5,6 @@ use super::{Response, ResponseField};
impl Response {
pub fn expand_incoming(&self, error_ty: &TokenStream, ruma_api: &TokenStream) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http };
let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
let serde_json = quote! { #ruma_api::exports::serde_json };
@ -25,15 +24,17 @@ impl Response {
ResponseBody
as #ruma_serde::Outgoing
>::Incoming = {
let body = response.into_body();
if #bytes::Buf::has_remaining(&body) {
#serde_json::from_reader(#bytes::Buf::reader(body))?
} else {
let body = ::std::convert::AsRef::<[::std::primitive::u8]>::as_ref(
response.body(),
);
#serde_json::from_slice(match body {
// If the response body is completely empty, pretend it is an empty
// JSON object instead. This allows responses with only optional body
// parameters to be deserialized in that case.
#serde_json::from_str("{}")?
}
[] => b"{}",
b => b,
})?
};
}
} else {
@ -93,11 +94,10 @@ impl Response {
ResponseField::NewtypeRawBody(_) => {
new_type_raw_body = Some(quote! {
#field_name: {
let mut reader = #bytes::Buf::reader(response.into_body());
let mut vec = ::std::vec::Vec::new();
::std::io::Read::read_to_end(&mut reader, &mut vec)
.expect("reading from a bytes::Buf never fails");
vec
::std::convert::AsRef::<[::std::primitive::u8]>::as_ref(
response.body(),
)
.to_vec()
}
});
// skip adding to the vec
@ -120,7 +120,7 @@ impl Response {
impl #ruma_api::IncomingResponse for Response {
type EndpointError = #error_ty;
fn try_from_http_response<T: #bytes::Buf>(
fn try_from_http_response<T: ::std::convert::AsRef<[::std::primitive::u8]>>(
response: #http::Response<T>,
) -> ::std::result::Result<
Self,
@ -130,15 +130,17 @@ impl Response {
#extract_response_headers
#typed_response_body_decl
Ok(Self {
::std::result::Result::Ok(Self {
#response_init_fields
})
} else {
match <#error_ty as #ruma_api::EndpointError>::try_from_http_response(
response
) {
Ok(err) => Err(#ruma_api::error::ServerError::Known(err).into()),
Err(response_err) => {
::std::result::Result::Ok(err) => {
Err(#ruma_api::error::ServerError::Known(err).into())
}
::std::result::Result::Err(response_err) => {
Err(#ruma_api::error::ServerError::Unknown(response_err).into())
}
}

View File

@ -19,7 +19,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
bytes = "1.0.1"
http = "0.2.2"
percent-encoding = "2.1.0"
ruma-api-macros = { version = "=0.17.0-alpha.2", path = "../ruma-api-macros" }

View File

@ -4,7 +4,6 @@
use std::{error::Error as StdError, fmt};
use bytes::Buf;
use thiserror::Error;
use crate::{EndpointError, OutgoingResponse};
@ -21,7 +20,7 @@ impl OutgoingResponse for Void {
}
impl EndpointError for Void {
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
_response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
Err(ResponseDeserializationError::none())

View File

@ -22,7 +22,6 @@ compile_error!("ruma_api's Cargo features only exist as a workaround are not mea
use std::{convert::TryInto as _, error::Error as StdError};
use bytes::Buf;
use http::Method;
use ruma_identifiers::UserId;
@ -204,7 +203,6 @@ pub mod error;
/// It is not considered part of ruma-api's public API.
#[doc(hidden)]
pub mod exports {
pub use bytes;
pub use http;
pub use percent_encoding;
pub use ruma_serde;
@ -246,7 +244,7 @@ pub trait IncomingResponse: Sized {
type EndpointError: EndpointError;
/// Tries to convert the given `http::Response` into this response type.
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Self::EndpointError>>;
}
@ -301,7 +299,9 @@ pub trait IncomingRequest: Sized {
const METADATA: Metadata;
/// Tries to turn the given `http::Request` into this request type.
fn try_from_http_request<T: Buf>(req: http::Request<T>) -> Result<Self, FromHttpRequestError>;
fn try_from_http_request<T: AsRef<[u8]>>(
req: http::Request<T>,
) -> Result<Self, FromHttpRequestError>;
}
/// A request type for a Matrix API endpoint, used for sending responses.
@ -319,7 +319,7 @@ pub trait EndpointError: OutgoingResponse + StdError + Sized + 'static {
///
/// This will always return `Err` variant when no `error` field is defined in
/// the `ruma_api` macro.
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, error::ResponseDeserializationError>;
}

View File

@ -48,7 +48,7 @@ fn request_serde() {
};
let http_req = req.clone().try_into_http_request("https://homeserver.tld", None).unwrap();
let req2 = Request::try_from_http_request(http_req.map(std::io::Cursor::new)).unwrap();
let req2 = Request::try_from_http_request(http_req).unwrap();
assert_eq!(req.hello, req2.hello);
assert_eq!(req.world, req2.world);

View File

@ -2,7 +2,6 @@
use std::convert::TryFrom;
use bytes::Buf;
use http::{header::CONTENT_TYPE, method::Method};
use ruma_api::{
error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, ServerError, Void},
@ -67,7 +66,7 @@ impl IncomingRequest for Request {
const METADATA: Metadata = METADATA;
fn try_from_http_request<T: Buf>(
fn try_from_http_request<T: AsRef<[u8]>>(
request: http::Request<T>,
) -> Result<Self, FromHttpRequestError> {
let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect();
@ -78,7 +77,7 @@ impl IncomingRequest for Request {
TryFrom::try_from(&*decoded)?
};
let request_body: RequestBody = serde_json::from_reader(request.into_body().reader())?;
let request_body: RequestBody = serde_json::from_slice(request.body().as_ref())?;
Ok(Request { room_id: request_body.room_id, room_alias })
}
@ -100,7 +99,7 @@ impl Outgoing for Response {
impl IncomingResponse for Response {
type EndpointError = Void;
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
http_response: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Void>> {
if http_response.status().as_u16() < 400 {

View File

@ -1,4 +1,3 @@
use bytes::Buf;
use ruma_api::{
error::{FromHttpResponseError, IntoHttpError, Void},
ruma_api, IncomingResponse, OutgoingResponse,
@ -28,7 +27,7 @@ pub struct Response;
impl IncomingResponse for Response {
type EndpointError = Void;
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
_: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Void>> {
todo!()

View File

@ -17,7 +17,6 @@ edition = "2018"
[dependencies]
assign = "1.1.1"
bytes = "1.0.1"
http = "0.2.2"
js_int = { version = "0.2.0", features = ["serde"] }
maplit = "1.0.2"

View File

@ -2,14 +2,13 @@
use std::{collections::BTreeMap, fmt, time::Duration};
use bytes::Buf;
use ruma_api::{
error::{IntoHttpError, ResponseDeserializationError},
EndpointError, OutgoingResponse,
};
use ruma_identifiers::RoomVersionId;
use serde::{Deserialize, Serialize};
use serde_json::{from_reader as from_json_reader, to_vec as to_json_vec, Value as JsonValue};
use serde_json::{from_slice as from_json_slice, to_vec as to_json_vec, Value as JsonValue};
/// Deserialize and Serialize implementations for ErrorKind.
/// Separate module because it's a lot of code.
@ -206,11 +205,11 @@ pub struct Error {
}
impl EndpointError for Error {
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
let status = response.status();
let error_body: ErrorBody = from_json_reader(response.into_body().reader())?;
let error_body: ErrorBody = from_json_slice(response.body().as_ref())?;
Ok(error_body.into_error(status))
}
}

View File

@ -104,7 +104,7 @@ impl ruma_api::IncomingRequest for IncomingRequest {
const METADATA: ruma_api::Metadata = METADATA;
fn try_from_http_request<T: bytes::Buf>(
fn try_from_http_request<T: AsRef<[u8]>>(
request: http::Request<T>,
) -> Result<Self, ruma_api::error::FromHttpRequestError> {
use std::convert::TryFrom;
@ -129,7 +129,7 @@ impl ruma_api::IncomingRequest for IncomingRequest {
let content = {
let event_type =
percent_encoding::percent_decode(path_segments[6].as_bytes()).decode_utf8()?;
let body: Box<RawJsonValue> = serde_json::from_reader(body.reader())?;
let body: Box<RawJsonValue> = serde_json::from_slice(body.as_ref())?;
AnyMessageEventContent::from_parts(&event_type, body)?
};

View File

@ -77,9 +77,8 @@ mod tests {
http::Request::builder()
.method("PUT")
.uri("https://bar.org/_matrix/client/r0/profile/@foo:bar.org/avatar_url")
.body(std::io::Cursor::new(
serde_json::to_vec(&serde_json::json!({ "avatar_url": "" })).unwrap(),
)).unwrap(),
.body(serde_json::to_vec(&serde_json::json!({ "avatar_url": "" })).unwrap())
.unwrap(),
).unwrap(),
IncomingRequest { user_id, avatar_url: None } if user_id == "@foo:bar.org"
);

View File

@ -108,7 +108,7 @@ impl ruma_api::IncomingRequest for IncomingRequest {
const METADATA: ruma_api::Metadata = METADATA;
fn try_from_http_request<T: bytes::Buf>(
fn try_from_http_request<T: AsRef<[u8]>>(
request: http::Request<T>,
) -> Result<Self, ruma_api::error::FromHttpRequestError> {
use std::convert::TryFrom;

View File

@ -108,7 +108,7 @@ impl ruma_api::IncomingRequest for IncomingRequest {
const METADATA: ruma_api::Metadata = METADATA;
fn try_from_http_request<T: bytes::Buf>(
fn try_from_http_request<T: AsRef<[u8]>>(
request: http::Request<T>,
) -> Result<Self, ruma_api::error::FromHttpRequestError> {
use std::{borrow::Cow, convert::TryFrom};
@ -136,7 +136,7 @@ impl ruma_api::IncomingRequest for IncomingRequest {
let content = {
let event_type =
percent_encoding::percent_decode(path_segments[6].as_bytes()).decode_utf8()?;
let body: Box<RawJsonValue> = serde_json::from_reader(body.reader())?;
let body: Box<RawJsonValue> = serde_json::from_slice(body.as_ref())?;
AnyStateEventContent::from_parts(&event_type, body)?
};

View File

@ -2,7 +2,6 @@
use std::{collections::BTreeMap, fmt};
use bytes::Buf;
use ruma_api::{
error::{IntoHttpError, ResponseDeserializationError},
EndpointError, OutgoingResponse,
@ -10,7 +9,7 @@ use ruma_api::{
use ruma_serde::Outgoing;
use serde::{Deserialize, Serialize};
use serde_json::{
from_reader as from_json_reader, to_vec as to_json_vec, value::RawValue as RawJsonValue,
from_slice as from_json_slice, to_vec as to_json_vec, value::RawValue as RawJsonValue,
Value as JsonValue,
};
@ -137,11 +136,11 @@ impl From<MatrixError> for UiaaResponse {
}
impl EndpointError for UiaaResponse {
fn try_from_http_response<T: Buf>(
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
if response.status() == http::StatusCode::UNAUTHORIZED {
Ok(UiaaResponse::AuthResponse(from_json_reader(response.into_body().reader())?))
Ok(UiaaResponse::AuthResponse(from_json_slice(response.body().as_ref())?))
} else {
MatrixError::try_from_http_response(response).map(From::from)
}

View File

@ -373,7 +373,9 @@ impl Client {
let hyper_response = client.hyper.request(http_request.map(hyper::Body::from)).await?;
let (head, body) = hyper_response.into_parts();
let full_body = hyper::body::aggregate(body).await?;
// FIXME: Use aggregate instead of to_bytes once serde_json can parse from a reader at a
// comparable speed as reading from a slice: https://github.com/serde-rs/json/issues/160
let full_body = hyper::body::to_bytes(body).await?;
let full_response = HttpResponse::from_parts(head, full_body);
Ok(ruma_api::IncomingResponse::try_from_http_response(full_response)?)

View File

@ -6,7 +6,7 @@ pub fn expand_display_as_ref_str(ident: &Ident) -> syn::Result<TokenStream> {
#[automatically_derived]
impl ::std::fmt::Display for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(<Self as ::std::convert::AsRef<::std::primitive::str>>::as_ref(self))
f.write_str(::std::convert::AsRef::<::std::primitive::str>::as_ref(self))
}
}
})

View File

@ -13,8 +13,7 @@ pub fn expand_serialize_as_ref_str(ident: &Ident) -> syn::Result<TokenStream> {
where
S: #ruma_serde::exports::serde::ser::Serializer,
{
<Self as ::std::convert::AsRef<::std::primitive::str>>::as_ref(self)
.serialize(serializer)
::std::convert::AsRef::<::std::primitive::str>::as_ref(self).serialize(serializer)
}
}
})