Skip to content

Commit 0a22c00

Browse files
niklasad1lexnv
andauthored
server: make it possible to enable ws/http only (#939)
* server: make it possible to enable ws/http only * Update server/src/server.rs * Update server/src/server.rs * Update server/src/server.rs Co-authored-by: Alexandru Vasile <[email protected]> Co-authored-by: Alexandru Vasile <[email protected]>
1 parent 63e7124 commit 0a22c00

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

server/src/server.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ where
155155
conn_id: id,
156156
logger: logger.clone(),
157157
max_connections: self.cfg.max_connections,
158+
enable_http: self.cfg.enable_http,
159+
enable_ws: self.cfg.enable_ws,
158160
};
159161
process_connection(&self.service_builder, &connection_guard, data, socket, &mut connections);
160162
id = id.wrapping_add(1);
@@ -193,6 +195,10 @@ struct Settings {
193195
tokio_runtime: Option<tokio::runtime::Handle>,
194196
/// The interval at which `Ping` frames are submitted.
195197
ping_interval: Duration,
198+
/// Enable HTTP.
199+
enable_http: bool,
200+
/// Enable WS.
201+
enable_ws: bool,
196202
}
197203

198204
impl Default for Settings {
@@ -207,6 +213,8 @@ impl Default for Settings {
207213
allow_hosts: AllowHosts::Any,
208214
tokio_runtime: None,
209215
ping_interval: Duration::from_secs(60),
216+
enable_http: true,
217+
enable_ws: true,
210218
}
211219
}
212220
}
@@ -425,6 +433,26 @@ impl<B, L> Builder<B, L> {
425433
}
426434
}
427435

436+
/// Configure the server to only serve JSON-RPC HTTP requests.
437+
///
438+
/// Default: both http and ws are enabled.
439+
pub fn http_only(mut self) -> Self {
440+
self.settings.enable_http = true;
441+
self.settings.enable_ws = false;
442+
self
443+
}
444+
445+
/// Configure the server to only serve JSON-RPC WebSocket requests.
446+
///
447+
/// That implies that server just denies HTTP requests which isn't a WebSocket upgrade request
448+
///
449+
/// Default: both http and ws are enabled.
450+
pub fn ws_only(mut self) -> Self {
451+
self.settings.enable_http = false;
452+
self.settings.enable_ws = true;
453+
self
454+
}
455+
428456
/// Finalize the configuration of the server. Consumes the [`Builder`].
429457
///
430458
/// ```rust
@@ -547,6 +575,10 @@ pub(crate) struct ServiceData<L: Logger> {
547575
pub(crate) logger: L,
548576
/// Handle to hold a `connection permit`.
549577
pub(crate) conn: Arc<OwnedSemaphorePermit>,
578+
/// Enable HTTP.
579+
pub(crate) enable_http: bool,
580+
/// Enable WS.
581+
pub(crate) enable_ws: bool,
550582
}
551583

552584
/// JsonRPSee service compatible with `tower`.
@@ -589,7 +621,9 @@ impl<L: Logger> hyper::service::Service<hyper::Request<hyper::Body>> for TowerSe
589621
return async { Ok(http::response::host_not_allowed()) }.boxed();
590622
}
591623

592-
if is_upgrade_request(&request) {
624+
let is_upgrade_request = is_upgrade_request(&request);
625+
626+
if self.inner.enable_ws && is_upgrade_request {
593627
let mut server = soketto::handshake::http::Server::new();
594628

595629
let response = match server.receive_request(&request) {
@@ -626,7 +660,7 @@ impl<L: Logger> hyper::service::Service<hyper::Request<hyper::Body>> for TowerSe
626660
};
627661

628662
async { Ok(response) }.boxed()
629-
} else {
663+
} else if self.inner.enable_http && !is_upgrade_request {
630664
// The request wasn't an upgrade request; let's treat it as a standard HTTP request:
631665
let data = http::HandleRequest {
632666
methods: self.inner.methods.clone(),
@@ -643,6 +677,8 @@ impl<L: Logger> hyper::service::Service<hyper::Request<hyper::Body>> for TowerSe
643677
self.inner.logger.on_connect(self.inner.remote_addr, &request, TransportProtocol::Http);
644678

645679
Box::pin(http::handle_request(request, data).map(Ok))
680+
} else {
681+
Box::pin(async { http::response::denied() }.map(Ok))
646682
}
647683
}
648684
}
@@ -730,6 +766,10 @@ struct ProcessConnection<L> {
730766
conn_id: u32,
731767
/// Logger.
732768
logger: L,
769+
/// Allow JSON-RPC HTTP requests.
770+
enable_http: bool,
771+
/// Allow JSON-RPC WS request and WS upgrade requests.
772+
enable_ws: bool,
733773
}
734774

