Skip to main content

jsonrpsee_server/
utils.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
27use std::{
28    borrow::Cow,
29    future::Future,
30    io,
31    pin::{pin, Pin},
32    sync::Arc,
33    task::{Context, Poll},
34    time::Duration,
35};
36
37use crate::{ConnectionState, HttpBody};
38
39use futures_util::{future, ready};
40use hyper::body::{Bytes, Frame};
41use hyper_util::rt::{TokioExecutor, TokioIo};
42use jsonrpsee_core::{middleware::Notification, BoxError, JsonRawValue};
43use jsonrpsee_types::TwoPointZero;
44use pin_project::pin_project;
45use serde::{Deserialize, Deserializer};
46use tokio::{
47    io::{AsyncRead, AsyncWrite, ReadBuf},
48    sync::Notify,
49    time::Sleep,
50};
51
52/// Largest data frame [`PermitBody`] yields.
53const BODY_CHUNK_SIZE: usize = 64 * 1024;
54
55/// Default for how long a connection may take to send its first request.
56pub(crate) const DEFAULT_FIRST_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
57
58/// How long an accept loop pauses after `accept` fails for a reason that outlasts the attempt.
59const ACCEPT_ERROR_BACKOFF: Duration = Duration::from_secs(1);
60
61/// Default timeout for writing a single WebSocket message and for a stalled HTTP response write,
62/// matching geth.
63pub(crate) const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(10);
64
65/// Notifies `request_received` whenever hyper passes a request to the service.
66#[derive(Debug, Clone)]
67pub(crate) struct NotifyOnRequest<S> {
68    service: S,
69    request_received: Arc<Notify>,
70}
71
72impl<S> NotifyOnRequest<S> {
73    pub(crate) fn new(service: S, request_received: Arc<Notify>) -> Self {
74        Self { service, request_received }
75    }
76}
77
78impl<S, R> hyper::service::Service<R> for NotifyOnRequest<S>
79where
80    S: hyper::service::Service<R>,
81{
82    type Response = S::Response;
83    type Error = S::Error;
84    type Future = S::Future;
85
86    fn call(&self, request: R) -> Self::Future {
87        self.request_received.notify_one();
88        self.service.call(request)
89    }
90}
91
92/// Resolves with an error if no request was received within `timeout`, otherwise never.
93pub(crate) async fn no_request_within(
94    request_received: &Notify,
95    timeout: Option<Duration>,
96) -> BoxError {
97    if let Some(timeout) = timeout &&
98        tokio::time::timeout(timeout, request_received.notified()).await.is_err()
99    {
100        return io::Error::new(io::ErrorKind::TimedOut, "no request received in time").into();
101    }
102
103    future::pending().await
104}
105
106/// Serve a service over a TCP connection with graceful shutdown.
107/// This means that pending requests will be completed before the server is stopped.
108///
109/// Connections which don't send a request within 30 seconds are closed.
110pub async fn serve_with_graceful_shutdown<S, B, I>(
111    io: I,
112    service: S,
113    stopped: impl Future<Output = ()>,
114) -> Result<(), BoxError>
115where
116    S: tower::Service<http::Request<hyper::body::Incoming>, Response = http::Response<B>>
117        + Clone
118        + Send
119        + 'static,
120    S::Future: Send,
121    S::Response: Send,
122    S::Error: Into<BoxError>,
123    B: http_body::Body<Data = hyper::body::Bytes> + Send + 'static,
124    B::Error: Into<BoxError>,
125    I: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin + 'static,
126{
127    let request_received = Arc::new(Notify::new());
128    let service = NotifyOnRequest::new(
129        hyper_util::service::TowerToHyperService::new(service),
130        request_received.clone(),
131    );
132    let io = TokioIo::new(io);
133
134    let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
135    let conn = builder.serve_connection_with_upgrades(io, service);
136
137    tokio::pin!(stopped, conn);
138
139    tokio::select! {
140        // Return if the connection was completed.
141        res = &mut conn => res,
142        // If the server is stopped, we should gracefully shutdown
143        // the connection and poll it until it finishes.
144        _ = &mut stopped => {
145            conn.as_mut().graceful_shutdown();
146            conn.await
147        }
148        err = no_request_within(&request_received, Some(DEFAULT_FIRST_REQUEST_TIMEOUT)) => Err(err),
149    }
150}
151
152/// Deserializes a JSON-RPC notification, rejecting objects that contain an `id` member.
153pub(crate) fn deserialize_notification(data: &[u8]) -> Result<Notification<'_>, serde_json::Error> {
154    let notif = serde_json::from_slice::<NotificationWithoutId<'_>>(data)?;
155    Ok(Notification::new(notif.method, notif.params.map(Cow::Borrowed)))
156}
157
158#[derive(Deserialize)]
159struct NotificationWithoutId<'a> {
160    #[serde(rename = "jsonrpc")]
161    _jsonrpc: TwoPointZero,
162    #[serde(borrow)]
163    method: Cow<'a, str>,
164    #[serde(borrow)]
165    params: Option<&'a JsonRawValue>,
166    #[serde(default, rename = "id", deserialize_with = "reject_notification_id")]
167    _id: (),
168}
169
170fn reject_notification_id<'de, D: Deserializer<'de>>(_: D) -> Result<(), D::Error> {
171    Err(serde::de::Error::custom("notification must not contain an id"))
172}
173
174/// Pauses an accept loop after `accept` failed with `err`.
175///
176/// Errors that concern only the connection being accepted (refused, aborted or reset) return at
177/// once. Any other error, such as running out of file descriptors, persists and would make the
178/// loop spin, so it is followed by a one second sleep.
179///
180/// Returns the `stopped` future to keep polling, or `None` if it resolved during the sleep and
181/// the loop must exit.
182pub async fn accept_error_backoff<S>(err: &io::Error, stopped: S) -> Option<S>
183where
184    S: Future + Unpin,
185{
186    if matches!(
187        err.kind(),
188        io::ErrorKind::ConnectionRefused |
189            io::ErrorKind::ConnectionAborted |
190            io::ErrorKind::ConnectionReset
191    ) {
192        return Some(stopped);
193    }
194
195    match future::select(pin!(tokio::time::sleep(ACCEPT_ERROR_BACKOFF)), stopped).await {
196        future::Either::Left((_, stopped)) => Some(stopped),
197        future::Either::Right(_) => None,
198    }
199}
200
201/// HTTP response body that holds the connection permit until the body is written or dropped.
202///
203/// hyper writes the body after the service has returned the response, so a permit dropped by the
204/// service would no longer limit the connections that are still sending a response.
205///
206/// hyper drops the body as soon as it has taken the last frame, even if it still has to write
207/// that frame to the socket. A response is therefore yielded in frames of at most
208/// [`BODY_CHUNK_SIZE`] bytes: hyper only takes the next frame while its write buffer has room, so
209/// the permit is held until all but the last few frames have been written.
210#[pin_project]
211pub(crate) struct PermitBody {
212    #[pin]
213    inner: HttpBody,
214    /// Rest of the data frame that is being yielded in chunks.
215    pending: Bytes,
216    _conn: ConnectionState,
217}
218
219impl PermitBody {
220    pub(crate) fn new(inner: HttpBody, conn: ConnectionState) -> Self {
221        Self { inner, pending: Bytes::new(), _conn: conn }
222    }
223}
224
225impl http_body::Body for PermitBody {
226    type Data = Bytes;
227    type Error = BoxError;
228
229    fn poll_frame(
230        self: Pin<&mut Self>,
231        cx: &mut Context<'_>,
232    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
233        let this = self.project();
234
235        if this.pending.is_empty() {
236            match ready!(this.inner.poll_frame(cx)) {
237                Some(Ok(frame)) => match frame.into_data() {
238                    Ok(data) => *this.pending = data,
239                    Err(frame) => return Poll::Ready(Some(Ok(frame))),
240                },
241                end_or_error => return Poll::Ready(end_or_error),
242            }
243        }
244
245        let chunk = this.pending.split_to(this.pending.len().min(BODY_CHUNK_SIZE));
246        Poll::Ready(Some(Ok(Frame::data(chunk))))
247    }
248
249    fn is_end_stream(&self) -> bool {
250        self.pending.is_empty() && self.inner.is_end_stream()
251    }
252
253    fn size_hint(&self) -> http_body::SizeHint {
254        let pending = self.pending.len() as u64;
255        let mut hint = self.inner.size_hint();
256        hint.set_lower(hint.lower() + pending);
257        if let Some(upper) = hint.upper() {
258            hint.set_upper(upper + pending);
259        }
260        hint
261    }
262}
263
264/// IO that fails a write with [`io::ErrorKind::TimedOut`] once the peer has accepted no bytes for
265/// the configured duration.
266///
267/// A peer that stops reading leaves hyper's buffered response, and the permit held by its body,
268/// pending forever otherwise.
269#[pin_project]
270pub(crate) struct WriteTimeoutIo<T> {
271    #[pin]
272    inner: T,
273    timeout: Duration,
274    /// Armed when a write returns `Pending` and cleared when a write makes progress.
275    stalled: Option<Pin<Box<Sleep>>>,
276}
277
278impl<T> WriteTimeoutIo<T> {
279    pub(crate) fn new(inner: T, timeout: Duration) -> Self {
280        Self { inner, timeout, stalled: None }
281    }
282}
283
284impl<T: AsyncRead> AsyncRead for WriteTimeoutIo<T> {
285    fn poll_read(
286        self: Pin<&mut Self>,
287        cx: &mut Context<'_>,
288        buf: &mut ReadBuf<'_>,
289    ) -> Poll<io::Result<()>> {
290        self.project().inner.poll_read(cx, buf)
291    }
292}
293
294impl<T: AsyncWrite> AsyncWrite for WriteTimeoutIo<T> {
295    fn poll_write(
296        self: Pin<&mut Self>,
297        cx: &mut Context<'_>,
298        buf: &[u8],
299    ) -> Poll<io::Result<usize>> {
300        let this = self.project();
301        let poll = this.inner.poll_write(cx, buf);
302        poll_write_timeout(this.stalled, *this.timeout, cx, poll)
303    }
304
305    fn poll_write_vectored(
306        self: Pin<&mut Self>,
307        cx: &mut Context<'_>,
308        bufs: &[io::IoSlice<'_>],
309    ) -> Poll<io::Result<usize>> {
310        let this = self.project();
311        let poll = this.inner.poll_write_vectored(cx, bufs);
312        poll_write_timeout(this.stalled, *this.timeout, cx, poll)
313    }
314
315    fn is_write_vectored(&self) -> bool {
316        self.inner.is_write_vectored()
317    }
318
319    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
320        self.project().inner.poll_flush(cx)
321    }
322
323    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
324        self.project().inner.poll_shutdown(cx)
325    }
326}
327
328/// Arms the stall timer when a write is `Pending`, so the task is woken when it fires, and clears
329/// it when the write makes progress.
330fn poll_write_timeout(
331    stalled: &mut Option<Pin<Box<Sleep>>>,
332    timeout: Duration,
333    cx: &mut Context<'_>,
334    poll: Poll<io::Result<usize>>,
335) -> Poll<io::Result<usize>> {
336    if poll.is_ready() {
337        *stalled = None;
338        return poll;
339    }
340
341    let stalled = stalled.get_or_insert_with(|| Box::pin(tokio::time::sleep(timeout)));
342    match stalled.as_mut().poll(cx) {
343        Poll::Ready(()) => Poll::Ready(Err(io::ErrorKind::TimedOut.into())),
344        Poll::Pending => Poll::Pending,
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351    use tokio::time::Instant;
352
353    #[test]
354    fn notification_id_classification_notification_metadata() {
355        for (raw, expected) in [
356            (
357                r#"{"jsonrpc":"2.0","method":"example","params":[1],"extra":{"id":true}}"#,
358                Some("[1]"),
359            ),
360            (r#"{"jsonrpc":"2.0","method":"example"}"#, None),
361        ] {
362            let notification = deserialize_notification(raw.as_bytes()).unwrap();
363            assert_eq!(notification.method_name(), "example");
364            assert_eq!(notification.params().as_ref().map(|p| p.get()), expected);
365            assert!(!matches!(notification.params(), Some(Cow::Owned(_))));
366        }
367        for id in ["true", "false", "{}", "[]", "null", "0", "\"valid\""] {
368            let raw = format!(r#"{{"jsonrpc":"2.0","method":"example","id":{id}}}"#);
369            assert!(deserialize_notification(raw.as_bytes()).is_err());
370        }
371    }
372
373    #[tokio::test(start_paused = true)]
374    async fn accept_error_backoff_skips_connection_errors() {
375        for kind in [
376            io::ErrorKind::ConnectionRefused,
377            io::ErrorKind::ConnectionAborted,
378            io::ErrorKind::ConnectionReset,
379        ] {
380            let start = Instant::now();
381            let stopped =
382                accept_error_backoff(&io::Error::from(kind), future::pending::<()>()).await;
383            assert!(stopped.is_some());
384            assert_eq!(start.elapsed(), Duration::ZERO);
385        }
386    }
387
388    #[tokio::test(start_paused = true)]
389    async fn accept_error_backoff_sleeps_on_persistent_errors() {
390        let start = Instant::now();
391        let err = io::Error::other("too many open files");
392        assert!(accept_error_backoff(&err, future::pending::<()>()).await.is_some());
393        assert_eq!(start.elapsed(), ACCEPT_ERROR_BACKOFF);
394    }
395
396    #[tokio::test(start_paused = true)]
397    async fn accept_error_backoff_observes_stop() {
398        let start = Instant::now();
399        let err = io::Error::other("out of descriptors");
400        assert!(accept_error_backoff(&err, future::ready(())).await.is_none());
401        assert_eq!(start.elapsed(), Duration::ZERO);
402    }
403}