Skip to main content

jsonrpsee_core/server/
method_response.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27//! Represent a method response.
28
29const LOG_TARGET: &str = "jsonrpsee-core";
30
31use std::{
32    io,
33    sync::atomic::{AtomicUsize, Ordering},
34};
35
36use jsonrpsee_types::{
37    error::{
38        reject_too_big_batch_response, ErrorCode, ErrorObject, OVERSIZED_RESPONSE_CODE,
39        OVERSIZED_RESPONSE_MSG,
40    },
41    Id, Response,
42};
43use serde::Serialize;
44use serde_json::value::to_raw_value;
45
46pub use jsonrpsee_types::ResponsePayload;
47
48#[derive(Debug, Clone)]
49enum ResponseKind {
50    MethodCall,
51    Subscription,
52    Batch,
53    Notification,
54}
55
56/// Represents a response to a method call.
57///
58/// NOTE: A subscription is also a method call but it's
59/// possible determine whether a method response
60/// is "subscription" or "ordinary method call"
61/// by calling [`MethodResponse::is_subscription`]
62#[derive(Debug)]
63pub struct MethodResponse {
64    /// Serialized JSON-RPC response.
65    json: String,
66    /// Indicates whether the call was successful or not.
67    success_or_error: MethodResponseResult,
68    /// Indicates whether the call was a subscription response.
69    kind: ResponseKind,
70}
71
72impl AsRef<str> for MethodResponse {
73    fn as_ref(&self) -> &str {
74        &self.json
75    }
76}
77
78impl std::fmt::Display for MethodResponse {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", self.json)
81    }
82}
83
84impl MethodResponse {
85    /// Returns whether the call was successful.
86    pub fn is_success(&self) -> bool {
87        self.success_or_error.is_success()
88    }
89
90    /// Returns whether the call failed.
91    pub fn is_error(&self) -> bool {
92        self.success_or_error.is_error()
93    }
94
95    /// Returns whether the response is a subscription response.
96    pub fn is_subscription(&self) -> bool {
97        matches!(self.kind, ResponseKind::Subscription)
98    }
99
100    /// Returns whether the response is a method response.
101    pub fn is_method_call(&self) -> bool {
102        matches!(self.kind, ResponseKind::MethodCall)
103    }
104
105    /// Returns whether the response is a batch response.
106    pub fn is_batch(&self) -> bool {
107        matches!(self.kind, ResponseKind::Batch)
108    }
109
110    /// Consume the method response and extract the serialized JSON response.
111    pub fn into_json(self) -> String {
112        self.json
113    }
114
115    /// Get the serialized JSON response.
116    pub fn to_json(&self) -> String {
117        self.json.clone()
118    }
119
120    /// Get the error code
121    ///
122    /// Returns `Some(error code)` if the call failed.
123    pub fn as_error_code(&self) -> Option<i32> {
124        self.success_or_error.as_error_code()
125    }
126
127    /// This is similar to [`MethodResponse::response`] but sets a flag to indicate
128    /// that response is a subscription.
129    pub fn subscription_response<T>(
130        id: Id,
131        result: ResponsePayload<T>,
132        max_response_size: usize,
133    ) -> Self
134    where
135        T: Serialize + Clone,
136    {
137        let mut rp = Self::response(id, result, max_response_size);
138        rp.kind = ResponseKind::Subscription;
139        rp
140    }
141
142    /// Create a new method response.
143    ///
144    /// If the serialization of `result` exceeds `max_response_size` then
145    /// the response is changed to an JSON-RPC error object.
146    pub fn response<T>(id: Id, rp: ResponsePayload<T>, max_response_size: usize) -> Self
147    where
148        T: Serialize + Clone,
149    {
150        Self::response_with_capacity(id, rp, max_response_size, 128)
151    }
152
153    /// Same as [`MethodResponse::response`], but sizes the serialization buffer from the method's
154    /// previous responses and records the size of this one.
155    pub(crate) fn sized_response<T>(
156        id: Id,
157        rp: ResponsePayload<T>,
158        max_response_size: usize,
159        size_hint: &ResponseSizeHint,
160    ) -> Self
161    where
162        T: Serialize + Clone,
163    {
164        let mut rp = Self::response_with_capacity(id, rp, max_response_size, size_hint.get());
165        // Responses are never shrunk later, so a response much smaller than the previous one
166        // would otherwise keep the unused capacity until it is sent.
167        if rp.json.capacity() / 2 > rp.json.len() {
168            rp.json.shrink_to_fit();
169        }
170        size_hint.record(rp.json.len());
171        rp
172    }
173
174    fn response_with_capacity<T>(
175        id: Id,
176        rp: ResponsePayload<T>,
177        max_response_size: usize,
178        capacity: usize,
179    ) -> Self
180    where
181        T: Serialize + Clone,
182    {
183        let mut writer = BoundedWriter::new(max_response_size, capacity);
184
185        let success_or_error = if let ResponsePayload::Error(e) = &rp {
186            MethodResponseResult::Failed(e.code())
187        } else {
188            MethodResponseResult::Success
189        };
190
191        let kind = ResponseKind::MethodCall;
192
193        match serde_json::to_writer(&mut writer, &Response::new(rp, id.clone())) {
194            Ok(_) => {
195                // SAFETY: serde_json does not emit invalid UTF-8.
196                let json = unsafe { String::from_utf8_unchecked(writer.into_bytes()) };
197
198                Self { json, success_or_error, kind }
199            }
200            Err(err) => {
201                tracing::error!(target: LOG_TARGET, "Error serializing response: {:?}", err);
202
203                if err.is_io() {
204                    let data =
205                        to_raw_value(&format!("Exceeded max limit of {max_response_size}")).ok();
206                    let err_code = OVERSIZED_RESPONSE_CODE;
207
208                    let err = ResponsePayload::<()>::error_borrowed(ErrorObject::borrowed(
209                        err_code,
210                        OVERSIZED_RESPONSE_MSG,
211                        data.as_deref(),
212                    ));
213                    let json = serde_json::to_string(&Response::new(err, id))
214                        .expect("JSON serialization infallible; qed");
215
216                    Self { json, success_or_error: MethodResponseResult::Failed(err_code), kind }
217                } else {
218                    let err = ErrorCode::InternalError;
219                    let payload = ResponsePayload::<()>::error(err);
220                    let json = serde_json::to_string(&Response::new(payload, id))
221                        .expect("JSON serialization infallible; qed");
222                    Self { json, success_or_error: MethodResponseResult::Failed(err.code()), kind }
223                }
224            }
225        }
226    }
227
228    /// This is similar to [`MethodResponse::error`] but sets a flag to indicate
229    /// that error is a subscription.
230    pub fn subscription_error<'a>(id: Id, err: impl Into<ErrorObject<'a>>) -> Self {
231        let mut rp = Self::error(id, err);
232        rp.kind = ResponseKind::Subscription;
233        rp
234    }
235
236    /// Create a [`MethodResponse`] from a JSON-RPC error.
237    pub fn error<'a>(id: Id, err: impl Into<ErrorObject<'a>>) -> Self {
238        let err: ErrorObject = err.into();
239        let err_code = err.code();
240        let err = ResponsePayload::<()>::error_borrowed(err);
241        let json = serde_json::to_string(&Response::new(err, id))
242            .expect("JSON serialization infallible; qed");
243        Self {
244            json,
245            success_or_error: MethodResponseResult::Failed(err_code),
246            kind: ResponseKind::MethodCall,
247        }
248    }
249
250    /// Create notification response which is a response that doesn't expect a reply.
251    pub fn notification() -> Self {
252        Self {
253            json: String::from("null"),
254            success_or_error: MethodResponseResult::Success,
255            kind: ResponseKind::Notification,
256        }
257    }
258}
259
260/// Represent the outcome of a method call success or failed.
261#[derive(Debug, Copy, Clone)]
262enum MethodResponseResult {
263    /// The method call was successful.
264    Success,
265    /// The method call failed with error code.
266    Failed(i32),
267}
268
269impl MethodResponseResult {
270    /// Returns whether the call was successful.
271    fn is_success(&self) -> bool {
272        matches!(self, MethodResponseResult::Success)
273    }
274
275    /// Returns whether the call failed.
276    fn is_error(&self) -> bool {
277        matches!(self, MethodResponseResult::Failed(_))
278    }
279
280    /// Get the error code
281    ///
282    /// Returns `Some(error code)` if the call failed.
283    fn as_error_code(&self) -> Option<i32> {
284        match self {
285            Self::Failed(e) => Some(*e),
286            _ => None,
287        }
288    }
289}
290
291/// Builder for the response to a batch request.
292#[derive(Debug)]
293pub struct BatchResponseBuilder {
294    /// Serialized JSON-RPC response,
295    result: String,
296    /// Max limit for the batch
297    max_response_size: usize,
298}
299
300impl BatchResponseBuilder {
301    /// Create a new batch response builder with limit.
302    pub fn new_with_limit(limit: usize) -> Self {
303        let mut initial = String::with_capacity(2048);
304        initial.push('[');
305
306        Self { result: initial, max_response_size: limit }
307    }
308
309    /// Append a result from an individual method to the batch response.
310    ///
311    /// Fails if the max limit is exceeded and returns to error response to
312    /// return early in order to not process method call responses which are thrown away anyway.
313    pub fn append(&mut self, response: MethodResponse) -> Result<(), MethodResponse> {
314        // `,` will occupy one extra byte for each entry
315        // on the last item the `,` is replaced by `]`.
316        let len = response.json.len() + self.result.len() + 1;
317
318        if len > self.max_response_size {
319            Err(MethodResponse::error(
320                Id::Null,
321                reject_too_big_batch_response(self.max_response_size),
322            ))
323        } else {
324            self.result.push_str(&response.json);
325            self.result.push(',');
326            Ok(())
327        }
328    }
329
330    /// Check if the batch is empty.
331    pub fn is_empty(&self) -> bool {
332        self.result.len() <= 1
333    }
334
335    /// Finish the batch response.
336    ///
337    /// An empty batch is answered with an invalid request error.
338    pub fn finish(mut self) -> MethodResponse {
339        if self.is_empty() {
340            return MethodResponse::error(Id::Null, ErrorObject::from(ErrorCode::InvalidRequest))
341        }
342        // Replace the trailing `,` of the last entry.
343        self.result.pop();
344        self.result.push(']');
345        MethodResponse {
346            json: self.result,
347            success_or_error: MethodResponseResult::Success,
348            kind: ResponseKind::Batch,
349        }
350    }
351}
352
353/// Size of a method's last response.
354///
355/// Responses of the same method tend to have similar sizes, so serializing into a buffer of the
356/// previous size avoids growing (and copying) large responses step by step.
357#[derive(Debug)]
358pub(crate) struct ResponseSizeHint(AtomicUsize);
359
360impl ResponseSizeHint {
361    pub(crate) const fn new() -> Self {
362        Self(AtomicUsize::new(128))
363    }
364
365    fn get(&self) -> usize {
366        self.0.load(Ordering::Relaxed)
367    }
368
369    fn record(&self, len: usize) {
370        self.0.store(len.min(MAX_RESPONSE_SIZE_HINT), Ordering::Relaxed);
371    }
372}
373
374/// Largest [`ResponseSizeHint`], so one huge response doesn't make every later call of the method,
375/// from any connection, reserve as much.
376const MAX_RESPONSE_SIZE_HINT: usize = 8 * 1024 * 1024;
377
378/// Bounded writer that allows writing at most `max_len` bytes.
379#[derive(Debug, Clone)]
380struct BoundedWriter {
381    max_len: usize,
382    buf: Vec<u8>,
383}
384
385impl BoundedWriter {
386    /// Create a new bounded writer with room for `capacity` bytes.
387    pub fn new(max_len: usize, capacity: usize) -> Self {
388        Self { max_len, buf: Vec::with_capacity(capacity.min(max_len)) }
389    }
390
391    /// Consume the writer and extract the written bytes.
392    pub fn into_bytes(self) -> Vec<u8> {
393        self.buf
394    }
395}
396
397// serde_json emits a response as many small writes, so both methods are inlined into the
398// serializer and only check the limit, instead of going through the default `write_all` loop.
399impl io::Write for &mut BoundedWriter {
400    #[inline]
401    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
402        self.write_all(buf)?;
403        Ok(buf.len())
404    }
405
406    #[inline]
407    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
408        if buf.len() > self.max_len - self.buf.len() {
409            return Err(limit_exceeded());
410        }
411        self.buf.extend_from_slice(buf);
412        Ok(())
413    }
414
415    #[inline]
416    fn flush(&mut self) -> io::Result<()> {
417        Ok(())
418    }
419}
420
421#[cold]
422fn limit_exceeded() -> io::Error {
423    io::Error::new(io::ErrorKind::OutOfMemory, "Memory capacity exceeded")
424}
425
426#[cfg(test)]
427mod tests {
428    use super::{
429        BatchResponseBuilder, BoundedWriter, Id, MethodResponse, ResponsePayload, ResponseSizeHint,
430        MAX_RESPONSE_SIZE_HINT,
431    };
432
433    #[test]
434    fn batch_with_single_works() {
435        let method = MethodResponse::response(
436            Id::Number(1),
437            ResponsePayload::success_borrowed(&"a"),
438            usize::MAX,
439        );
440        assert_eq!(method.json.len(), 37);
441
442        // Recall a batch appends two bytes for the `[]`.
443        let mut builder = BatchResponseBuilder::new_with_limit(39);
444        builder.append(method).unwrap();
445        let batch = builder.finish();
446
447        assert_eq!(batch.json, r#"[{"jsonrpc":"2.0","id":1,"result":"a"}]"#)
448    }
449
450    #[test]
451    fn batch_with_multiple_works() {
452        let m1 = MethodResponse::response(
453            Id::Number(1),
454            ResponsePayload::success_borrowed(&"a"),
455            usize::MAX,
456        );
457        let m11 = MethodResponse::response(
458            Id::Number(1),
459            ResponsePayload::success_borrowed(&"a"),
460            usize::MAX,
461        );
462        assert_eq!(m1.json.len(), 37);
463
464        // Recall a batch appends two bytes for the `[]` and one byte for `,` to append a method
465        // call. so it should be 2 + (37 * n) + (n-1)
466        let limit = 2 + (37 * 2) + 1;
467        let mut builder = BatchResponseBuilder::new_with_limit(limit);
468        builder.append(m1).unwrap();
469        builder.append(m11).unwrap();
470        let batch = builder.finish();
471
472        assert_eq!(
473            batch.json,
474            r#"[{"jsonrpc":"2.0","id":1,"result":"a"},{"jsonrpc":"2.0","id":1,"result":"a"}]"#
475        )
476    }
477
478    #[test]
479    fn batch_empty_err() {
480        let batch = BatchResponseBuilder::new_with_limit(1024).finish();
481
482        let exp_err =
483            r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}}"#;
484        assert_eq!(batch.json, exp_err);
485    }
486
487    #[test]
488    fn batch_too_big() {
489        let method = MethodResponse::response(
490            Id::Number(1),
491            ResponsePayload::success_borrowed(&"a".repeat(28)),
492            128,
493        );
494        assert_eq!(method.json.len(), 64);
495
496        let batch = BatchResponseBuilder::new_with_limit(63).append(method).unwrap_err();
497
498        let exp_err = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32011,"message":"The batch response was too large","data":"Exceeded max limit of 63"}}"#;
499        assert_eq!(batch.json, exp_err);
500    }
501
502    #[test]
503    fn bounded_serializer_work() {
504        use jsonrpsee_types::{Response, ResponsePayload};
505
506        let mut writer = BoundedWriter::new(100, 128);
507        let result = ResponsePayload::success(&"success");
508        let rp = &Response::new(result, Id::Number(1));
509
510        assert!(serde_json::to_writer(&mut writer, rp).is_ok());
511        assert_eq!(
512            String::from_utf8(writer.into_bytes()).unwrap(),
513            r#"{"jsonrpc":"2.0","id":1,"result":"success"}"#
514        );
515    }
516
517    #[test]
518    fn bounded_serializer_cap_works() {
519        let mut writer = BoundedWriter::new(100, 128);
520        // NOTE: `"` is part of the serialization so 101 characters.
521        assert!(serde_json::to_writer(&mut writer, &"x".repeat(99)).is_err());
522    }
523
524    #[test]
525    fn size_hint_is_capped_and_small_responses_shrink() {
526        let hint = ResponseSizeHint::new();
527        let big = "x".repeat(MAX_RESPONSE_SIZE_HINT);
528        let rp = MethodResponse::sized_response(
529            Id::Number(1),
530            ResponsePayload::success_borrowed(&big),
531            usize::MAX,
532            &hint,
533        );
534        assert!(rp.json.len() > MAX_RESPONSE_SIZE_HINT);
535        assert_eq!(hint.get(), MAX_RESPONSE_SIZE_HINT);
536
537        let rp = MethodResponse::sized_response(
538            Id::Number(1),
539            ResponsePayload::success_borrowed(&"a"),
540            usize::MAX,
541            &hint,
542        );
543        assert_eq!(rp.json, r#"{"jsonrpc":"2.0","id":1,"result":"a"}"#);
544        assert!(rp.json.capacity() < 2 * rp.json.len());
545        assert_eq!(hint.get(), rp.json.len());
546    }
547}