735775
#[instrument(name = "connection", skip_all, fields(remote_addr = %cfg.remote_addr, conn_id = %cfg.conn_id), level = "INFO")]
@@ -787,6 +827,8 @@ fn process_connection<'a, L: Logger, B, U>(
787827
conn_id: cfg.conn_id,
788828
logger: cfg.logger,
789829
conn: Arc::new(conn),
830+
enable_http: cfg.enable_http,
831+
enable_ws: cfg.enable_ws,
790832
},
791833
};
792834

server/src/tests/shared.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
use crate::tests::helpers::{init_logger, server_with_handles};
2+
use http::StatusCode;
23
use jsonrpsee_core::Error;
3-
use jsonrpsee_test_utils::TimeoutFutureExt;
4+
use jsonrpsee_test_utils::{
5+
helpers::{http_request, ok_response, to_http_uri},
6+
mocks::{Id, WebSocketTestClient, WebSocketTestError},
7+
TimeoutFutureExt,
8+
};
49
use std::time::Duration;
510

611
#[tokio::test]
@@ -35,3 +40,54 @@ async fn run_forever() {
3540
// Send the shutdown request from one handle and await the server on the second one.
3641
server_handle.stopped().with_timeout(TIMEOUT).await.unwrap();
3742
}
43+
44+
#[tokio::test]
45+
async fn http_only_works() {
46+
use crate::{RpcModule, ServerBuilder};
47+
48+
let server =
49+
ServerBuilder::default().http_only().build("127.0.0.1:0").with_default_timeout().await.unwrap().unwrap();
50+
let mut module = RpcModule::new(());
51+
module
52+
.register_method("say_hello", |_, _| {
53+
tracing::debug!("server respond to hello");
54+
Ok("hello")
55+
})
56+
.unwrap();
57+
58+
let addr = server.local_addr().unwrap();
59+
let _server_handle = server.start(module).unwrap();
60+
61+
let req = r#"{"jsonrpc":"2.0","method":"say_hello","id":1}"#;
62+
let response = http_request(req.into(), to_http_uri(addr)).with_default_timeout().await.unwrap().unwrap();
63+
assert_eq!(response.status, StatusCode::OK);
64+
assert_eq!(response.body, ok_response("hello".to_string().into(), Id::Num(1)));
65+
66+
let err = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap_err();
67+
assert!(matches!(err, WebSocketTestError::RejectedWithStatusCode(code) if code == 403));
68+
}
69+
70+
#[tokio::test]
71+
async fn ws_only_works() {
72+
use crate::{RpcModule, ServerBuilder};
73+
74+
let server = ServerBuilder::default().ws_only().build("127.0.0.1:0").with_default_timeout().await.unwrap().unwrap();
75+
let mut module = RpcModule::new(());
76+
module
77+
.register_method("say_hello", |_, _| {
78+
tracing::debug!("server respond to hello");
79+
Ok("hello")
80+
})
81+
.unwrap();
82+
83+
let addr = server.local_addr().unwrap();
84+
let _server_handle = server.start(module).unwrap();
85+
86+
let req = r#"{"jsonrpc":"2.0","method":"say_hello","id":1}"#;
87+
let response = http_request(req.into(), to_http_uri(addr)).with_default_timeout().await.unwrap().unwrap();
88+
assert_eq!(response.status, StatusCode::FORBIDDEN);
89+
90+
let mut client = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap();
91+
let response = client.send_request_text(req.to_string()).await.unwrap();
92+
assert_eq!(response, ok_response("hello".to_string().into(), Id::Num(1)));
93+
}

server/src/transport/http.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,9 @@ pub(crate) mod response {
426426
TEXT,
427427
)
428428
}
429+
430+
/// Create a response for when the server denied the request.
431+
pub(crate) fn denied() -> hyper::Response<hyper::Body> {
432+
from_template(hyper::StatusCode::FORBIDDEN, "".to_owned(), TEXT)
433+
}
429434
}

0 commit comments

Comments
 (0)