diff --git a/Cargo.toml b/Cargo.toml index b7aa3fc07..665aa1332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "ipc", "macros", "derive", - "minihttp", "pubsub", "pubsub/more-examples", "server-utils", diff --git a/README.md b/README.md index 74ffa2de7..224f2749b 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,12 @@ Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` ## Sub-projects - [jsonrpc-core](./core) [![crates.io][core-image]][core-url] - [jsonrpc-http-server](./http) [![crates.io][http-server-image]][http-server-url] -- [jsonrpc-minihttp-server](./minihttp) -- [jsonrpc-ipc-server](./ipc) +- [jsonrpc-ipc-server](./ipc) [![crates.io][ipc-server-image]][ipc-server-url] - [jsonrpc-tcp-server](./tcp) [![crates.io][tcp-server-image]][tcp-server-url] -- [jsonrpc-ws-server](./ws) -- [jsonrpc-stdio-server](./stdio) +- [jsonrpc-ws-server](./ws) [![crates.io][ws-server-image]][ws-server-url] +- [jsonrpc-stdio-server](./stdio) [![crates.io][stdio-server-image]][stdio-server-url] - [jsonrpc-macros](./macros) [![crates.io][macros-image]][macros-url] *deprecated:* use `derive` instead -- [jsonrpc-derive](./derive) +- [jsonrpc-derive](./derive) [![crates.io][derive-image]][derive-url] - [jsonrpc-server-utils](./server-utils) [![crates.io][server-utils-image]][server-utils-url] - [jsonrpc-pubsub](./pubsub) [![crates.io][pubsub-image]][pubsub-url] @@ -30,10 +29,18 @@ Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` [core-url]: https://crates.io/crates/jsonrpc-core [http-server-image]: https://img.shields.io/crates/v/jsonrpc-http-server.svg [http-server-url]: https://crates.io/crates/jsonrpc-http-server +[ipc-server-image]: https://img.shields.io/crates/v/jsonrpc-ipc-server.svg +[ipc-server-url]: https://crates.io/crates/jsonrpc-ipc-server [tcp-server-image]: https://img.shields.io/crates/v/jsonrpc-tcp-server.svg [tcp-server-url]: https://crates.io/crates/jsonrpc-tcp-server +[ws-server-image]: https://img.shields.io/crates/v/jsonrpc-ws-server.svg +[ws-server-url]: https://crates.io/crates/jsonrpc-ws-server +[stdio-server-image]: https://img.shields.io/crates/v/jsonrpc-stdio-server.svg +[stdio-server-url]: https://crates.io/crates/jsonrpc-stdio-server [macros-image]: https://img.shields.io/crates/v/jsonrpc-macros.svg [macros-url]: https://crates.io/crates/jsonrpc-macros +[derive-image]: https://img.shields.io/crates/v/jsonrpc-derive.svg +[derive-url]: https://crates.io/crates/jsonrpc-derive [server-utils-image]: https://img.shields.io/crates/v/jsonrpc-server-utils.svg [server-utils-url]: https://crates.io/crates/jsonrpc-server-utils [pubsub-image]: https://img.shields.io/crates/v/jsonrpc-pubsub.svg @@ -49,11 +56,8 @@ Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` ### Basic Usage (with HTTP transport) ```rust -extern crate jsonrpc_core; -extern crate jsonrpc_minihttp_server; - use jsonrpc_core::{IoHandler, Value, Params}; -use jsonrpc_minihttp_server::{ServerBuilder}; +use jsonrpc_http_server::{ServerBuilder}; fn main() { let mut io = IoHandler::new(); diff --git a/http/README.md b/http/README.md index 40c62df05..d2da0d720 100644 --- a/http/README.md +++ b/http/README.md @@ -9,7 +9,7 @@ Rust http server using JSON-RPC 2.0. ``` [dependencies] -jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-http-server = "10.0" ``` `main.rs` diff --git a/ipc/README.md b/ipc/README.md index 14552d40e..7effc341c 100644 --- a/ipc/README.md +++ b/ipc/README.md @@ -9,7 +9,7 @@ IPC server (Windows & Linux) for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-ipc-server = "10.0" ``` `main.rs` diff --git a/minihttp/Cargo.toml b/minihttp/Cargo.toml deleted file mode 100644 index 4249bff52..000000000 --- a/minihttp/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -description = "Blazing fast http server for JSON-RPC 2.0." -homepage = "https://github.com/paritytech/jsonrpc" -repository = "https://github.com/paritytech/jsonrpc" -license = "MIT" -name = "jsonrpc-minihttp-server" -version = "10.0.0" -authors = ["tomusdrw "] -keywords = ["jsonrpc", "json-rpc", "json", "rpc", "server"] -documentation = "https://paritytech.github.io/jsonrpc/jsonrpc_minihttp_server/index.html" - -[dependencies] -bytes = "0.4" -jsonrpc-core = { version = "10.0", path = "../core" } -jsonrpc-server-utils = { version = "10.0", path = "../server-utils" } -log = "0.4" -parking_lot = "0.7" -tokio-minihttp = { git = "https://github.com/tomusdrw/tokio-minihttp" } -tokio-proto = { git = "https://github.com/tomusdrw/tokio-proto" } -tokio-service = "0.1" - -[dev-dependencies] -env_logger = "0.6" -reqwest = "0.6" - -[badges] -travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/minihttp/README.md b/minihttp/README.md deleted file mode 100644 index fa692b078..000000000 --- a/minihttp/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# jsonrpc-minihttp-server -Blazing fast HTTP server for JSON-RPC 2.0. - -[Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_http_server/index.html) - -## Example - -`Cargo.toml` - -``` -[dependencies] -jsonrpc-minihttp-server = { git = "https://github.com/paritytech/jsonrpc" } -``` - -`main.rs` - -```rust -extern crate jsonrpc_minihttp_server; - -use jsonrpc_minihttp_server::*; -use jsonrpc_minihttp_server::jsonrpc_core::*; -use jsonrpc_minihttp_server::cors::AccessControlAllowOrigin; - -fn main() { - let mut io = IoHandler::default(); - io.add_method("say_hello", |_| { - Ok(Value::String("hello".into())) - }); - - let server = ServerBuilder::new(io) - .cors(DomainsValidation::AllowOnly(vec![AccessControlAllowOrigin::Null])) - .start_http(&"127.0.0.1:3030".parse().unwrap()) - .expect("Unable to start RPC server"); - - server.wait().unwrap(); -} -``` diff --git a/minihttp/examples/http_async.rs b/minihttp/examples/http_async.rs deleted file mode 100644 index 1fbe52e48..000000000 --- a/minihttp/examples/http_async.rs +++ /dev/null @@ -1,19 +0,0 @@ -extern crate jsonrpc_minihttp_server; - -use jsonrpc_minihttp_server::{cors, ServerBuilder, DomainsValidation}; -use jsonrpc_minihttp_server::jsonrpc_core::*; - -fn main() { - let mut io = IoHandler::default(); - io.add_method("say_hello", |_params| { - futures::finished(Value::String("hello".to_owned())) - }); - - let server = ServerBuilder::new(io) - .cors(DomainsValidation::AllowOnly(vec![cors::AccessControlAllowOrigin::Null])) - .start_http(&"127.0.0.1:3030".parse().unwrap()) - .expect("Unable to start RPC server"); - - server.wait().unwrap(); -} - diff --git a/minihttp/examples/http_meta.rs b/minihttp/examples/http_meta.rs deleted file mode 100644 index 630c9fb11..000000000 --- a/minihttp/examples/http_meta.rs +++ /dev/null @@ -1,26 +0,0 @@ -extern crate jsonrpc_minihttp_server; - -use jsonrpc_minihttp_server::{cors, ServerBuilder, DomainsValidation, Req}; -use jsonrpc_minihttp_server::jsonrpc_core::*; - -#[derive(Clone, Default)] -struct Meta(usize); -impl Metadata for Meta {} - -fn main() { - let mut io = MetaIoHandler::default(); - io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| { - futures::finished(Value::String(format!("hello: {}", meta.0))) - }); - - let server = ServerBuilder::new(io) - .meta_extractor(|req: &Req| { - Meta(req.header("Origin").map(|v| v.len()).unwrap_or_default()) - }) - .cors(DomainsValidation::AllowOnly(vec![cors::AccessControlAllowOrigin::Null])) - .start_http(&"127.0.0.1:3030".parse().unwrap()) - .expect("Unable to start RPC server"); - - server.wait().unwrap(); -} - diff --git a/minihttp/examples/server.rs b/minihttp/examples/server.rs deleted file mode 100644 index 63a3e6065..000000000 --- a/minihttp/examples/server.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate jsonrpc_minihttp_server; - -use jsonrpc_minihttp_server::{cors, ServerBuilder, DomainsValidation}; -use jsonrpc_minihttp_server::jsonrpc_core::*; - -fn main() { - let mut io = IoHandler::default(); - io.add_method("say_hello", |_params: Params| { - Ok(Value::String("hello".to_string())) - }); - - let server = ServerBuilder::new(io) - .threads(3) - .cors(DomainsValidation::AllowOnly(vec![cors::AccessControlAllowOrigin::Null])) - .start_http(&"127.0.0.1:3030".parse().unwrap()) - .expect("Unable to start RPC server"); - - server.wait().unwrap(); -} - diff --git a/minihttp/src/lib.rs b/minihttp/src/lib.rs deleted file mode 100644 index 9f7920fd6..000000000 --- a/minihttp/src/lib.rs +++ /dev/null @@ -1,358 +0,0 @@ -//! jsonrpc http server. -//! -//! ```no_run -//! extern crate jsonrpc_core; -//! extern crate jsonrpc_minihttp_server; -//! -//! use jsonrpc_core::*; -//! use jsonrpc_minihttp_server::*; -//! -//! fn main() { -//! let mut io = IoHandler::new(); -//! io.add_method("say_hello", |_: Params| { -//! Ok(Value::String("hello".to_string())) -//! }); -//! -//! let _server = ServerBuilder::new(io).start_http(&"127.0.0.1:3030".parse().unwrap()); -//! } -//! ``` - -#![warn(missing_docs)] - -extern crate bytes; -extern crate jsonrpc_server_utils; -extern crate parking_lot; -extern crate tokio_proto; -extern crate tokio_service; -extern crate tokio_minihttp; - -pub extern crate jsonrpc_core; - -#[macro_use] -extern crate log; - -mod req; -mod res; -#[cfg(test)] -mod tests; - -use std::io; -use std::sync::{Arc, mpsc}; -use std::net::SocketAddr; -use std::thread; -use parking_lot::RwLock; -use jsonrpc_core as jsonrpc; -use jsonrpc::futures::{self, future, Future}; -use jsonrpc::{FutureResult, MetaIoHandler, Response, Output}; -use jsonrpc_server_utils::hosts; - -pub use jsonrpc_server_utils::cors; -pub use jsonrpc_server_utils::hosts::{Host, DomainsValidation}; -pub use req::Req; - -/// Extracts metadata from the HTTP request. -pub trait MetaExtractor: Sync + Send + 'static { - /// Read the metadata from the request - fn read_metadata(&self, _: &req::Req) -> M; -} - -impl MetaExtractor for F where - M: jsonrpc::Metadata, - F: Fn(&req::Req) -> M + Sync + Send + 'static, -{ - fn read_metadata(&self, req: &req::Req) -> M { - (*self)(req) - } -} - -#[derive(Default)] -struct NoopExtractor; -impl MetaExtractor for NoopExtractor { - fn read_metadata(&self, _: &req::Req) -> M { - M::default() - } -} - -/// Convenient JSON-RPC HTTP Server builder. -pub struct ServerBuilder = jsonrpc::middleware::Noop> { - jsonrpc_handler: Arc>, - meta_extractor: Arc>, - cors_domains: Option>, - allowed_hosts: Option>, - threads: usize, -} - -const SENDER_PROOF: &'static str = "Server initialization awaits local address."; - -impl> ServerBuilder { - /// Creates new `ServerBuilder` for given `IoHandler`. - /// - /// By default: - /// 1. Server is not sending any CORS headers. - /// 2. Server is validating `Host` header. - pub fn new(handler: T) -> Self where T: Into> { - Self::with_meta_extractor(handler, NoopExtractor) - } -} - -impl> ServerBuilder { - /// Creates new `ServerBuilder` for given `IoHandler` and meta extractor. - /// - /// By default: - /// 1. Server is not sending any CORS headers. - /// 2. Server is validating `Host` header. - pub fn with_meta_extractor(handler: T, extractor: E) -> Self where - T: Into>, - E: MetaExtractor, - { - ServerBuilder { - jsonrpc_handler: Arc::new(handler.into()), - meta_extractor: Arc::new(extractor), - cors_domains: None, - allowed_hosts: None, - threads: 1, - } - } - - /// Sets number of threads of the server to run. (not available for windows) - /// Panics when set to `0`. - pub fn threads(mut self, threads: usize) -> Self { - assert!(threads > 0); - self.threads = threads; - self - } - - /// Configures a list of allowed CORS origins. - pub fn cors(mut self, cors_domains: DomainsValidation) -> Self { - self.cors_domains = cors_domains.into(); - self - } - - /// Configures metadata extractor - pub fn meta_extractor>(mut self, extractor: T) -> Self { - self.meta_extractor = Arc::new(extractor); - self - } - - /// Allow connections only with `Host` header set to binding address. - pub fn allow_only_bind_host(mut self) -> Self { - self.allowed_hosts = Some(Vec::new()); - self - } - - /// Specify a list of valid `Host` headers. Binding address is allowed automatically. - pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation) -> Self { - self.allowed_hosts = allowed_hosts.into(); - self - } - - /// Start this JSON-RPC HTTP server trying to bind to specified `SocketAddr`. - pub fn start_http(self, addr: &SocketAddr) -> io::Result { - let cors_domains = self.cors_domains; - let allowed_hosts = self.allowed_hosts; - let handler = self.jsonrpc_handler; - let meta_extractor = self.meta_extractor; - let threads = self.threads; - - let (local_addr_tx, local_addr_rx) = mpsc::channel(); - let (close, shutdown_signal) = futures::sync::oneshot::channel(); - let addr = addr.to_owned(); - let handle = thread::spawn(move || { - let run = move || { - let hosts = Arc::new(RwLock::new(allowed_hosts.clone())); - let hosts2 = hosts.clone(); - let mut hosts_setter = hosts2.write(); - - let mut server = tokio_proto::TcpServer::new(tokio_minihttp::Http, addr); - server.threads(threads); - let server = server.bind(move || Ok(RpcService { - handler: handler.clone(), - meta_extractor: meta_extractor.clone(), - hosts: hosts.read().clone(), - cors_domains: cors_domains.clone(), - }))?; - - let local_addr = server.local_addr()?; - // Add current host to allowed headers. - // NOTE: we need to use `local_address` instead of `addr` - // it might be different! - *hosts_setter = hosts::update(allowed_hosts, &local_addr); - - Ok((server, local_addr)) - }; - - match run() { - Ok((server, local_addr)) => { - // Send local address - local_addr_tx.send(Ok(local_addr)).expect(SENDER_PROOF); - - // Start the server and wait for shutdown signal - server.run_until(shutdown_signal.map_err(|_| { - warn!("Shutdown signaller dropped, closing server."); - })).expect("Expected clean shutdown.") - }, - Err(err) => { - // Send error - local_addr_tx.send(Err(err)).expect(SENDER_PROOF); - } - } - }); - - // Wait for server initialization - let local_addr: io::Result = local_addr_rx.recv().map_err(|_| { - io::Error::new(io::ErrorKind::Interrupted, "") - })?; - - Ok(Server { - address: local_addr?, - handle: Some(handle), - close: Some(close), - }) - } -} - -/// Tokio-proto JSON-RPC HTTP Service -pub struct RpcService> { - handler: Arc>, - meta_extractor: Arc>, - hosts: Option>, - cors_domains: Option>, -} - -fn is_json(content_type: Option<&str>) -> bool { - match content_type { - None => false, - Some(ref content_type) => { - let json = "application/json"; - content_type.eq_ignore_ascii_case(json) - } - } -} - -impl> tokio_service::Service for RpcService { - type Request = tokio_minihttp::Request; - type Response = tokio_minihttp::Response; - type Error = io::Error; - type Future = future::Either< - future::FutureResult, - RpcResponse, - >; - - fn call(&self, request: Self::Request) -> Self::Future { - use self::future::Either; - - let request = req::Req::new(request); - // Validate HTTP Method - let is_options = request.method() == req::Method::Options; - if !is_options && request.method() != req::Method::Post { - return Either::A(future::ok( - res::method_not_allowed() - )); - } - - // Validate allowed hosts - let host = request.header("Host"); - if !hosts::is_host_valid(host.clone(), &self.hosts) { - return Either::A(future::ok( - res::invalid_host() - )); - } - - // Extract CORS headers - let origin = request.header("Origin"); - let cors = cors::get_cors_allow_origin(origin, host, &self.cors_domains); - - // Validate cors header - if let cors::AllowCors::Invalid = cors { - return Either::A(future::ok( - res::invalid_allow_origin() - )); - } - - // Don't process data if it's OPTIONS - if is_options { - return Either::A(future::ok( - res::options(cors.into()) - )); - } - - // Validate content type - let content_type = request.header("Content-type"); - if !is_json(content_type) { - return Either::A(future::ok( - res::invalid_content_type() - )); - } - - // Extract metadata - let metadata = self.meta_extractor.read_metadata(&request); - - // Read & handle request - let data = request.body(); - let future = self.handler.handle_request(data, metadata); - Either::B(RpcResponse { - future: future, - cors: cors.into(), - }) - } -} - -/// RPC response wrapper -pub struct RpcResponse where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ - future: FutureResult, - cors: Option, -} - -impl Future for RpcResponse where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ - type Item = tokio_minihttp::Response; - type Error = io::Error; - - fn poll(&mut self) -> futures::Poll { - use self::futures::Async::*; - - match self.future.poll() { - Err(_) => Ok(Ready(res::internal_error())), - Ok(NotReady) => Ok(NotReady), - Ok(Ready(result)) => { - let result = format!("{}\n", result.unwrap_or_default()); - Ok(Ready(res::new(&result, self.cors.take()))) - }, - } - } -} - -/// jsonrpc http server instance -pub struct Server { - address: SocketAddr, - handle: Option>, - close: Option>, -} - -impl Server { - /// Returns addresses of this server - pub fn address(&self) -> &SocketAddr { - &self.address - } - - /// Closes the server. - pub fn close(mut self) { - let _ = self.close.take().expect("Close is always set before self is consumed.").send(()); - } - - /// Will block, waiting for the server to finish. - pub fn wait(mut self) -> thread::Result<()> { - self.handle.take().expect("Handle is always set before set is consumed.").join() - } -} - -impl Drop for Server { - fn drop(&mut self) { - self.close.take().map(|close| close.send(())); - } -} diff --git a/minihttp/src/req.rs b/minihttp/src/req.rs deleted file mode 100644 index 5405226f2..000000000 --- a/minihttp/src/req.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Convenient Request wrapper used internally. - -use tokio_minihttp; -use bytes::Bytes; - -/// HTTP Method used -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Method { - /// POST - Post, - /// OPTIONS - Options, - /// Other method - Other -} - -/// Request -pub struct Req { - request: tokio_minihttp::Request, - body: Bytes, -} - -impl Req { - /// Creates new `Req` object - pub fn new(request: tokio_minihttp::Request) -> Self { - let body = request.body(); - Req { - request: request, - body: body, - } - } - - /// Returns request method - pub fn method(&self) -> Method { - // RFC 2616: The method is case-sensitive - match self.request.method() { - "OPTIONS" => Method::Options, - "POST" => Method::Post, - _ => Method::Other, - } - } - - /// Returns value of first header with given name. - /// `None` if header is not found or value is not utf-8 encoded - pub fn header(&self, name: &str) -> Option<&str> { - self.request.headers() - .find(|header| header.0.eq_ignore_ascii_case(name)) - .and_then(|header| ::std::str::from_utf8(header.1).ok()) - } - - /// Returns body of the request as a string - pub fn body(&self) -> &str { - ::std::str::from_utf8(&self.body).unwrap_or("") - } -} diff --git a/minihttp/src/res.rs b/minihttp/src/res.rs deleted file mode 100644 index 6eb61052c..000000000 --- a/minihttp/src/res.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Convenient Response utils used internally - -use tokio_minihttp::Response; -use jsonrpc_server_utils::cors; - -const SERVER: &'static str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); - -pub fn options(cors: Option) -> Response { - let mut response = new("", cors); - response - .header("Allow", "OPTIONS, POST") - .header("Accept", "application/json"); - response -} - -pub fn method_not_allowed() -> Response { - let mut response = Response::new(); - response - .status_code(405, "Method Not Allowed") - .server(SERVER) - .header("Content-Type", "text/plain") - .body("Used HTTP Method is not allowed. POST or OPTIONS is required.\n"); - response -} - -pub fn invalid_host() -> Response { - let mut response = Response::new(); - response - .status_code(403, "Forbidden") - .server(SERVER) - .header("Content-Type", "text/plain") - .body("Provided Host header is not whitelisted.\n"); - response -} - -pub fn internal_error() -> Response { - let mut response = Response::new(); - response - .status_code(500, "Internal Error") - .server(SERVER) - .header("Content-Type", "text/plain") - .body("Interal Server Error has occured."); - response -} - -pub fn invalid_allow_origin() -> Response { - let mut response = Response::new(); - response - .status_code(403, "Forbidden") - .server(SERVER) - .header("Content-Type", "text/plain") - .body("Origin of the request is not whitelisted. CORS headers would not be sent and any side-effects were cancelled as well.\n"); - response -} - -pub fn invalid_content_type() -> Response { - let mut response = Response::new(); - response - .status_code(415, "Unsupported Media Type") - .server(SERVER) - .header("Content-Type", "text/plain") - .body("Supplied content type is not allowed. Content-Type: application/json is required.\n"); - response -} - -pub fn new(body: &str, cors: Option) -> Response { - let mut response = Response::new(); - response - .header("Content-Type", "application/json") - .server(SERVER) - .body(body); - - if let Some(cors) = cors { - response - .header("Access-Control-Allow-Methods", "OPTIONS, POST") - .header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept") - .header("Access-Control-Allow-Origin", match cors { - cors::AccessControlAllowOrigin::Null => "null", - cors::AccessControlAllowOrigin::Any => "*", - cors::AccessControlAllowOrigin::Value(ref val) => val, - }) - .header("Vary", "Origin"); - } - response -} diff --git a/minihttp/src/tests.rs b/minihttp/src/tests.rs deleted file mode 100644 index 31f954396..000000000 --- a/minihttp/src/tests.rs +++ /dev/null @@ -1,497 +0,0 @@ -extern crate jsonrpc_core; -extern crate env_logger; -extern crate reqwest; - -use std::io::Read; -use self::reqwest::{StatusCode, Method}; -use self::reqwest::header::{self, Headers}; -use self::jsonrpc_core::{IoHandler, Params, Value, Error}; -use self::jsonrpc_core::futures::{self, Future}; -use super::{ServerBuilder, Server, cors, hosts}; - -fn serve_hosts(hosts: Vec) -> Server { - let _ = env_logger::try_init(); - - ServerBuilder::new(IoHandler::default()) - .cors(hosts::DomainsValidation::AllowOnly(vec![cors::AccessControlAllowOrigin::Value("http://parity.io".into())])) - .allowed_hosts(hosts::DomainsValidation::AllowOnly(hosts)) - .start_http(&"127.0.0.1:0".parse().unwrap()) - .unwrap() -} - -fn serve() -> Server { - use std::thread; - - let _ = env_logger::try_init(); - let mut io = IoHandler::default(); - io.add_method("hello", |_params: Params| Ok(Value::String("world".into()))); - io.add_method("hello_async", |_params: Params| { - futures::finished(Value::String("world".into())) - }); - io.add_method("hello_async2", |_params: Params| { - let (c, p) = futures::oneshot(); - thread::spawn(move || { - thread::sleep(::std::time::Duration::from_millis(10)); - c.send(Value::String("world".into())).unwrap(); - }); - p.map_err(|_| Error::invalid_request()) - }); - - ServerBuilder::new(io) - .cors(hosts::DomainsValidation::AllowOnly(vec![ - cors::AccessControlAllowOrigin::Value("http://parity.io".into()), - cors::AccessControlAllowOrigin::Null, - ])) - .start_http(&"127.0.0.1:0".parse().unwrap()) - .unwrap() -} - -struct Response { - pub status: reqwest::StatusCode, - pub body: String, - pub headers: Headers, -} - -fn request(server: Server, method: Method, headers: Headers, body: &'static str) -> Response { - let client = reqwest::Client::new().unwrap(); - let mut res = client.request(method, &format!("http://{}", server.address())) - .headers(headers) - .body(body) - .send() - .unwrap(); - - let mut body = String::new(); - res.read_to_string(&mut body).unwrap(); - - Response { - status: res.status().clone(), - body: body, - headers: res.headers().clone(), - } -} - -#[test] -fn should_return_method_not_allowed_for_get() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Get, - Headers::new(), - "I shouldn't be read.", - ); - - // then - assert_eq!(response.status, StatusCode::MethodNotAllowed); - assert_eq!(response.body, "Used HTTP Method is not allowed. POST or OPTIONS is required.\n".to_owned()); -} - -#[test] -fn should_ignore_media_type_if_options() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Options, - Headers::new(), - "", - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!( - response.headers.get::(), - Some(&reqwest::header::Allow(vec![Method::Options, Method::Post])) - ); - assert!(response.headers.get::().is_some()); - assert_eq!(response.body, ""); -} - - -#[test] -fn should_return_403_for_options_if_origin_is_invalid() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.append_raw("origin", b"http://invalid.io".to_vec()); - headers - }, - "" - ); - - // then - assert_eq!(response.status, StatusCode::Forbidden); - assert_eq!(response.body, cors_invalid_allow_origin()); -} - -#[test] -fn should_return_unsupported_media_type_if_not_json() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - Headers::new(), - "{}", - ); - - // then - assert_eq!(response.status, StatusCode::UnsupportedMediaType); - assert_eq!(response.body, "Supplied content type is not allowed. Content-Type: application/json is required.\n".to_owned()); -} - -fn content_type_json() -> Headers { - let mut headers = Headers::new(); - headers.set_raw("content-type", vec![b"application/json".to_vec()]); - headers -} - -#[test] -fn should_return_error_for_malformed_request() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"3.0","method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, invalid_request()); -} - -#[test] -fn should_return_error_for_malformed_request2() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","metho1d":""}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, invalid_request()); -} - -#[test] -fn should_return_empty_response_for_notification() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, "\n".to_owned()); -} - - -#[test] -fn should_return_method_not_found() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); -} - -#[test] -fn should_add_cors_allow_origins() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set(header::Origin::new("http", "parity.io", None)); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); - assert_eq!( - response.headers.get::(), - Some(&reqwest::header::AccessControlAllowOrigin::Value("http://parity.io".into())) - ); -} - -#[test] -fn should_add_cors_allow_origins_for_options() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Options, - { - let mut headers = content_type_json(); - headers.set(header::Origin::new("http", "parity.io", None)); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, "".to_owned()); - println!("{:?}", response.headers); - assert_eq!( - response.headers.get::(), - Some(&reqwest::header::AccessControlAllowOrigin::Value("http://parity.io".into())) - ); -} - -#[test] -fn should_not_process_request_with_invalid_allow_origin() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set(header::Origin::new("http", "fake.io", None)); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Forbidden); - assert_eq!(response.body, cors_invalid_allow_origin()); -} - -#[test] -fn should_add_cors_allow_origin_for_null_origin() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.append_raw("origin", b"null".to_vec()); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); - assert_eq!( - response.headers.get::().cloned(), - Some(reqwest::header::AccessControlAllowOrigin::Null) - ); -} - -#[test] -fn should_reject_invalid_hosts() { - // given - let server = serve_hosts(vec!["parity.io".into()]); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set_raw("Host", vec![b"127.0.0.1:8080".to_vec()]); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Forbidden); - assert_eq!(response.body, invalid_host()); -} - -#[test] -fn should_allow_if_host_is_valid() { - // given - let server = serve_hosts(vec!["parity.io".into()]); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set_raw("Host", vec![b"parity.io".to_vec()]); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); -} - -#[test] -fn should_always_allow_the_bind_address() { - // given - let server = serve_hosts(vec!["parity.io".into()]); - let addr = server.address().clone(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set_raw("Host", vec![format!("{}", addr).as_bytes().to_vec()]); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); -} - -#[test] -fn should_always_allow_the_bind_address_as_localhost() { - // given - let server = serve_hosts(vec![]); - let addr = server.address().clone(); - - // when - let response = request(server, - Method::Post, - { - let mut headers = content_type_json(); - headers.set_raw("Host", vec![format!("localhost:{}", addr.port()).as_bytes().to_vec()]); - headers - }, - r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, method_not_found()); -} - -#[test] -fn should_handle_sync_requests_correctly() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","id":1,"method":"hello"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, world()); -} - -#[test] -fn should_handle_async_requests_with_immediate_response_correctly() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","id":1,"method":"hello_async"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, world()); -} - -#[test] -fn should_handle_async_requests_correctly() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"{"jsonrpc":"2.0","id":1,"method":"hello_async2"}"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, world()); -} - -#[test] -fn should_handle_sync_batch_requests_correctly() { - // given - let server = serve(); - - // when - let response = request(server, - Method::Post, - content_type_json(), - r#"[{"jsonrpc":"2.0","id":1,"method":"hello"}]"#, - ); - - // then - assert_eq!(response.status, StatusCode::Ok); - assert_eq!(response.body, world_batch()); -} - -fn invalid_host() -> String { - "Provided Host header is not whitelisted.\n".into() -} - -fn cors_invalid_allow_origin() -> String { - "Origin of the request is not whitelisted. CORS headers would not be sent and any side-effects were cancelled as well.\n".into() -} - -fn method_not_found() -> String { - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":1}\n".into() -} - -fn invalid_request() -> String { - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":null}\n".into() -} -fn world() -> String { - "{\"jsonrpc\":\"2.0\",\"result\":\"world\",\"id\":1}\n".into() -} -fn world_batch() -> String { - "[{\"jsonrpc\":\"2.0\",\"result\":\"world\",\"id\":1}]\n".into() -} diff --git a/stdio/README.md b/stdio/README.md index f4497901f..15c4054a2 100644 --- a/stdio/README.md +++ b/stdio/README.md @@ -10,7 +10,7 @@ Takes one request per line and outputs each response on a new line. ``` [dependencies] -jsonrpc-stdio-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-stdio-server = "10.0" ``` `main.rs` diff --git a/tcp/README.md b/tcp/README.md index e850da952..83edada87 100644 --- a/tcp/README.md +++ b/tcp/README.md @@ -9,7 +9,7 @@ TCP server for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-tcp-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-tcp-server = "10.0" ``` `main.rs` diff --git a/ws/Cargo.toml b/ws/Cargo.toml index 7d5f53737..7f0251e49 100644 --- a/ws/Cargo.toml +++ b/ws/Cargo.toml @@ -15,7 +15,7 @@ jsonrpc-server-utils = { version = "10.0", path = "../server-utils" } log = "0.4" parking_lot = "0.7" slab = "0.4" -ws = { git = "https://github.com/tomusdrw/ws-rs" } +parity-ws = "0.8" [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/ws/README.md b/ws/README.md index b95124c44..d8fb5b111 100644 --- a/ws/README.md +++ b/ws/README.md @@ -9,7 +9,7 @@ WebSockets server for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-ws-server = "10.0" ``` `main.rs` diff --git a/ws/src/lib.rs b/ws/src/lib.rs index 905884cfd..1823ebce9 100644 --- a/ws/src/lib.rs +++ b/ws/src/lib.rs @@ -6,7 +6,7 @@ extern crate jsonrpc_server_utils as server_utils; extern crate parking_lot; extern crate slab; -pub extern crate ws; +pub extern crate parity_ws as ws; pub extern crate jsonrpc_core; #[macro_use] diff --git a/ws/src/server.rs b/ws/src/server.rs index 74a2f6435..e3d25441a 100644 --- a/ws/src/server.rs +++ b/ws/src/server.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{cmp, fmt}; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::thread; @@ -56,10 +56,10 @@ impl Server { let mut config = ws::Settings::default(); config.max_connections = max_connections; // don't accept super large requests - config.max_in_buffer = max_payload_bytes; + config.max_fragment_size = max_payload_bytes; // don't grow non-final fragments (to prevent DOS) config.fragments_grow = false; - config.fragments_capacity = max_payload_bytes / config.fragment_size; + config.fragments_capacity = cmp::max(1, max_payload_bytes / config.fragment_size); // accept only handshakes beginning with GET config.method_strict = true; // require masking