├── .editorconfig ├── .github └── workflows │ └── gitspiegel-trigger.yml ├── .gitignore ├── .gitlab-ci.yml ├── CODEOWNERS ├── Cargo.toml ├── LICENSE ├── README.md ├── _automate ├── bump_version.sh └── publish.sh ├── core-client ├── Cargo.toml ├── src │ └── lib.rs └── transports │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── logger.rs │ └── transports │ ├── duplex.rs │ ├── http.rs │ ├── ipc.rs │ ├── local.rs │ ├── mod.rs │ └── ws.rs ├── core ├── Cargo.toml ├── examples │ ├── async.rs │ ├── basic.rs │ ├── meta.rs │ ├── middlewares.rs │ └── params.rs └── src │ ├── calls.rs │ ├── delegates.rs │ ├── io.rs │ ├── lib.rs │ ├── middleware.rs │ └── types │ ├── error.rs │ ├── id.rs │ ├── mod.rs │ ├── params.rs │ ├── request.rs │ ├── response.rs │ └── version.rs ├── derive ├── Cargo.toml ├── examples │ ├── client-local.rs │ ├── generic-trait-bounds.rs │ ├── generic-trait.rs │ ├── meta-macros.rs │ ├── pubsub-macros.rs │ └── std.rs ├── src │ ├── lib.rs │ ├── options.rs │ ├── params_style.rs │ ├── rpc_attr.rs │ ├── rpc_trait.rs │ ├── to_client.rs │ └── to_delegate.rs └── tests │ ├── client.rs │ ├── macros.rs │ ├── pubsub-macros.rs │ ├── run-pass │ ├── client_only.rs │ ├── client_with_generic_trait_bounds.rs │ ├── pubsub-dependency-not-required-for-vanilla-rpc.rs │ ├── pubsub-subscription-generic-type-with-deserialize.rs │ ├── pubsub-subscription-type-with-deserialize.rs │ ├── pubsub-subscription-type-without-deserialize.rs │ └── server_only.rs │ ├── trailing.rs │ ├── trybuild.rs │ └── ui │ ├── attr-invalid-meta-list-names.rs │ ├── attr-invalid-meta-list-names.stderr │ ├── attr-invalid-meta-words.rs │ ├── attr-invalid-meta-words.stderr │ ├── attr-invalid-name-values.rs │ ├── attr-invalid-name-values.stderr │ ├── attr-missing-rpc-name.rs │ ├── attr-missing-rpc-name.stderr │ ├── attr-named-params-on-server.rs │ ├── attr-named-params-on-server.stderr │ ├── multiple-rpc-attributes.rs │ ├── multiple-rpc-attributes.stderr │ ├── pubsub │ ├── attr-both-subscribe-and-unsubscribe.rs │ ├── attr-both-subscribe-and-unsubscribe.stderr │ ├── attr-invalid-meta-list-names.rs │ ├── attr-invalid-meta-list-names.stderr │ ├── attr-invalid-meta-words.rs │ ├── attr-invalid-meta-words.stderr │ ├── attr-invalid-name-values.rs │ ├── attr-invalid-name-values.stderr │ ├── attr-missing-subscription-name.rs │ ├── attr-missing-subscription-name.stderr │ ├── attr-neither-subscribe-or-unsubscribe.rs │ ├── attr-neither-subscribe-or-unsubscribe.stderr │ ├── missing-subscribe.rs │ ├── missing-subscribe.stderr │ ├── missing-unsubscribe.rs │ ├── missing-unsubscribe.stderr │ ├── mixed-subscriber-signatures.rs │ └── mixed-subscriber-signatures.stderr │ ├── too-many-params.rs │ ├── too-many-params.stderr │ ├── trait-attr-named-params-on-server.rs │ └── trait-attr-named-params-on-server.stderr ├── http ├── Cargo.toml ├── README.md ├── examples │ ├── http_async.rs │ ├── http_meta.rs │ ├── http_middleware.rs │ └── server.rs └── src │ ├── handler.rs │ ├── lib.rs │ ├── response.rs │ ├── tests.rs │ └── utils.rs ├── ipc ├── Cargo.toml ├── README.md ├── examples │ └── ipc.rs └── src │ ├── lib.rs │ ├── logger.rs │ ├── meta.rs │ ├── select_with_weak.rs │ └── server.rs ├── pubsub ├── Cargo.toml ├── examples │ ├── pubsub.rs │ └── pubsub_simple.rs ├── more-examples │ ├── Cargo.toml │ ├── examples │ │ ├── pubsub_ipc.rs │ │ └── pubsub_ws.rs │ └── src │ │ └── lib.rs └── src │ ├── delegates.rs │ ├── handler.rs │ ├── lib.rs │ ├── manager.rs │ ├── oneshot.rs │ ├── subscription.rs │ ├── typed.rs │ └── types.rs ├── rustfmt.toml ├── server-utils ├── Cargo.toml └── src │ ├── cors.rs │ ├── hosts.rs │ ├── lib.rs │ ├── matcher.rs │ ├── reactor.rs │ ├── session.rs │ ├── stream_codec.rs │ └── suspendable_stream.rs ├── stdio ├── Cargo.toml ├── README.md ├── examples │ └── stdio.rs └── src │ └── lib.rs ├── tcp ├── Cargo.toml ├── README.md ├── examples │ └── tcp.rs └── src │ ├── dispatch.rs │ ├── lib.rs │ ├── logger.rs │ ├── meta.rs │ ├── server.rs │ ├── service.rs │ └── tests.rs ├── test ├── Cargo.toml └── src │ └── lib.rs └── ws ├── Cargo.toml ├── README.md ├── examples └── ws.rs └── src ├── error.rs ├── lib.rs ├── metadata.rs ├── server.rs ├── server_builder.rs ├── session.rs └── tests.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=tab 4 | indent_size=tab 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | 12 | [{.travis.yml,appveyor.yml,.gitlab-ci.yml}] 13 | indent_style=space 14 | indent_size=2 15 | tab_width=8 16 | end_of_line=lf 17 | 18 | [*.stderr] 19 | indent_style=none 20 | indent_size=none 21 | end_of_line=none 22 | charset=none 23 | trim_trailing_whitespace=none 24 | insert_final_newline=none 25 | -------------------------------------------------------------------------------- /.github/workflows/gitspiegel-trigger.yml: -------------------------------------------------------------------------------- 1 | name: gitspiegel sync 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - unlocked 9 | - ready_for_review 10 | - reopened 11 | 12 | jobs: 13 | sync: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Trigger sync via API 17 | run: | 18 | curl --fail-with-body -XPOST "https://gitspiegel.parity-prod.parity.io/api/v1/mirror/${{ github.repository }}/pull/${{ github.event.number }}" \ 19 | -H "Content-Type: application/json" \ 20 | -H "x-auth: ${{ secrets.GITSPIEGEL_TOKEN }}" 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.swp 3 | Cargo.lock 4 | .idea 5 | 6 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - checkstyle 3 | - test 4 | variables: &default-vars 5 | GIT_STRATEGY: fetch 6 | GIT_DEPTH: 100 7 | CARGO_INCREMENTAL: 0 8 | 9 | .test_and_build: &test_and_build 10 | script: 11 | - cargo build --all 12 | - cargo test --all 13 | - cargo build --manifest-path ./core-client/Cargo.toml --no-default-features --features http 14 | 15 | .only: &only 16 | only: 17 | - triggers 18 | - tags 19 | - master 20 | - schedules 21 | - web 22 | - /^[0-9]+$/ 23 | 24 | .docker-env: &docker-env 25 | image: paritytech/ci-linux:production 26 | before_script: 27 | - rustup show 28 | - cargo --version 29 | - sccache -s 30 | variables: 31 | <<: *default-vars 32 | CARGO_TARGET_DIR: "/ci-cache/${CI_PROJECT_NAME}/targets/${CI_COMMIT_REF_NAME}/${CI_JOB_NAME}" 33 | retry: 34 | max: 2 35 | when: 36 | - runner_system_failure 37 | - unknown_failure 38 | - api_failure 39 | interruptible: true 40 | tags: 41 | - linux-docker 42 | 43 | # check style 44 | checkstyle-linux-stable: 45 | stage: checkstyle 46 | <<: *only 47 | <<: *docker-env 48 | script: 49 | - rustup component add rustfmt clippy 50 | - cargo fmt --all -- --check 51 | - cargo clippy 52 | allow_failure: true 53 | 54 | # test rust stable 55 | test-linux-stable: 56 | stage: test 57 | <<: *docker-env 58 | <<: *only 59 | <<: *test_and_build 60 | 61 | test-mac-stable: 62 | stage: test 63 | <<: *test_and_build 64 | <<: *only 65 | tags: 66 | - osx 67 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lists some code owners. 2 | # 3 | # A codeowner just oversees some part of the codebase. If an owned file is changed then the 4 | # corresponding codeowner receives a review request. An approval of the codeowner might be 5 | # required for merging a PR (depends on repository settings). 6 | # 7 | # For details about syntax, see: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # But here are some important notes: 10 | # 11 | # - Glob syntax is git-like, e.g. `/core` means the core directory in the root, unlike `core` 12 | # which can be everywhere. 13 | # - Multiple owners are supported. 14 | # - Either handle (e.g, @github_user or @github_org/team) or email can be used. Keep in mind, 15 | # that handles might work better because they are more recognizable on GitHub, 16 | # you can use them for mentioning unlike an email. 17 | # - The latest matching rule, if multiple, takes precedence. 18 | 19 | # CI 20 | /.github/ @paritytech/ci 21 | /.gitlab-ci.yml @paritytech/ci @tomusdrw 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "core", 4 | "core-client", 5 | "core-client/transports", 6 | "http", 7 | "ipc", 8 | "derive", 9 | "pubsub", 10 | "pubsub/more-examples", 11 | "server-utils", 12 | "stdio", 13 | "tcp", 14 | "test", 15 | "ws", 16 | ] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 Parity Technologies Limited 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parity JSON-RPC 2 | 3 | **NOTE: This crate is no longer actively developed; please have a look at our 4 | [jsonrpsee](https://github.com/paritytech/jsonrpsee) crate if you're looking for an actively 5 | maintained JSON RPC implementation.** 6 | 7 | Rust implementation of JSON-RPC 2.0 Specification. 8 | Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` and `tcp`. 9 | 10 | **New!** Support for [clients](#Client-support). 11 | 12 | [Documentation](https://docs.rs/jsonrpc-core/) 13 | 14 | ## Sub-projects 15 | - [jsonrpc-core](./core) [![crates.io][core-image]][core-url] 16 | - [jsonrpc-core-client](./core-client) [![crates.io][core-client-image]][core-client-url] 17 | - [jsonrpc-http-server](./http) [![crates.io][http-server-image]][http-server-url] 18 | - [jsonrpc-ipc-server](./ipc) [![crates.io][ipc-server-image]][ipc-server-url] 19 | - [jsonrpc-tcp-server](./tcp) [![crates.io][tcp-server-image]][tcp-server-url] 20 | - [jsonrpc-ws-server](./ws) [![crates.io][ws-server-image]][ws-server-url] 21 | - [jsonrpc-stdio-server](./stdio) [![crates.io][stdio-server-image]][stdio-server-url] 22 | - [jsonrpc-derive](./derive) [![crates.io][derive-image]][derive-url] 23 | - [jsonrpc-server-utils](./server-utils) [![crates.io][server-utils-image]][server-utils-url] 24 | - [jsonrpc-pubsub](./pubsub) [![crates.io][pubsub-image]][pubsub-url] 25 | 26 | [core-image]: https://img.shields.io/crates/v/jsonrpc-core.svg 27 | [core-url]: https://crates.io/crates/jsonrpc-core 28 | [core-client-image]: https://img.shields.io/crates/v/jsonrpc-core-client.svg 29 | [core-client-url]: https://crates.io/crates/jsonrpc-core-client 30 | [http-server-image]: https://img.shields.io/crates/v/jsonrpc-http-server.svg 31 | [http-server-url]: https://crates.io/crates/jsonrpc-http-server 32 | [ipc-server-image]: https://img.shields.io/crates/v/jsonrpc-ipc-server.svg 33 | [ipc-server-url]: https://crates.io/crates/jsonrpc-ipc-server 34 | [tcp-server-image]: https://img.shields.io/crates/v/jsonrpc-tcp-server.svg 35 | [tcp-server-url]: https://crates.io/crates/jsonrpc-tcp-server 36 | [ws-server-image]: https://img.shields.io/crates/v/jsonrpc-ws-server.svg 37 | [ws-server-url]: https://crates.io/crates/jsonrpc-ws-server 38 | [stdio-server-image]: https://img.shields.io/crates/v/jsonrpc-stdio-server.svg 39 | [stdio-server-url]: https://crates.io/crates/jsonrpc-stdio-server 40 | [derive-image]: https://img.shields.io/crates/v/jsonrpc-derive.svg 41 | [derive-url]: https://crates.io/crates/jsonrpc-derive 42 | [server-utils-image]: https://img.shields.io/crates/v/jsonrpc-server-utils.svg 43 | [server-utils-url]: https://crates.io/crates/jsonrpc-server-utils 44 | [pubsub-image]: https://img.shields.io/crates/v/jsonrpc-pubsub.svg 45 | [pubsub-url]: https://crates.io/crates/jsonrpc-pubsub 46 | 47 | ## Examples 48 | 49 | - [core](./core/examples) 50 | - [derive](./derive/examples) 51 | - [pubsub](./pubsub/examples) 52 | 53 | ### Basic Usage (with HTTP transport) 54 | 55 | ```rust 56 | use jsonrpc_http_server::jsonrpc_core::{IoHandler, Value, Params}; 57 | use jsonrpc_http_server::ServerBuilder; 58 | 59 | fn main() { 60 | let mut io = IoHandler::default(); 61 | io.add_method("say_hello", |_params: Params| async { 62 | Ok(Value::String("hello".to_owned())) 63 | }); 64 | 65 | let server = ServerBuilder::new(io) 66 | .threads(3) 67 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 68 | .unwrap(); 69 | 70 | server.wait(); 71 | } 72 | ``` 73 | 74 | ### Basic usage with derive 75 | 76 | ```rust 77 | use jsonrpc_core::Result; 78 | use jsonrpc_derive::rpc; 79 | 80 | #[rpc] 81 | pub trait Rpc { 82 | /// Adds two numbers and returns a result 83 | #[rpc(name = "add")] 84 | fn add(&self, a: u64, b: u64) -> Result; 85 | } 86 | 87 | pub struct RpcImpl; 88 | impl Rpc for RpcImpl { 89 | fn add(&self, a: u64, b: u64) -> Result { 90 | Ok(a + b) 91 | } 92 | } 93 | 94 | fn main() { 95 | let mut io = jsonrpc_core::IoHandler::new(); 96 | io.extend_with(RpcImpl.to_delegate()) 97 | } 98 | ``` 99 | 100 | ### Client support 101 | 102 | ```rust 103 | use jsonrpc_core_client::transports::local; 104 | use jsonrpc_core::{BoxFuture, IoHandler, Result}; 105 | use jsonrpc_core::futures::{self, future, TryFutureExt}; 106 | use jsonrpc_derive::rpc; 107 | 108 | /// Rpc trait 109 | #[rpc] 110 | pub trait Rpc { 111 | /// Returns a protocol version 112 | #[rpc(name = "protocolVersion")] 113 | fn protocol_version(&self) -> Result; 114 | 115 | /// Adds two numbers and returns a result 116 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 117 | fn add(&self, a: u64, b: u64) -> Result; 118 | 119 | /// Performs asynchronous operation 120 | #[rpc(name = "callAsync")] 121 | fn call(&self, a: u64) -> BoxFuture>; 122 | } 123 | 124 | struct RpcImpl; 125 | 126 | impl Rpc for RpcImpl { 127 | fn protocol_version(&self) -> Result { 128 | Ok("version1".into()) 129 | } 130 | 131 | fn add(&self, a: u64, b: u64) -> Result { 132 | Ok(a + b) 133 | } 134 | 135 | fn call(&self, _: u64) -> BoxFuture> { 136 | Box::pin(future::ready(Ok("OK".to_owned()))) 137 | } 138 | } 139 | 140 | fn main() { 141 | let mut io = IoHandler::new(); 142 | io.extend_with(RpcImpl.to_delegate()); 143 | 144 | let (client, server) = local::connect::(io); 145 | let fut = client.add(5, 6).map_ok(|res| println!("5 + 6 = {}", res)); 146 | futures::executor::block_on(async move { futures::join!(fut, server) }) 147 | .0 148 | .unwrap(); 149 | } 150 | ``` 151 | -------------------------------------------------------------------------------- /_automate/bump_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xeu 4 | 5 | VERSION=$1 6 | PREV_DEPS=$2 7 | NEW_DEPS=$3 8 | 9 | ack "^version = \"" -l | \ 10 | grep toml | \ 11 | xargs sed -i "s/^version = \".*/version = \"$VERSION\"/" 12 | 13 | ack "{ version = \"$PREV_DEPS" -l | \ 14 | grep toml | \ 15 | xargs sed -i "s/{ version = \"$PREV_DEPS/{ version = \"$NEW_DEPS/" 16 | 17 | ack " = \"$PREV_DEPS" -l | \ 18 | grep md | \ 19 | xargs sed -i "s/ = \"$PREV_DEPS/ = \"$NEW_DEPS/" 20 | 21 | cargo check 22 | -------------------------------------------------------------------------------- /_automate/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | ORDER=(core server-utils tcp ws http ipc stdio pubsub core-client/transports core-client derive test) 6 | 7 | function read_toml () { 8 | NAME="" 9 | VERSION="" 10 | NAME=$(grep "^name" ./Cargo.toml | sed -e 's/.*"\(.*\)"/\1/') 11 | VERSION=$(grep "^version" ./Cargo.toml | sed -e 's/.*"\(.*\)"/\1/') 12 | } 13 | function remote_version () { 14 | REMOTE_VERSION="" 15 | REMOTE_VERSION=$(cargo search "$NAME" | grep "^$NAME =" | sed -e 's/.*"\(.*\)".*/\1/') 16 | } 17 | 18 | # First display the plan 19 | for CRATE_DIR in ${ORDER[@]}; do 20 | cd $CRATE_DIR > /dev/null 21 | read_toml 22 | echo "$NAME@$VERSION" 23 | cd - > /dev/null 24 | done 25 | 26 | read -p ">>>> Really publish?. Press [enter] to continue. " 27 | 28 | set -x 29 | 30 | cargo clean 31 | 32 | set +x 33 | 34 | # Then actually perform publishing. 35 | for CRATE_DIR in ${ORDER[@]}; do 36 | cd $CRATE_DIR > /dev/null 37 | read_toml 38 | remote_version 39 | # Seems the latest version matches, skip by default. 40 | if [ "$REMOTE_VERSION" = "$VERSION" ] || [[ "$REMOTE_VERSION" > "$VERSION" ]]; then 41 | RET="" 42 | echo "Seems like $NAME@$REMOTE_VERSION is already published. Continuing in 5s. " 43 | read -t 5 -p ">>>> Type [r][enter] to retry, or [enter] to continue... " RET || true 44 | if [ "$RET" != "r" ]; then 45 | echo "Skipping $NAME@$VERSION" 46 | cd - > /dev/null 47 | continue 48 | fi 49 | fi 50 | 51 | # Attempt to publish (allow retries) 52 | while : ; do 53 | # give the user an opportunity to abort or skip before publishing 54 | echo "🚀 Publishing $NAME@$VERSION..." 55 | sleep 3 56 | 57 | set +e && set -x 58 | cargo publish $@ 59 | RES=$? 60 | set +x && set -e 61 | # Check if it succeeded 62 | if [ "$RES" != "0" ]; then 63 | CHOICE="" 64 | echo "##### Publishing $NAME failed" 65 | read -p ">>>>> Type [s][enter] to skip, or [enter] to retry.. " CHOICE 66 | if [ "$CHOICE" = "s" ]; then 67 | break 68 | fi 69 | else 70 | break 71 | fi 72 | done 73 | 74 | # Wait again to make sure that the new version is published and available. 75 | echo "Waiting for $NAME@$VERSION to become available at the registry..." 76 | while : ; do 77 | sleep 3 78 | remote_version 79 | if [ "$REMOTE_VERSION" = "$VERSION" ]; then 80 | echo "🥳 $NAME@$VERSION published succesfully." 81 | sleep 3 82 | break 83 | else 84 | echo "#### Got $NAME@$REMOTE_VERSION but expected $NAME@$VERSION. Retrying..." 85 | fi 86 | done 87 | cd - > /dev/null 88 | done 89 | 90 | # Make tags in one go 91 | set -x 92 | git fetch --tags 93 | set +x 94 | 95 | for CRATE_DIR in ${ORDER[@]}; do 96 | cd $CRATE_DIR > /dev/null 97 | read_toml 98 | echo "Tagging $NAME@$VERSION" 99 | set -x 100 | git tag -a "$NAME-$VERSION" -m "$NAME $VERSION" || true 101 | set +x 102 | cd - > /dev/null 103 | done 104 | 105 | set -x 106 | sleep 3 107 | git push --tags 108 | set +x 109 | 110 | cd core > /dev/null 111 | read_toml 112 | cd - > /dev/null 113 | echo "Tagging jsonrpc@$VERSION" 114 | set -x 115 | git tag -a v$VERSION -m "Version $VERSION" 116 | sleep 3 117 | git push --tags 118 | -------------------------------------------------------------------------------- /core-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Transport agnostic JSON-RPC 2.0 client implementation." 4 | documentation = "https://docs.rs/jsonrpc-core-client/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] 8 | license = "MIT" 9 | name = "jsonrpc-core-client" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | categories = [ 14 | "asynchronous", 15 | "network-programming", 16 | "web-programming::http-client", 17 | "web-programming::http-server", 18 | "web-programming::websocket", 19 | ] 20 | 21 | [features] 22 | tls = ["jsonrpc-client-transports/tls"] 23 | http = ["jsonrpc-client-transports/http"] 24 | ws = ["jsonrpc-client-transports/ws"] 25 | ipc = ["jsonrpc-client-transports/ipc"] 26 | arbitrary_precision = ["jsonrpc-client-transports/arbitrary_precision"] 27 | 28 | [dependencies] 29 | jsonrpc-client-transports = { version = "18.0.0", path = "./transports", default-features = false } 30 | futures = { version = "0.3", features = [ "compat" ] } 31 | 32 | [badges] 33 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 34 | -------------------------------------------------------------------------------- /core-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! JSON-RPC client implementation primitives. 2 | //! 3 | //! By default this crate does not implement any transports, 4 | //! use corresponding features (`tls`, `http` or `ws`) to opt-in for them. 5 | //! 6 | //! See documentation of [`jsonrpc-client-transports`](https://docs.rs/jsonrpc-client-transports) for more details. 7 | 8 | #![deny(missing_docs)] 9 | 10 | pub use futures; 11 | pub use jsonrpc_client_transports::*; 12 | -------------------------------------------------------------------------------- /core-client/transports/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Transport agnostic JSON-RPC 2.0 client implementation." 4 | documentation = "https://docs.rs/jsonrpc-client-transports/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] 8 | license = "MIT" 9 | name = "jsonrpc-client-transports" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | categories = [ 14 | "asynchronous", 15 | "network-programming", 16 | "web-programming::http-client", 17 | "web-programming::http-server", 18 | "web-programming::websocket", 19 | ] 20 | 21 | [features] 22 | default = ["http", "tls", "ws"] 23 | tls = ["hyper-tls", "http"] 24 | http = ["hyper", "tokio/full"] 25 | ws = [ 26 | "websocket", 27 | "tokio", 28 | "futures/compat" 29 | ] 30 | ipc = [ 31 | "parity-tokio-ipc", 32 | "jsonrpc-server-utils", 33 | "tokio", 34 | ] 35 | arbitrary_precision = ["serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] 36 | 37 | [dependencies] 38 | derive_more = "0.99" 39 | futures = "0.3" 40 | jsonrpc-core = { version = "18.0.0", path = "../../core" } 41 | jsonrpc-pubsub = { version = "18.0.0", path = "../../pubsub" } 42 | log = "0.4" 43 | serde = { version = "1.0", features = ["derive"] } 44 | serde_json = "1.0" 45 | 46 | hyper = { version = "0.14", features = ["client", "http1", "tcp"], optional = true } 47 | hyper-tls = { version = "0.5", optional = true } 48 | jsonrpc-server-utils = { version = "18.0.0", path = "../../server-utils", optional = true } 49 | parity-tokio-ipc = { version = "0.9", optional = true } 50 | tokio = { version = "1", optional = true } 51 | websocket = { version = "0.26", optional = true } 52 | 53 | [dev-dependencies] 54 | assert_matches = "1.1" 55 | jsonrpc-http-server = { version = "18.0.0", path = "../../http" } 56 | jsonrpc-ipc-server = { version = "18.0.0", path = "../../ipc" } 57 | lazy_static = "1.0" 58 | env_logger = "0.7" 59 | 60 | [badges] 61 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master" } 62 | -------------------------------------------------------------------------------- /core-client/transports/src/logger.rs: -------------------------------------------------------------------------------- 1 | use env_logger::Builder; 2 | use lazy_static::lazy_static; 3 | use log::LevelFilter; 4 | use std::env; 5 | 6 | lazy_static! { 7 | static ref LOG_DUMMY: bool = { 8 | let mut builder = Builder::new(); 9 | builder.filter(None, LevelFilter::Info); 10 | 11 | if let Ok(log) = env::var("RUST_LOG") { 12 | builder.parse_filters(&log); 13 | } 14 | 15 | if let Ok(_) = builder.try_init() { 16 | println!("logger initialized"); 17 | } 18 | true 19 | }; 20 | } 21 | 22 | /// Intialize log with default settings 23 | pub fn init_log() { 24 | let _ = *LOG_DUMMY; 25 | } 26 | -------------------------------------------------------------------------------- /core-client/transports/src/transports/ipc.rs: -------------------------------------------------------------------------------- 1 | //! JSON-RPC IPC client implementation using Unix Domain Sockets on UNIX-likes 2 | //! and Named Pipes on Windows. 3 | 4 | use crate::transports::duplex::duplex; 5 | use crate::{RpcChannel, RpcError}; 6 | use futures::{SinkExt, StreamExt, TryStreamExt}; 7 | use jsonrpc_server_utils::codecs::StreamCodec; 8 | use jsonrpc_server_utils::tokio; 9 | use jsonrpc_server_utils::tokio_util::codec::Decoder as _; 10 | use parity_tokio_ipc::Endpoint; 11 | use std::path::Path; 12 | 13 | /// Connect to a JSON-RPC IPC server. 14 | pub async fn connect, Client: From>(path: P) -> Result { 15 | let connection = Endpoint::connect(path) 16 | .await 17 | .map_err(|e| RpcError::Other(Box::new(e)))?; 18 | let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split(); 19 | let sink = sink.sink_map_err(|e| RpcError::Other(Box::new(e))); 20 | let stream = stream.map_err(|e| log::error!("IPC stream error: {}", e)); 21 | 22 | let (client, sender) = duplex( 23 | Box::pin(sink), 24 | Box::pin( 25 | stream 26 | .take_while(|x| futures::future::ready(x.is_ok())) 27 | .map(|x| x.expect("Stream is closed upon first error.")), 28 | ), 29 | ); 30 | 31 | tokio::spawn(client); 32 | 33 | Ok(sender.into()) 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | use crate::*; 40 | use jsonrpc_core::{Error, ErrorCode, IoHandler, Params, Value}; 41 | use jsonrpc_ipc_server::ServerBuilder; 42 | use parity_tokio_ipc::dummy_endpoint; 43 | use serde_json::map::Map; 44 | 45 | #[test] 46 | fn should_call_one() { 47 | let sock_path = dummy_endpoint(); 48 | 49 | let mut io = IoHandler::new(); 50 | io.add_method("greeting", |params| async { 51 | let map_obj = match params { 52 | Params::Map(obj) => obj, 53 | _ => return Err(Error::invalid_params("missing object")), 54 | }; 55 | let name = match map_obj.get("name") { 56 | Some(val) => val.as_str().unwrap(), 57 | None => return Err(Error::invalid_params("no name")), 58 | }; 59 | Ok(Value::String(format!("Hello {}!", name))) 60 | }); 61 | let builder = ServerBuilder::new(io); 62 | let _server = builder.start(&sock_path).expect("Couldn't open socket"); 63 | 64 | let client_fut = async move { 65 | let client: RawClient = connect(sock_path).await.unwrap(); 66 | let mut map = Map::new(); 67 | map.insert("name".to_string(), "Jeffry".into()); 68 | let fut = client.call_method("greeting", Params::Map(map)); 69 | 70 | match fut.await { 71 | Ok(val) => assert_eq!(&val, "Hello Jeffry!"), 72 | Err(err) => panic!("IPC RPC call failed: {}", err), 73 | } 74 | }; 75 | tokio::runtime::Runtime::new().unwrap().block_on(client_fut); 76 | } 77 | 78 | #[test] 79 | fn should_fail_without_server() { 80 | let test_fut = async move { 81 | match connect::<_, RawClient>(dummy_endpoint()).await { 82 | Err(..) => {} 83 | Ok(..) => panic!("Should not be able to connect to an IPC socket that's not open"), 84 | } 85 | }; 86 | 87 | tokio::runtime::Runtime::new().unwrap().block_on(test_fut); 88 | } 89 | 90 | #[test] 91 | fn should_handle_server_error() { 92 | let sock_path = dummy_endpoint(); 93 | 94 | let mut io = IoHandler::new(); 95 | io.add_method("greeting", |_params| async { Err(Error::invalid_params("test error")) }); 96 | let builder = ServerBuilder::new(io); 97 | let _server = builder.start(&sock_path).expect("Couldn't open socket"); 98 | 99 | let client_fut = async move { 100 | let client: RawClient = connect(sock_path).await.unwrap(); 101 | let mut map = Map::new(); 102 | map.insert("name".to_string(), "Jeffry".into()); 103 | let fut = client.call_method("greeting", Params::Map(map)); 104 | 105 | match fut.await { 106 | Err(RpcError::JsonRpcError(err)) => assert_eq!(err.code, ErrorCode::InvalidParams), 107 | Ok(_) => panic!("Expected the call to fail"), 108 | _ => panic!("Unexpected error type"), 109 | } 110 | }; 111 | 112 | tokio::runtime::Runtime::new().unwrap().block_on(client_fut); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Transport agnostic rust implementation of JSON-RPC 2.0 Specification." 4 | documentation = "https://docs.rs/jsonrpc-core/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] 8 | license = "MIT" 9 | name = "jsonrpc-core" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | categories = [ 14 | "asynchronous", 15 | "network-programming", 16 | "web-programming::http-client", 17 | "web-programming::http-server", 18 | "web-programming::websocket", 19 | ] 20 | 21 | [dependencies] 22 | log = "0.4" 23 | # FIXME: Currently a lot of jsonrpc-* crates depend on entire `futures` being 24 | # re-exported but it's not strictly required for this crate. Either adapt the 25 | # remaining crates or settle for this re-export to be a single, common dependency 26 | futures = { version = "0.3", optional = true } 27 | futures-util = { version = "0.3", default-features = false, features = ["std"] } 28 | futures-executor = { version = "0.3", optional = true } 29 | serde = "1.0" 30 | serde_json = "1.0" 31 | serde_derive = "1.0" 32 | 33 | [features] 34 | default = ["futures-executor", "futures"] 35 | arbitrary_precision = ["serde_json/arbitrary_precision"] 36 | 37 | [badges] 38 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 39 | -------------------------------------------------------------------------------- /core/examples/async.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::*; 2 | 3 | fn main() { 4 | futures_executor::block_on(async { 5 | let mut io = IoHandler::new(); 6 | 7 | io.add_method("say_hello", |_: Params| async { 8 | Ok(Value::String("Hello World!".to_owned())) 9 | }); 10 | 11 | let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; 12 | let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; 13 | 14 | assert_eq!(io.handle_request(request).await, Some(response.to_owned())); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /core/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::*; 2 | 3 | fn main() { 4 | let mut io = IoHandler::new(); 5 | 6 | io.add_sync_method("say_hello", |_: Params| Ok(Value::String("Hello World!".to_owned()))); 7 | 8 | let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; 9 | let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; 10 | 11 | assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); 12 | } 13 | -------------------------------------------------------------------------------- /core/examples/meta.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::*; 2 | 3 | #[derive(Clone, Default)] 4 | struct Meta(usize); 5 | impl Metadata for Meta {} 6 | 7 | pub fn main() { 8 | let mut io = MetaIoHandler::default(); 9 | 10 | io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| async move { 11 | Ok(Value::String(format!("Hello World: {}", meta.0))) 12 | }); 13 | 14 | let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; 15 | let response = r#"{"jsonrpc":"2.0","result":"Hello World: 5","id":1}"#; 16 | 17 | let headers = 5; 18 | assert_eq!( 19 | io.handle_request_sync(request, Meta(headers)), 20 | Some(response.to_owned()) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /core/examples/middlewares.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::futures_util::{future::Either, FutureExt}; 2 | use jsonrpc_core::*; 3 | use std::future::Future; 4 | use std::sync::atomic::{self, AtomicUsize}; 5 | use std::time::Instant; 6 | 7 | #[derive(Clone, Debug)] 8 | struct Meta(usize); 9 | impl Metadata for Meta {} 10 | 11 | #[derive(Default)] 12 | struct MyMiddleware(AtomicUsize); 13 | impl Middleware for MyMiddleware { 14 | type Future = FutureResponse; 15 | type CallFuture = middleware::NoopCallFuture; 16 | 17 | fn on_request(&self, request: Request, meta: Meta, next: F) -> Either 18 | where 19 | F: FnOnce(Request, Meta) -> X + Send, 20 | X: Future> + Send + 'static, 21 | { 22 | let start = Instant::now(); 23 | let request_number = self.0.fetch_add(1, atomic::Ordering::SeqCst); 24 | println!("Processing request {}: {:?}, {:?}", request_number, request, meta); 25 | 26 | Either::Left(Box::pin(next(request, meta).map(move |res| { 27 | println!("Processing took: {:?}", start.elapsed()); 28 | res 29 | }))) 30 | } 31 | } 32 | 33 | pub fn main() { 34 | let mut io = MetaIoHandler::with_middleware(MyMiddleware::default()); 35 | 36 | io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| async move { 37 | Ok(Value::String(format!("Hello World: {}", meta.0))) 38 | }); 39 | 40 | let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; 41 | let response = r#"{"jsonrpc":"2.0","result":"Hello World: 5","id":1}"#; 42 | 43 | let headers = 5; 44 | assert_eq!( 45 | io.handle_request_sync(request, Meta(headers)), 46 | Some(response.to_owned()) 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /core/examples/params.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::*; 2 | use serde_derive::Deserialize; 3 | 4 | #[derive(Deserialize)] 5 | struct HelloParams { 6 | name: String, 7 | } 8 | 9 | fn main() { 10 | let mut io = IoHandler::new(); 11 | 12 | io.add_method("say_hello", |params: Params| async move { 13 | let parsed: HelloParams = params.parse().unwrap(); 14 | Ok(Value::String(format!("hello, {}", parsed.name))) 15 | }); 16 | 17 | let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": { "name": "world" }, "id": 1}"#; 18 | let response = r#"{"jsonrpc":"2.0","result":"hello, world","id":1}"#; 19 | 20 | assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); 21 | } 22 | -------------------------------------------------------------------------------- /core/src/calls.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Error, Params, Value}; 2 | use crate::BoxFuture; 3 | use std::fmt; 4 | use std::future::Future; 5 | use std::sync::Arc; 6 | 7 | /// Metadata trait 8 | pub trait Metadata: Clone + Send + 'static {} 9 | impl Metadata for () {} 10 | impl Metadata for Option {} 11 | impl Metadata for Box {} 12 | impl Metadata for Arc {} 13 | 14 | /// A future-conversion trait. 15 | pub trait WrapFuture { 16 | /// Convert itself into a boxed future. 17 | fn into_future(self) -> BoxFuture>; 18 | } 19 | 20 | impl WrapFuture for Result { 21 | fn into_future(self) -> BoxFuture> { 22 | Box::pin(async { self }) 23 | } 24 | } 25 | 26 | impl WrapFuture for BoxFuture> { 27 | fn into_future(self) -> BoxFuture> { 28 | self 29 | } 30 | } 31 | 32 | /// A synchronous or asynchronous method. 33 | pub trait RpcMethodSync: Send + Sync + 'static { 34 | /// Call method 35 | fn call(&self, params: Params) -> BoxFuture>; 36 | } 37 | 38 | /// Asynchronous Method 39 | pub trait RpcMethodSimple: Send + Sync + 'static { 40 | /// Output future 41 | type Out: Future> + Send; 42 | /// Call method 43 | fn call(&self, params: Params) -> Self::Out; 44 | } 45 | 46 | /// Asynchronous Method with Metadata 47 | pub trait RpcMethod: Send + Sync + 'static { 48 | /// Call method 49 | fn call(&self, params: Params, meta: T) -> BoxFuture>; 50 | } 51 | 52 | /// Notification 53 | pub trait RpcNotificationSimple: Send + Sync + 'static { 54 | /// Execute notification 55 | fn execute(&self, params: Params); 56 | } 57 | 58 | /// Notification with Metadata 59 | pub trait RpcNotification: Send + Sync + 'static { 60 | /// Execute notification 61 | fn execute(&self, params: Params, meta: T); 62 | } 63 | 64 | /// Possible Remote Procedures with Metadata 65 | #[derive(Clone)] 66 | pub enum RemoteProcedure { 67 | /// A method call 68 | Method(Arc>), 69 | /// A notification 70 | Notification(Arc>), 71 | /// An alias to other method, 72 | Alias(String), 73 | } 74 | 75 | impl fmt::Debug for RemoteProcedure { 76 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 77 | use self::RemoteProcedure::*; 78 | match *self { 79 | Method(..) => write!(fmt, ""), 80 | Notification(..) => write!(fmt, ""), 81 | Alias(ref alias) => write!(fmt, "alias => {:?}", alias), 82 | } 83 | } 84 | } 85 | 86 | impl RpcMethodSimple for F 87 | where 88 | F: Fn(Params) -> X, 89 | X: Future>, 90 | { 91 | type Out = X; 92 | fn call(&self, params: Params) -> Self::Out { 93 | self(params) 94 | } 95 | } 96 | 97 | impl RpcMethodSync for F 98 | where 99 | F: Fn(Params) -> X, 100 | X: WrapFuture, 101 | { 102 | fn call(&self, params: Params) -> BoxFuture> { 103 | self(params).into_future() 104 | } 105 | } 106 | 107 | impl RpcNotificationSimple for F 108 | where 109 | F: Fn(Params), 110 | { 111 | fn execute(&self, params: Params) { 112 | self(params) 113 | } 114 | } 115 | 116 | impl RpcMethod for F 117 | where 118 | T: Metadata, 119 | F: Fn(Params, T) -> X, 120 | X: Future>, 121 | { 122 | fn call(&self, params: Params, meta: T) -> BoxFuture> { 123 | Box::pin(self(params, meta)) 124 | } 125 | } 126 | 127 | impl RpcNotification for F 128 | where 129 | T: Metadata, 130 | F: Fn(Params, T), 131 | { 132 | fn execute(&self, params: Params, meta: T) { 133 | self(params, meta) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/src/delegates.rs: -------------------------------------------------------------------------------- 1 | //! Delegate rpc calls 2 | 3 | use std::collections::HashMap; 4 | use std::future::Future; 5 | use std::sync::Arc; 6 | 7 | use crate::calls::{Metadata, RemoteProcedure, RpcMethod, RpcNotification}; 8 | use crate::types::{Error, Params, Value}; 9 | use crate::BoxFuture; 10 | 11 | struct DelegateAsyncMethod { 12 | delegate: Arc, 13 | closure: F, 14 | } 15 | 16 | impl RpcMethod for DelegateAsyncMethod 17 | where 18 | M: Metadata, 19 | F: Fn(&T, Params) -> I, 20 | I: Future> + Send + 'static, 21 | T: Send + Sync + 'static, 22 | F: Send + Sync + 'static, 23 | { 24 | fn call(&self, params: Params, _meta: M) -> BoxFuture> { 25 | let closure = &self.closure; 26 | Box::pin(closure(&self.delegate, params)) 27 | } 28 | } 29 | 30 | struct DelegateMethodWithMeta { 31 | delegate: Arc, 32 | closure: F, 33 | } 34 | 35 | impl RpcMethod for DelegateMethodWithMeta 36 | where 37 | M: Metadata, 38 | F: Fn(&T, Params, M) -> I, 39 | I: Future> + Send + 'static, 40 | T: Send + Sync + 'static, 41 | F: Send + Sync + 'static, 42 | { 43 | fn call(&self, params: Params, meta: M) -> BoxFuture> { 44 | let closure = &self.closure; 45 | Box::pin(closure(&self.delegate, params, meta)) 46 | } 47 | } 48 | 49 | struct DelegateNotification { 50 | delegate: Arc, 51 | closure: F, 52 | } 53 | 54 | impl RpcNotification for DelegateNotification 55 | where 56 | M: Metadata, 57 | F: Fn(&T, Params) + 'static, 58 | F: Send + Sync + 'static, 59 | T: Send + Sync + 'static, 60 | { 61 | fn execute(&self, params: Params, _meta: M) { 62 | let closure = &self.closure; 63 | closure(&self.delegate, params) 64 | } 65 | } 66 | 67 | struct DelegateNotificationWithMeta { 68 | delegate: Arc, 69 | closure: F, 70 | } 71 | 72 | impl RpcNotification for DelegateNotificationWithMeta 73 | where 74 | M: Metadata, 75 | F: Fn(&T, Params, M) + 'static, 76 | F: Send + Sync + 'static, 77 | T: Send + Sync + 'static, 78 | { 79 | fn execute(&self, params: Params, meta: M) { 80 | let closure = &self.closure; 81 | closure(&self.delegate, params, meta) 82 | } 83 | } 84 | 85 | /// A set of RPC methods and notifications tied to single `delegate` struct. 86 | pub struct IoDelegate 87 | where 88 | T: Send + Sync + 'static, 89 | M: Metadata, 90 | { 91 | delegate: Arc, 92 | methods: HashMap>, 93 | } 94 | 95 | impl IoDelegate 96 | where 97 | T: Send + Sync + 'static, 98 | M: Metadata, 99 | { 100 | /// Creates new `IoDelegate` 101 | pub fn new(delegate: Arc) -> Self { 102 | IoDelegate { 103 | delegate, 104 | methods: HashMap::new(), 105 | } 106 | } 107 | 108 | /// Adds an alias to existing method. 109 | /// NOTE: Aliases are not transitive, i.e. you cannot create alias to an alias. 110 | pub fn add_alias(&mut self, from: &str, to: &str) { 111 | self.methods.insert(from.into(), RemoteProcedure::Alias(to.into())); 112 | } 113 | 114 | /// Adds async method to the delegate. 115 | pub fn add_method(&mut self, name: &str, method: F) 116 | where 117 | F: Fn(&T, Params) -> I, 118 | I: Future> + Send + 'static, 119 | F: Send + Sync + 'static, 120 | { 121 | self.methods.insert( 122 | name.into(), 123 | RemoteProcedure::Method(Arc::new(DelegateAsyncMethod { 124 | delegate: self.delegate.clone(), 125 | closure: method, 126 | })), 127 | ); 128 | } 129 | 130 | /// Adds async method with metadata to the delegate. 131 | pub fn add_method_with_meta(&mut self, name: &str, method: F) 132 | where 133 | F: Fn(&T, Params, M) -> I, 134 | I: Future> + Send + 'static, 135 | F: Send + Sync + 'static, 136 | { 137 | self.methods.insert( 138 | name.into(), 139 | RemoteProcedure::Method(Arc::new(DelegateMethodWithMeta { 140 | delegate: self.delegate.clone(), 141 | closure: method, 142 | })), 143 | ); 144 | } 145 | 146 | /// Adds notification to the delegate. 147 | pub fn add_notification(&mut self, name: &str, notification: F) 148 | where 149 | F: Fn(&T, Params), 150 | F: Send + Sync + 'static, 151 | { 152 | self.methods.insert( 153 | name.into(), 154 | RemoteProcedure::Notification(Arc::new(DelegateNotification { 155 | delegate: self.delegate.clone(), 156 | closure: notification, 157 | })), 158 | ); 159 | } 160 | 161 | /// Adds notification with metadata to the delegate. 162 | pub fn add_notification_with_meta(&mut self, name: &str, notification: F) 163 | where 164 | F: Fn(&T, Params, M), 165 | F: Send + Sync + 'static, 166 | { 167 | self.methods.insert( 168 | name.into(), 169 | RemoteProcedure::Notification(Arc::new(DelegateNotificationWithMeta { 170 | delegate: self.delegate.clone(), 171 | closure: notification, 172 | })), 173 | ); 174 | } 175 | } 176 | 177 | impl crate::io::IoHandlerExtension for IoDelegate 178 | where 179 | T: Send + Sync + 'static, 180 | M: Metadata, 181 | { 182 | fn augment>(self, handler: &mut crate::MetaIoHandler) { 183 | handler.extend_with(self.methods) 184 | } 185 | } 186 | 187 | impl IntoIterator for IoDelegate 188 | where 189 | T: Send + Sync + 'static, 190 | M: Metadata, 191 | { 192 | type Item = (String, RemoteProcedure); 193 | type IntoIter = std::collections::hash_map::IntoIter>; 194 | 195 | fn into_iter(self) -> Self::IntoIter { 196 | self.methods.into_iter() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ### Transport agnostic jsonrpc library. 2 | //! 3 | //! Right now it supports only server side handling requests. 4 | //! 5 | //! ```rust 6 | //! use jsonrpc_core::IoHandler; 7 | //! use jsonrpc_core::Value; 8 | //! let mut io = IoHandler::new(); 9 | //! io.add_sync_method("say_hello", |_| { 10 | //! Ok(Value::String("Hello World!".into())) 11 | //! }); 12 | //! 13 | //! let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; 14 | //! let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; 15 | //! 16 | //! assert_eq!(io.handle_request_sync(request), Some(response.to_string())); 17 | //! ``` 18 | 19 | #![deny(missing_docs)] 20 | 21 | use std::pin::Pin; 22 | 23 | #[macro_use] 24 | extern crate log; 25 | #[macro_use] 26 | extern crate serde_derive; 27 | 28 | #[cfg(feature = "futures")] 29 | pub use futures; 30 | #[cfg(feature = "futures-executor")] 31 | pub use futures_executor; 32 | pub use futures_util; 33 | 34 | #[doc(hidden)] 35 | pub extern crate serde; 36 | #[doc(hidden)] 37 | pub extern crate serde_json; 38 | 39 | mod calls; 40 | mod io; 41 | 42 | pub mod delegates; 43 | pub mod middleware; 44 | pub mod types; 45 | 46 | /// A Result type. 47 | pub type Result = std::result::Result; 48 | 49 | /// A `Future` trait object. 50 | pub type BoxFuture = Pin + Send>>; 51 | 52 | pub use crate::calls::{ 53 | Metadata, RemoteProcedure, RpcMethod, RpcMethodSimple, RpcMethodSync, RpcNotification, RpcNotificationSimple, 54 | WrapFuture, 55 | }; 56 | pub use crate::delegates::IoDelegate; 57 | pub use crate::io::{ 58 | Compatibility, FutureOutput, FutureResponse, FutureResult, FutureRpcResult, IoHandler, IoHandlerExtension, 59 | MetaIoHandler, 60 | }; 61 | pub use crate::middleware::{Middleware, Noop as NoopMiddleware}; 62 | pub use crate::types::*; 63 | 64 | use serde_json::Error as SerdeError; 65 | 66 | /// workaround for https://github.com/serde-rs/json/issues/505 67 | /// Arbitrary precision confuses serde when deserializing into untagged enums, 68 | /// this is a workaround 69 | pub fn serde_from_str<'a, T>(input: &'a str) -> std::result::Result 70 | where 71 | T: serde::de::Deserialize<'a>, 72 | { 73 | if cfg!(feature = "arbitrary_precision") { 74 | let val = serde_json::from_str::(input)?; 75 | T::deserialize(val) 76 | } else { 77 | serde_json::from_str::(input) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /core/src/middleware.rs: -------------------------------------------------------------------------------- 1 | //! `IoHandler` middlewares 2 | 3 | use crate::calls::Metadata; 4 | use crate::types::{Call, Output, Request, Response}; 5 | use futures_util::future::Either; 6 | use std::future::Future; 7 | use std::pin::Pin; 8 | 9 | /// RPC middleware 10 | pub trait Middleware: Send + Sync + 'static { 11 | /// A returned request future. 12 | type Future: Future> + Send + 'static; 13 | 14 | /// A returned call future. 15 | type CallFuture: Future> + Send + 'static; 16 | 17 | /// Method invoked on each request. 18 | /// Allows you to either respond directly (without executing RPC call) 19 | /// or do any additional work before and/or after processing the request. 20 | fn on_request(&self, request: Request, meta: M, next: F) -> Either 21 | where 22 | F: Fn(Request, M) -> X + Send + Sync, 23 | X: Future> + Send + 'static, 24 | { 25 | Either::Right(next(request, meta)) 26 | } 27 | 28 | /// Method invoked on each call inside a request. 29 | /// 30 | /// Allows you to either handle the call directly (without executing RPC call). 31 | fn on_call(&self, call: Call, meta: M, next: F) -> Either 32 | where 33 | F: Fn(Call, M) -> X + Send + Sync, 34 | X: Future> + Send + 'static, 35 | { 36 | Either::Right(next(call, meta)) 37 | } 38 | } 39 | 40 | /// Dummy future used as a noop result of middleware. 41 | pub type NoopFuture = Pin> + Send>>; 42 | /// Dummy future used as a noop call result of middleware. 43 | pub type NoopCallFuture = Pin> + Send>>; 44 | 45 | /// No-op middleware implementation 46 | #[derive(Clone, Debug, Default)] 47 | pub struct Noop; 48 | impl Middleware for Noop { 49 | type Future = NoopFuture; 50 | type CallFuture = NoopCallFuture; 51 | } 52 | 53 | impl, B: Middleware> Middleware for (A, B) { 54 | type Future = Either; 55 | type CallFuture = Either; 56 | 57 | fn on_request(&self, request: Request, meta: M, process: F) -> Either 58 | where 59 | F: Fn(Request, M) -> X + Send + Sync, 60 | X: Future> + Send + 'static, 61 | { 62 | repack(self.0.on_request(request, meta, |request, meta| { 63 | self.1.on_request(request, meta, &process) 64 | })) 65 | } 66 | 67 | fn on_call(&self, call: Call, meta: M, process: F) -> Either 68 | where 69 | F: Fn(Call, M) -> X + Send + Sync, 70 | X: Future> + Send + 'static, 71 | { 72 | repack( 73 | self.0 74 | .on_call(call, meta, |call, meta| self.1.on_call(call, meta, &process)), 75 | ) 76 | } 77 | } 78 | 79 | impl, B: Middleware, C: Middleware> Middleware for (A, B, C) { 80 | type Future = Either>; 81 | type CallFuture = Either>; 82 | 83 | fn on_request(&self, request: Request, meta: M, process: F) -> Either 84 | where 85 | F: Fn(Request, M) -> X + Send + Sync, 86 | X: Future> + Send + 'static, 87 | { 88 | repack(self.0.on_request(request, meta, |request, meta| { 89 | repack(self.1.on_request(request, meta, |request, meta| { 90 | self.2.on_request(request, meta, &process) 91 | })) 92 | })) 93 | } 94 | 95 | fn on_call(&self, call: Call, meta: M, process: F) -> Either 96 | where 97 | F: Fn(Call, M) -> X + Send + Sync, 98 | X: Future> + Send + 'static, 99 | { 100 | repack(self.0.on_call(call, meta, |call, meta| { 101 | repack( 102 | self.1 103 | .on_call(call, meta, |call, meta| self.2.on_call(call, meta, &process)), 104 | ) 105 | })) 106 | } 107 | } 108 | 109 | impl, B: Middleware, C: Middleware, D: Middleware> Middleware 110 | for (A, B, C, D) 111 | { 112 | type Future = Either>>; 113 | type CallFuture = Either>>; 114 | 115 | fn on_request(&self, request: Request, meta: M, process: F) -> Either 116 | where 117 | F: Fn(Request, M) -> X + Send + Sync, 118 | X: Future> + Send + 'static, 119 | { 120 | repack(self.0.on_request(request, meta, |request, meta| { 121 | repack(self.1.on_request(request, meta, |request, meta| { 122 | repack(self.2.on_request(request, meta, |request, meta| { 123 | self.3.on_request(request, meta, &process) 124 | })) 125 | })) 126 | })) 127 | } 128 | 129 | fn on_call(&self, call: Call, meta: M, process: F) -> Either 130 | where 131 | F: Fn(Call, M) -> X + Send + Sync, 132 | X: Future> + Send + 'static, 133 | { 134 | repack(self.0.on_call(call, meta, |call, meta| { 135 | repack(self.1.on_call(call, meta, |call, meta| { 136 | repack( 137 | self.2 138 | .on_call(call, meta, |call, meta| self.3.on_call(call, meta, &process)), 139 | ) 140 | })) 141 | })) 142 | } 143 | } 144 | 145 | #[inline(always)] 146 | fn repack(result: Either>) -> Either, X> { 147 | match result { 148 | Either::Left(a) => Either::Left(Either::Left(a)), 149 | Either::Right(Either::Left(b)) => Either::Left(Either::Right(b)), 150 | Either::Right(Either::Right(x)) => Either::Right(x), 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /core/src/types/error.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc errors 2 | use super::Value; 3 | use serde::de::{Deserialize, Deserializer}; 4 | use serde::ser::{Serialize, Serializer}; 5 | use std::fmt; 6 | 7 | /// JSONRPC error code 8 | #[derive(Debug, PartialEq, Clone)] 9 | pub enum ErrorCode { 10 | /// Invalid JSON was received by the server. 11 | /// An error occurred on the server while parsing the JSON text. 12 | ParseError, 13 | /// The JSON sent is not a valid Request object. 14 | InvalidRequest, 15 | /// The method does not exist / is not available. 16 | MethodNotFound, 17 | /// Invalid method parameter(s). 18 | InvalidParams, 19 | /// Internal JSON-RPC error. 20 | InternalError, 21 | /// Reserved for implementation-defined server-errors. 22 | ServerError(i64), 23 | } 24 | 25 | impl ErrorCode { 26 | /// Returns integer code value 27 | pub fn code(&self) -> i64 { 28 | match *self { 29 | ErrorCode::ParseError => -32700, 30 | ErrorCode::InvalidRequest => -32600, 31 | ErrorCode::MethodNotFound => -32601, 32 | ErrorCode::InvalidParams => -32602, 33 | ErrorCode::InternalError => -32603, 34 | ErrorCode::ServerError(code) => code, 35 | } 36 | } 37 | 38 | /// Returns human-readable description 39 | pub fn description(&self) -> String { 40 | let desc = match *self { 41 | ErrorCode::ParseError => "Parse error", 42 | ErrorCode::InvalidRequest => "Invalid request", 43 | ErrorCode::MethodNotFound => "Method not found", 44 | ErrorCode::InvalidParams => "Invalid params", 45 | ErrorCode::InternalError => "Internal error", 46 | ErrorCode::ServerError(_) => "Server error", 47 | }; 48 | desc.to_string() 49 | } 50 | } 51 | 52 | impl From for ErrorCode { 53 | fn from(code: i64) -> Self { 54 | match code { 55 | -32700 => ErrorCode::ParseError, 56 | -32600 => ErrorCode::InvalidRequest, 57 | -32601 => ErrorCode::MethodNotFound, 58 | -32602 => ErrorCode::InvalidParams, 59 | -32603 => ErrorCode::InternalError, 60 | code => ErrorCode::ServerError(code), 61 | } 62 | } 63 | } 64 | 65 | impl<'a> Deserialize<'a> for ErrorCode { 66 | fn deserialize(deserializer: D) -> Result 67 | where 68 | D: Deserializer<'a>, 69 | { 70 | let code: i64 = Deserialize::deserialize(deserializer)?; 71 | Ok(ErrorCode::from(code)) 72 | } 73 | } 74 | 75 | impl Serialize for ErrorCode { 76 | fn serialize(&self, serializer: S) -> Result 77 | where 78 | S: Serializer, 79 | { 80 | serializer.serialize_i64(self.code()) 81 | } 82 | } 83 | 84 | /// Error object as defined in Spec 85 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 86 | #[serde(deny_unknown_fields)] 87 | pub struct Error { 88 | /// Code 89 | pub code: ErrorCode, 90 | /// Message 91 | pub message: String, 92 | /// Optional data 93 | #[serde(skip_serializing_if = "Option::is_none")] 94 | pub data: Option, 95 | } 96 | 97 | impl Error { 98 | /// Wraps given `ErrorCode` 99 | pub fn new(code: ErrorCode) -> Self { 100 | Error { 101 | message: code.description(), 102 | code, 103 | data: None, 104 | } 105 | } 106 | 107 | /// Creates new `ParseError` 108 | pub fn parse_error() -> Self { 109 | Self::new(ErrorCode::ParseError) 110 | } 111 | 112 | /// Creates new `InvalidRequest` 113 | pub fn invalid_request() -> Self { 114 | Self::new(ErrorCode::InvalidRequest) 115 | } 116 | 117 | /// Creates new `MethodNotFound` 118 | pub fn method_not_found() -> Self { 119 | Self::new(ErrorCode::MethodNotFound) 120 | } 121 | 122 | /// Creates new `InvalidParams` 123 | pub fn invalid_params(message: M) -> Self 124 | where 125 | M: Into, 126 | { 127 | Error { 128 | code: ErrorCode::InvalidParams, 129 | message: message.into(), 130 | data: None, 131 | } 132 | } 133 | 134 | /// Creates `InvalidParams` for given parameter, with details. 135 | pub fn invalid_params_with_details(message: M, details: T) -> Error 136 | where 137 | M: Into, 138 | T: fmt::Debug, 139 | { 140 | Error { 141 | code: ErrorCode::InvalidParams, 142 | message: format!("Invalid parameters: {}", message.into()), 143 | data: Some(Value::String(format!("{:?}", details))), 144 | } 145 | } 146 | 147 | /// Creates new `InternalError` 148 | pub fn internal_error() -> Self { 149 | Self::new(ErrorCode::InternalError) 150 | } 151 | 152 | /// Creates new `InvalidRequest` with invalid version description 153 | pub fn invalid_version() -> Self { 154 | Error { 155 | code: ErrorCode::InvalidRequest, 156 | message: "Unsupported JSON-RPC protocol version".to_owned(), 157 | data: None, 158 | } 159 | } 160 | } 161 | 162 | impl std::fmt::Display for Error { 163 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 164 | write!(f, "{}: {}", self.code.description(), self.message) 165 | } 166 | } 167 | 168 | impl std::error::Error for Error {} 169 | -------------------------------------------------------------------------------- /core/src/types/id.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc id field 2 | 3 | /// Request Id 4 | #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] 5 | #[serde(deny_unknown_fields)] 6 | #[serde(untagged)] 7 | pub enum Id { 8 | /// No id (notification) 9 | Null, 10 | /// Numeric id 11 | Num(u64), 12 | /// String id 13 | Str(String), 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | use serde_json; 20 | 21 | #[test] 22 | fn id_deserialization() { 23 | let s = r#""2""#; 24 | let deserialized: Id = serde_json::from_str(s).unwrap(); 25 | assert_eq!(deserialized, Id::Str("2".into())); 26 | 27 | let s = r#"2"#; 28 | let deserialized: Id = serde_json::from_str(s).unwrap(); 29 | assert_eq!(deserialized, Id::Num(2)); 30 | 31 | let s = r#""2x""#; 32 | let deserialized: Id = serde_json::from_str(s).unwrap(); 33 | assert_eq!(deserialized, Id::Str("2x".to_owned())); 34 | 35 | let s = r#"[null, 0, 2, "3"]"#; 36 | let deserialized: Vec = serde_json::from_str(s).unwrap(); 37 | assert_eq!( 38 | deserialized, 39 | vec![Id::Null, Id::Num(0), Id::Num(2), Id::Str("3".into())] 40 | ); 41 | } 42 | 43 | #[test] 44 | fn id_serialization() { 45 | let d = vec![ 46 | Id::Null, 47 | Id::Num(0), 48 | Id::Num(2), 49 | Id::Num(3), 50 | Id::Str("3".to_owned()), 51 | Id::Str("test".to_owned()), 52 | ]; 53 | let serialized = serde_json::to_string(&d).unwrap(); 54 | assert_eq!(serialized, r#"[null,0,2,3,"3","test"]"#); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! JSON-RPC types 2 | 3 | pub mod error; 4 | pub mod id; 5 | pub mod params; 6 | pub mod request; 7 | pub mod response; 8 | pub mod version; 9 | 10 | pub use serde_json::to_string; 11 | pub use serde_json::value::to_value; 12 | pub use serde_json::Value; 13 | 14 | pub use self::error::{Error, ErrorCode}; 15 | pub use self::id::Id; 16 | pub use self::params::Params; 17 | pub use self::request::{Call, MethodCall, Notification, Request}; 18 | pub use self::response::{Failure, Output, Response, Success}; 19 | pub use self::version::Version; 20 | -------------------------------------------------------------------------------- /core/src/types/params.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc params field 2 | 3 | use serde::de::DeserializeOwned; 4 | use serde_json::value::from_value; 5 | 6 | use super::{Error, Value}; 7 | 8 | /// Request parameters 9 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 10 | #[serde(deny_unknown_fields)] 11 | #[serde(untagged)] 12 | pub enum Params { 13 | /// No parameters 14 | None, 15 | /// Array of values 16 | Array(Vec), 17 | /// Map of values 18 | Map(serde_json::Map), 19 | } 20 | 21 | impl Params { 22 | /// Parse incoming `Params` into expected types. 23 | pub fn parse(self) -> Result 24 | where 25 | D: DeserializeOwned, 26 | { 27 | let value: Value = self.into(); 28 | from_value(value).map_err(|e| Error::invalid_params(format!("Invalid params: {}.", e))) 29 | } 30 | 31 | /// Check for no params, returns Err if any params 32 | pub fn expect_no_params(self) -> Result<(), Error> { 33 | match self { 34 | Params::None => Ok(()), 35 | Params::Array(ref v) if v.is_empty() => Ok(()), 36 | p => Err(Error::invalid_params_with_details("No parameters were expected", p)), 37 | } 38 | } 39 | } 40 | 41 | impl From for Value { 42 | fn from(params: Params) -> Value { 43 | match params { 44 | Params::Array(vec) => Value::Array(vec), 45 | Params::Map(map) => Value::Object(map), 46 | Params::None => Value::Null, 47 | } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::Params; 54 | use crate::types::{Error, ErrorCode, Value}; 55 | use serde_json; 56 | 57 | #[test] 58 | fn params_deserialization() { 59 | let s = r#"[null, true, -1, 4, 2.3, "hello", [0], {"key": "value"}, []]"#; 60 | let deserialized: Params = serde_json::from_str(s).unwrap(); 61 | 62 | let mut map = serde_json::Map::new(); 63 | map.insert("key".to_string(), Value::String("value".to_string())); 64 | 65 | assert_eq!( 66 | Params::Array(vec![ 67 | Value::Null, 68 | Value::Bool(true), 69 | Value::from(-1), 70 | Value::from(4), 71 | Value::from(2.3), 72 | Value::String("hello".to_string()), 73 | Value::Array(vec![Value::from(0)]), 74 | Value::Object(map), 75 | Value::Array(vec![]), 76 | ]), 77 | deserialized 78 | ); 79 | } 80 | 81 | #[test] 82 | fn should_return_meaningful_error_when_deserialization_fails() { 83 | // given 84 | let s = r#"[1, true]"#; 85 | let params = || serde_json::from_str::(s).unwrap(); 86 | 87 | // when 88 | let v1: Result<(Option, String), Error> = params().parse(); 89 | let v2: Result<(u8, bool, String), Error> = params().parse(); 90 | let err1 = v1.unwrap_err(); 91 | let err2 = v2.unwrap_err(); 92 | 93 | // then 94 | assert_eq!(err1.code, ErrorCode::InvalidParams); 95 | assert_eq!( 96 | err1.message, 97 | "Invalid params: invalid type: boolean `true`, expected a string." 98 | ); 99 | assert_eq!(err1.data, None); 100 | assert_eq!(err2.code, ErrorCode::InvalidParams); 101 | assert_eq!( 102 | err2.message, 103 | "Invalid params: invalid length 2, expected a tuple of size 3." 104 | ); 105 | assert_eq!(err2.data, None); 106 | } 107 | 108 | #[test] 109 | fn single_param_parsed_as_tuple() { 110 | let params: (u64,) = Params::Array(vec![Value::from(1)]).parse().unwrap(); 111 | assert_eq!(params, (1,)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /core/src/types/version.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc version field 2 | use serde::de::{self, Visitor}; 3 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 4 | 5 | use std::fmt; 6 | 7 | /// Protocol Version 8 | #[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)] 9 | pub enum Version { 10 | /// JSONRPC 2.0 11 | V2, 12 | } 13 | 14 | impl Serialize for Version { 15 | fn serialize(&self, serializer: S) -> Result 16 | where 17 | S: Serializer, 18 | { 19 | match *self { 20 | Version::V2 => serializer.serialize_str("2.0"), 21 | } 22 | } 23 | } 24 | 25 | impl<'a> Deserialize<'a> for Version { 26 | fn deserialize(deserializer: D) -> Result 27 | where 28 | D: Deserializer<'a>, 29 | { 30 | deserializer.deserialize_identifier(VersionVisitor) 31 | } 32 | } 33 | 34 | struct VersionVisitor; 35 | 36 | impl<'a> Visitor<'a> for VersionVisitor { 37 | type Value = Version; 38 | 39 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 40 | formatter.write_str("a string") 41 | } 42 | 43 | fn visit_str(self, value: &str) -> Result 44 | where 45 | E: de::Error, 46 | { 47 | match value { 48 | "2.0" => Ok(Version::V2), 49 | _ => Err(de::Error::custom("invalid version")), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | documentation = "https://docs.rs/jsonrpc-derive/" 4 | description = "High level, typed wrapper for `jsonrpc-core`" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | license = "MIT" 8 | name = "jsonrpc-derive" 9 | repository = "https://github.com/paritytech/jsonrpc" 10 | version = "18.0.0" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "1.0", features = ["full", "extra-traits", "visit", "fold"] } 17 | proc-macro2 = "1.0" 18 | quote = "1.0.6" 19 | proc-macro-crate = "0.1.4" 20 | 21 | [dev-dependencies] 22 | assert_matches = "1.3" 23 | jsonrpc-core = { version = "18.0.0", path = "../core" } 24 | jsonrpc-core-client = { version = "18.0.0", path = "../core-client" } 25 | jsonrpc-pubsub = { version = "18.0.0", path = "../pubsub" } 26 | jsonrpc-tcp-server = { version = "18.0.0", path = "../tcp" } 27 | serde = { version = "1.0", features = ["derive"] } 28 | serde_json = "1.0" 29 | trybuild = "1.0" 30 | -------------------------------------------------------------------------------- /derive/examples/client-local.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::{ 2 | futures::{self, FutureExt}, 3 | BoxFuture, IoHandler, Result, 4 | }; 5 | use jsonrpc_core_client::transports::local; 6 | use jsonrpc_derive::rpc; 7 | 8 | /// Rpc trait 9 | #[rpc] 10 | pub trait Rpc { 11 | /// Returns a protocol version 12 | #[rpc(name = "protocolVersion")] 13 | fn protocol_version(&self) -> Result; 14 | 15 | /// Adds two numbers and returns a result 16 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 17 | fn add(&self, a: u64, b: u64) -> Result; 18 | 19 | /// Performs asynchronous operation 20 | #[rpc(name = "callAsync")] 21 | fn call(&self, a: u64) -> BoxFuture>; 22 | } 23 | 24 | struct RpcImpl; 25 | 26 | impl Rpc for RpcImpl { 27 | fn protocol_version(&self) -> Result { 28 | Ok("version1".into()) 29 | } 30 | 31 | fn add(&self, a: u64, b: u64) -> Result { 32 | Ok(a + b) 33 | } 34 | 35 | fn call(&self, _: u64) -> BoxFuture> { 36 | Box::pin(futures::future::ready(Ok("OK".to_owned()))) 37 | } 38 | } 39 | 40 | fn main() { 41 | futures::executor::block_on(async { 42 | let mut io = IoHandler::new(); 43 | io.extend_with(RpcImpl.to_delegate()); 44 | println!("Starting local server"); 45 | let (client, server) = local::connect(io); 46 | let client = use_client(client).fuse(); 47 | let server = server.fuse(); 48 | 49 | futures::pin_mut!(client); 50 | futures::pin_mut!(server); 51 | 52 | futures::select! { 53 | _server = server => {}, 54 | _client = client => {}, 55 | } 56 | }); 57 | } 58 | 59 | async fn use_client(client: RpcClient) { 60 | let res = client.add(5, 6).await.unwrap(); 61 | println!("5 + 6 = {}", res); 62 | } 63 | -------------------------------------------------------------------------------- /derive/examples/generic-trait-bounds.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use jsonrpc_core::{futures::future, BoxFuture, IoHandler, IoHandlerExtension, Result}; 4 | use jsonrpc_derive::rpc; 5 | 6 | // One is both parameter and a result so requires both Serialize and DeserializeOwned 7 | // Two is only a parameter so only requires DeserializeOwned 8 | // Three is only a result so only requires Serialize 9 | #[rpc(server)] 10 | pub trait Rpc { 11 | /// Get One type. 12 | #[rpc(name = "getOne")] 13 | fn one(&self) -> Result; 14 | 15 | /// Adds two numbers and returns a result 16 | #[rpc(name = "setTwo")] 17 | fn set_two(&self, a: Two) -> Result<()>; 18 | 19 | #[rpc(name = "getThree")] 20 | fn get_three(&self) -> Result; 21 | 22 | /// Performs asynchronous operation 23 | #[rpc(name = "beFancy")] 24 | fn call(&self, a: One) -> BoxFuture>; 25 | } 26 | 27 | struct RpcImpl; 28 | 29 | #[derive(Serialize, Deserialize)] 30 | struct InAndOut { 31 | foo: u64, 32 | } 33 | #[derive(Deserialize)] 34 | struct In {} 35 | #[derive(Serialize)] 36 | struct Out {} 37 | 38 | impl Rpc for RpcImpl { 39 | fn one(&self) -> Result { 40 | Ok(InAndOut { foo: 1u64 }) 41 | } 42 | 43 | fn set_two(&self, _x: In) -> Result<()> { 44 | Ok(()) 45 | } 46 | 47 | fn get_three(&self) -> Result { 48 | Ok(Out {}) 49 | } 50 | 51 | fn call(&self, num: InAndOut) -> BoxFuture> { 52 | Box::pin(future::ready(Ok((InAndOut { foo: num.foo + 999 }, num.foo)))) 53 | } 54 | } 55 | 56 | fn main() { 57 | let mut io = IoHandler::new(); 58 | 59 | RpcImpl.to_delegate().augment(&mut io); 60 | } 61 | -------------------------------------------------------------------------------- /derive/examples/generic-trait.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core; 2 | 3 | use jsonrpc_core::{BoxFuture, IoHandler, Result}; 4 | use jsonrpc_derive::rpc; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | /// Get One type. 9 | #[rpc(name = "getOne")] 10 | fn one(&self) -> Result; 11 | 12 | /// Adds two numbers and returns a result 13 | #[rpc(name = "setTwo")] 14 | fn set_two(&self, a: Two) -> Result<()>; 15 | 16 | /// Performs asynchronous operation 17 | #[rpc(name = "beFancy")] 18 | fn call(&self, a: One) -> BoxFuture>; 19 | } 20 | 21 | struct RpcImpl; 22 | 23 | impl Rpc for RpcImpl { 24 | fn one(&self) -> Result { 25 | Ok(100) 26 | } 27 | 28 | fn set_two(&self, x: String) -> Result<()> { 29 | println!("{}", x); 30 | Ok(()) 31 | } 32 | 33 | fn call(&self, num: u64) -> BoxFuture> { 34 | Box::pin(jsonrpc_core::futures::future::ready(Ok((num + 999, "hello".into())))) 35 | } 36 | } 37 | 38 | fn main() { 39 | let mut io = IoHandler::new(); 40 | let rpc = RpcImpl; 41 | 42 | io.extend_with(rpc.to_delegate()) 43 | } 44 | -------------------------------------------------------------------------------- /derive/examples/meta-macros.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use jsonrpc_core::futures::future; 4 | use jsonrpc_core::{BoxFuture, MetaIoHandler, Metadata, Params, Result, Value}; 5 | use jsonrpc_derive::rpc; 6 | 7 | #[derive(Clone)] 8 | struct Meta(String); 9 | impl Metadata for Meta {} 10 | 11 | #[rpc] 12 | pub trait Rpc { 13 | type Metadata; 14 | 15 | /// Get One type. 16 | #[rpc(name = "getOne")] 17 | fn one(&self) -> Result; 18 | 19 | /// Adds two numbers and returns a result. 20 | #[rpc(name = "add")] 21 | fn add(&self, a: u64, b: u64) -> Result; 22 | 23 | /// Multiplies two numbers. Second number is optional. 24 | #[rpc(name = "mul")] 25 | fn mul(&self, a: u64, b: Option) -> Result; 26 | 27 | /// Retrieves and debug prints the underlying `Params` object. 28 | #[rpc(name = "raw", params = "raw")] 29 | fn raw(&self, params: Params) -> Result; 30 | 31 | /// Performs an asynchronous operation. 32 | #[rpc(name = "callAsync")] 33 | fn call(&self, a: u64) -> BoxFuture>; 34 | 35 | /// Performs an asynchronous operation with meta. 36 | #[rpc(meta, name = "callAsyncMeta", alias("callAsyncMetaAlias"))] 37 | fn call_meta(&self, a: Self::Metadata, b: BTreeMap) -> BoxFuture>; 38 | 39 | /// Handles a notification. 40 | #[rpc(name = "notify")] 41 | fn notify(&self, a: u64); 42 | } 43 | 44 | struct RpcImpl; 45 | impl Rpc for RpcImpl { 46 | type Metadata = Meta; 47 | 48 | fn one(&self) -> Result { 49 | Ok(100) 50 | } 51 | 52 | fn add(&self, a: u64, b: u64) -> Result { 53 | Ok(a + b) 54 | } 55 | 56 | fn mul(&self, a: u64, b: Option) -> Result { 57 | Ok(a * b.unwrap_or(1)) 58 | } 59 | 60 | fn raw(&self, params: Params) -> Result { 61 | Ok(format!("Got: {:?}", params)) 62 | } 63 | 64 | fn call(&self, x: u64) -> BoxFuture> { 65 | Box::pin(future::ready(Ok(format!("OK: {}", x)))) 66 | } 67 | 68 | fn call_meta(&self, meta: Self::Metadata, map: BTreeMap) -> BoxFuture> { 69 | Box::pin(future::ready(Ok(format!("From: {}, got: {:?}", meta.0, map)))) 70 | } 71 | 72 | fn notify(&self, a: u64) { 73 | println!("Received `notify` with value: {}", a); 74 | } 75 | } 76 | 77 | fn main() { 78 | let mut io = MetaIoHandler::default(); 79 | let rpc = RpcImpl; 80 | 81 | io.extend_with(rpc.to_delegate()); 82 | 83 | let server = 84 | jsonrpc_tcp_server::ServerBuilder::with_meta_extractor(io, |context: &jsonrpc_tcp_server::RequestContext| { 85 | Meta(format!("{}", context.peer_addr)) 86 | }) 87 | .start(&"0.0.0.0:3030".parse().unwrap()) 88 | .expect("Server must start with no issues"); 89 | 90 | server.wait() 91 | } 92 | -------------------------------------------------------------------------------- /derive/examples/pubsub-macros.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::{atomic, Arc, RwLock}; 3 | use std::thread; 4 | 5 | use jsonrpc_core::{Error, ErrorCode, Result}; 6 | use jsonrpc_derive::rpc; 7 | use jsonrpc_pubsub::typed; 8 | use jsonrpc_pubsub::{PubSubHandler, Session, SubscriptionId}; 9 | 10 | #[rpc] 11 | pub trait Rpc { 12 | type Metadata; 13 | 14 | /// Hello subscription 15 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 16 | fn subscribe(&self, meta: Self::Metadata, subscriber: typed::Subscriber, param: u64); 17 | 18 | /// Unsubscribe from hello subscription. 19 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 20 | fn unsubscribe(&self, meta: Option, subscription: SubscriptionId) -> Result; 21 | } 22 | 23 | #[derive(Default)] 24 | struct RpcImpl { 25 | uid: atomic::AtomicUsize, 26 | active: Arc>>>, 27 | } 28 | impl Rpc for RpcImpl { 29 | type Metadata = Arc; 30 | 31 | fn subscribe(&self, _meta: Self::Metadata, subscriber: typed::Subscriber, param: u64) { 32 | if param != 10 { 33 | subscriber 34 | .reject(Error { 35 | code: ErrorCode::InvalidParams, 36 | message: "Rejecting subscription - invalid parameters provided.".into(), 37 | data: None, 38 | }) 39 | .unwrap(); 40 | return; 41 | } 42 | 43 | let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); 44 | let sub_id = SubscriptionId::Number(id as u64); 45 | let sink = subscriber.assign_id(sub_id.clone()).unwrap(); 46 | self.active.write().unwrap().insert(sub_id, sink); 47 | } 48 | 49 | fn unsubscribe(&self, _meta: Option, id: SubscriptionId) -> Result { 50 | let removed = self.active.write().unwrap().remove(&id); 51 | if removed.is_some() { 52 | Ok(true) 53 | } else { 54 | Err(Error { 55 | code: ErrorCode::InvalidParams, 56 | message: "Invalid subscription.".into(), 57 | data: None, 58 | }) 59 | } 60 | } 61 | } 62 | 63 | fn main() { 64 | let mut io = PubSubHandler::default(); 65 | let rpc = RpcImpl::default(); 66 | let active_subscriptions = rpc.active.clone(); 67 | 68 | thread::spawn(move || loop { 69 | { 70 | let subscribers = active_subscriptions.read().unwrap(); 71 | for sink in subscribers.values() { 72 | let _ = sink.notify(Ok("Hello World!".into())); 73 | } 74 | } 75 | thread::sleep(::std::time::Duration::from_secs(1)); 76 | }); 77 | 78 | io.extend_with(rpc.to_delegate()); 79 | 80 | let server = 81 | jsonrpc_tcp_server::ServerBuilder::with_meta_extractor(io, |context: &jsonrpc_tcp_server::RequestContext| { 82 | Arc::new(Session::new(context.sender.clone())) 83 | }) 84 | .start(&"0.0.0.0:3030".parse().unwrap()) 85 | .expect("Server must start with no issues"); 86 | 87 | server.wait() 88 | } 89 | -------------------------------------------------------------------------------- /derive/examples/std.rs: -------------------------------------------------------------------------------- 1 | //! A simple example 2 | #![deny(missing_docs)] 3 | use jsonrpc_core::futures::{self, future, TryFutureExt}; 4 | use jsonrpc_core::{BoxFuture, IoHandler, Result}; 5 | use jsonrpc_core_client::transports::local; 6 | use jsonrpc_derive::rpc; 7 | 8 | /// Rpc trait 9 | #[rpc] 10 | pub trait Rpc { 11 | /// Returns a protocol version. 12 | #[rpc(name = "protocolVersion")] 13 | fn protocol_version(&self) -> Result; 14 | 15 | /// Adds two numbers and returns a result. 16 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 17 | fn add(&self, a: u64, b: u64) -> Result; 18 | 19 | /// Performs asynchronous operation. 20 | #[rpc(name = "callAsync")] 21 | fn call(&self, a: u64) -> BoxFuture>; 22 | 23 | /// Handles a notification. 24 | #[rpc(name = "notify")] 25 | fn notify(&self, a: u64); 26 | } 27 | 28 | struct RpcImpl; 29 | 30 | impl Rpc for RpcImpl { 31 | fn protocol_version(&self) -> Result { 32 | Ok("version1".into()) 33 | } 34 | 35 | fn add(&self, a: u64, b: u64) -> Result { 36 | Ok(a + b) 37 | } 38 | 39 | fn call(&self, _: u64) -> BoxFuture> { 40 | Box::pin(future::ready(Ok("OK".to_owned()))) 41 | } 42 | 43 | fn notify(&self, a: u64) { 44 | println!("Received `notify` with value: {}", a); 45 | } 46 | } 47 | 48 | fn main() { 49 | let mut io = IoHandler::new(); 50 | io.extend_with(RpcImpl.to_delegate()); 51 | 52 | let (client, server) = local::connect::(io); 53 | let fut = client.add(5, 6).map_ok(|res| println!("5 + 6 = {}", res)); 54 | 55 | futures::executor::block_on(async move { futures::join!(fut, server) }) 56 | .0 57 | .unwrap(); 58 | } 59 | -------------------------------------------------------------------------------- /derive/src/options.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::params_style::ParamStyle; 4 | use crate::rpc_attr::path_eq_str; 5 | 6 | const CLIENT_META_WORD: &str = "client"; 7 | const SERVER_META_WORD: &str = "server"; 8 | const PARAMS_META_KEY: &str = "params"; 9 | 10 | #[derive(Debug)] 11 | pub struct DeriveOptions { 12 | pub enable_client: bool, 13 | pub enable_server: bool, 14 | pub params_style: ParamStyle, 15 | } 16 | 17 | impl DeriveOptions { 18 | pub fn new(enable_client: bool, enable_server: bool, params_style: ParamStyle) -> Self { 19 | DeriveOptions { 20 | enable_client, 21 | enable_server, 22 | params_style, 23 | } 24 | } 25 | 26 | pub fn try_from(args: syn::AttributeArgs) -> Result { 27 | let mut options = DeriveOptions::new(false, false, ParamStyle::default()); 28 | for arg in args { 29 | if let syn::NestedMeta::Meta(meta) = arg { 30 | match meta { 31 | syn::Meta::Path(ref p) => { 32 | match p 33 | .get_ident() 34 | .ok_or(syn::Error::new_spanned( 35 | p, 36 | format!("Expecting identifier `{}` or `{}`", CLIENT_META_WORD, SERVER_META_WORD), 37 | ))? 38 | .to_string() 39 | .as_ref() 40 | { 41 | CLIENT_META_WORD => options.enable_client = true, 42 | SERVER_META_WORD => options.enable_server = true, 43 | _ => {} 44 | }; 45 | } 46 | syn::Meta::NameValue(nv) => { 47 | if path_eq_str(&nv.path, PARAMS_META_KEY) { 48 | if let syn::Lit::Str(ref lit) = nv.lit { 49 | options.params_style = ParamStyle::from_str(&lit.value()) 50 | .map_err(|e| syn::Error::new_spanned(nv.clone(), e))?; 51 | } 52 | } else { 53 | return Err(syn::Error::new_spanned(nv, "Unexpected RPC attribute key")); 54 | } 55 | } 56 | _ => return Err(syn::Error::new_spanned(meta, "Unexpected use of RPC attribute macro")), 57 | } 58 | } 59 | } 60 | if !options.enable_client && !options.enable_server { 61 | // if nothing provided default to both 62 | options.enable_client = true; 63 | options.enable_server = true; 64 | } 65 | if options.enable_server && options.params_style == ParamStyle::Named { 66 | // This is not allowed at this time 67 | panic!("Server code generation only supports `params = \"positional\"` (default) or `params = \"raw\" at this time.") 68 | } 69 | Ok(options) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /derive/src/params_style.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | const POSITIONAL: &str = "positional"; 4 | const NAMED: &str = "named"; 5 | const RAW: &str = "raw"; 6 | 7 | #[derive(Clone, Debug, PartialEq)] 8 | pub enum ParamStyle { 9 | Positional, 10 | Named, 11 | Raw, 12 | } 13 | 14 | impl Default for ParamStyle { 15 | fn default() -> Self { 16 | Self::Positional 17 | } 18 | } 19 | 20 | impl FromStr for ParamStyle { 21 | type Err = String; 22 | 23 | fn from_str(s: &str) -> Result { 24 | match s { 25 | POSITIONAL => Ok(Self::Positional), 26 | NAMED => Ok(Self::Named), 27 | RAW => Ok(Self::Raw), 28 | _ => Err(format!( 29 | "Invalid value for params key. Must be one of [{}, {}, {}]", 30 | POSITIONAL, NAMED, RAW 31 | )), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /derive/tests/client.rs: -------------------------------------------------------------------------------- 1 | use assert_matches::assert_matches; 2 | use jsonrpc_core::futures::{self, FutureExt, TryFutureExt}; 3 | use jsonrpc_core::{IoHandler, Result}; 4 | use jsonrpc_core_client::transports::local; 5 | use jsonrpc_derive::rpc; 6 | 7 | mod client_server { 8 | use super::*; 9 | 10 | #[rpc(params = "positional")] 11 | pub trait Rpc { 12 | #[rpc(name = "add")] 13 | fn add(&self, a: u64, b: u64) -> Result; 14 | 15 | #[rpc(name = "notify")] 16 | fn notify(&self, foo: u64); 17 | } 18 | 19 | struct RpcServer; 20 | 21 | impl Rpc for RpcServer { 22 | fn add(&self, a: u64, b: u64) -> Result { 23 | Ok(a + b) 24 | } 25 | 26 | fn notify(&self, foo: u64) { 27 | println!("received {}", foo); 28 | } 29 | } 30 | 31 | #[test] 32 | fn client_server_roundtrip() { 33 | let mut handler = IoHandler::new(); 34 | handler.extend_with(RpcServer.to_delegate()); 35 | let (client, rpc_client) = local::connect::(handler); 36 | let fut = client 37 | .clone() 38 | .add(3, 4) 39 | .map_ok(move |res| client.notify(res).map(move |_| res)) 40 | .map(|res| { 41 | self::assert_matches!(res, Ok(Ok(7))); 42 | }); 43 | let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); 44 | exec.spawn_ok(async move { 45 | futures::join!(fut, rpc_client).1.unwrap(); 46 | }); 47 | } 48 | } 49 | 50 | mod named_params { 51 | use super::*; 52 | use jsonrpc_core::Params; 53 | use serde_json::json; 54 | 55 | #[rpc(client, params = "named")] 56 | pub trait Rpc { 57 | #[rpc(name = "call_with_named")] 58 | fn call_with_named(&self, number: u64, string: String, json: Value) -> Result; 59 | 60 | #[rpc(name = "notify", params = "raw")] 61 | fn notify(&self, payload: Value); 62 | } 63 | 64 | #[test] 65 | fn client_generates_correct_named_params_payload() { 66 | use jsonrpc_core::futures::{FutureExt, TryFutureExt}; 67 | 68 | let expected = json!({ // key names are derived from function parameter names in the trait 69 | "number": 3, 70 | "string": String::from("test string"), 71 | "json": { 72 | "key": ["value"] 73 | } 74 | }); 75 | 76 | let mut handler = IoHandler::new(); 77 | handler.add_sync_method("call_with_named", |params: Params| Ok(params.into())); 78 | 79 | let (client, rpc_client) = local::connect::(handler); 80 | let fut = client 81 | .clone() 82 | .call_with_named(3, String::from("test string"), json!({"key": ["value"]})) 83 | .map_ok(move |res| client.notify(res.clone()).map(move |_| res)) 84 | .map(move |res| { 85 | self::assert_matches!(res, Ok(Ok(x)) if x == expected); 86 | }); 87 | let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); 88 | exec.spawn_ok(async move { futures::join!(fut, rpc_client).1.unwrap() }); 89 | } 90 | } 91 | 92 | mod raw_params { 93 | use super::*; 94 | use jsonrpc_core::Params; 95 | use serde_json::json; 96 | 97 | #[rpc(client)] 98 | pub trait Rpc { 99 | #[rpc(name = "call_raw", params = "raw")] 100 | fn call_raw_single_param(&self, params: Value) -> Result; 101 | 102 | #[rpc(name = "notify", params = "raw")] 103 | fn notify(&self, payload: Value); 104 | } 105 | 106 | #[test] 107 | fn client_generates_correct_raw_params_payload() { 108 | let expected = json!({ 109 | "sub_object": { 110 | "key": ["value"] 111 | } 112 | }); 113 | 114 | let mut handler = IoHandler::new(); 115 | handler.add_sync_method("call_raw", |params: Params| Ok(params.into())); 116 | 117 | let (client, rpc_client) = local::connect::(handler); 118 | let fut = client 119 | .clone() 120 | .call_raw_single_param(expected.clone()) 121 | .map_ok(move |res| client.notify(res.clone()).map(move |_| res)) 122 | .map(move |res| { 123 | self::assert_matches!(res, Ok(Ok(x)) if x == expected); 124 | }); 125 | let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); 126 | exec.spawn_ok(async move { futures::join!(fut, rpc_client).1.unwrap() }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /derive/tests/pubsub-macros.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core; 2 | use jsonrpc_pubsub; 3 | use serde_json; 4 | #[macro_use] 5 | extern crate jsonrpc_derive; 6 | 7 | use jsonrpc_core::futures::channel::mpsc; 8 | use jsonrpc_pubsub::typed::Subscriber; 9 | use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; 10 | use std::sync::Arc; 11 | 12 | pub enum MyError {} 13 | impl From for jsonrpc_core::Error { 14 | fn from(_e: MyError) -> Self { 15 | unreachable!() 16 | } 17 | } 18 | 19 | type Result = ::std::result::Result; 20 | 21 | #[rpc] 22 | pub trait Rpc { 23 | type Metadata; 24 | 25 | /// Hello subscription. 26 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_alias"))] 27 | fn subscribe(&self, a: Self::Metadata, b: Subscriber, c: u32, d: Option); 28 | 29 | /// Hello subscription through different method. 30 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe_second")] 31 | fn subscribe_second(&self, a: Self::Metadata, b: Subscriber, e: String); 32 | 33 | /// Unsubscribe from hello subscription. 34 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 35 | fn unsubscribe(&self, a: Option, b: SubscriptionId) -> Result; 36 | 37 | /// A regular rpc method alongside pubsub. 38 | #[rpc(name = "add")] 39 | fn add(&self, a: u64, b: u64) -> Result; 40 | 41 | /// A notification alongside pubsub. 42 | #[rpc(name = "notify")] 43 | fn notify(&self, a: u64); 44 | } 45 | 46 | #[derive(Default)] 47 | struct RpcImpl; 48 | 49 | impl Rpc for RpcImpl { 50 | type Metadata = Metadata; 51 | 52 | fn subscribe(&self, _meta: Self::Metadata, subscriber: Subscriber, _pre: u32, _trailing: Option) { 53 | let _sink = subscriber.assign_id(SubscriptionId::Number(5)); 54 | } 55 | 56 | fn subscribe_second(&self, _meta: Self::Metadata, subscriber: Subscriber, _e: String) { 57 | let _sink = subscriber.assign_id(SubscriptionId::Number(6)); 58 | } 59 | 60 | fn unsubscribe(&self, _meta: Option, _id: SubscriptionId) -> Result { 61 | Ok(true) 62 | } 63 | 64 | fn add(&self, a: u64, b: u64) -> Result { 65 | Ok(a + b) 66 | } 67 | 68 | fn notify(&self, a: u64) { 69 | println!("Received `notify` with value: {}", a); 70 | } 71 | } 72 | 73 | #[derive(Clone, Default)] 74 | struct Metadata; 75 | impl jsonrpc_core::Metadata for Metadata {} 76 | impl PubSubMetadata for Metadata { 77 | fn session(&self) -> Option> { 78 | let (tx, _rx) = mpsc::unbounded(); 79 | Some(Arc::new(Session::new(tx))) 80 | } 81 | } 82 | 83 | #[test] 84 | fn test_invalid_trailing_pubsub_params() { 85 | let mut io = PubSubHandler::default(); 86 | let rpc = RpcImpl::default(); 87 | io.extend_with(rpc.to_delegate()); 88 | 89 | // when 90 | let meta = Metadata; 91 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"hello_subscribe","params":[]}"#; 92 | let res = io.handle_request_sync(req, meta); 93 | let expected = r#"{ 94 | "jsonrpc": "2.0", 95 | "error": { 96 | "code": -32602, 97 | "message": "`params` should have at least 1 argument(s)" 98 | }, 99 | "id": 1 100 | }"#; 101 | 102 | let expected: jsonrpc_core::Response = serde_json::from_str(expected).unwrap(); 103 | let result: jsonrpc_core::Response = serde_json::from_str(&res.unwrap()).unwrap(); 104 | assert_eq!(expected, result); 105 | } 106 | 107 | #[test] 108 | fn test_subscribe_with_alias() { 109 | let mut io = PubSubHandler::default(); 110 | let rpc = RpcImpl::default(); 111 | io.extend_with(rpc.to_delegate()); 112 | 113 | // when 114 | let meta = Metadata; 115 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"hello_alias","params":[1]}"#; 116 | let res = io.handle_request_sync(req, meta); 117 | let expected = r#"{ 118 | "jsonrpc": "2.0", 119 | "result": 5, 120 | "id": 1 121 | }"#; 122 | 123 | let expected: jsonrpc_core::Response = serde_json::from_str(expected).unwrap(); 124 | let result: jsonrpc_core::Response = serde_json::from_str(&res.unwrap()).unwrap(); 125 | assert_eq!(expected, result); 126 | } 127 | 128 | #[test] 129 | fn test_subscribe_alternate_method() { 130 | let mut io = PubSubHandler::default(); 131 | let rpc = RpcImpl::default(); 132 | io.extend_with(rpc.to_delegate()); 133 | 134 | // when 135 | let meta = Metadata; 136 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"hello_subscribe_second","params":["Data"]}"#; 137 | let res = io.handle_request_sync(req, meta); 138 | let expected = r#"{ 139 | "jsonrpc": "2.0", 140 | "result": 6, 141 | "id": 1 142 | }"#; 143 | 144 | let expected: jsonrpc_core::Response = serde_json::from_str(expected).unwrap(); 145 | let result: jsonrpc_core::Response = serde_json::from_str(&res.unwrap()).unwrap(); 146 | assert_eq!(expected, result); 147 | } 148 | -------------------------------------------------------------------------------- /derive/tests/run-pass/client_only.rs: -------------------------------------------------------------------------------- 1 | extern crate jsonrpc_core; 2 | extern crate jsonrpc_core_client; 3 | #[macro_use] 4 | extern crate jsonrpc_derive; 5 | 6 | use jsonrpc_core::IoHandler; 7 | use jsonrpc_core::futures::{self, TryFutureExt}; 8 | use jsonrpc_core_client::transports::local; 9 | 10 | #[rpc(client)] 11 | pub trait Rpc { 12 | /// Returns a protocol version 13 | #[rpc(name = "protocolVersion")] 14 | fn protocol_version(&self) -> Result; 15 | 16 | /// Adds two numbers and returns a result 17 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 18 | fn add(&self, a: u64, b: u64) -> Result; 19 | } 20 | 21 | fn main() { 22 | let fut = { 23 | let handler = IoHandler::new(); 24 | let (client, _rpc_client) = local::connect::(handler); 25 | client 26 | .add(5, 6) 27 | .map_ok(|res| println!("5 + 6 = {}", res)) 28 | }; 29 | let _ = futures::executor::block_on(fut); 30 | } 31 | -------------------------------------------------------------------------------- /derive/tests/run-pass/client_with_generic_trait_bounds.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::futures::future; 2 | use jsonrpc_core::{IoHandler, Result, BoxFuture}; 3 | use jsonrpc_derive::rpc; 4 | use std::collections::BTreeMap; 5 | 6 | #[rpc] 7 | pub trait Rpc 8 | where 9 | One: Ord, 10 | Two: Ord + Eq, 11 | { 12 | /// Adds two numbers and returns a result 13 | #[rpc(name = "setTwo")] 14 | fn set_two(&self, a: Two) -> Result>; 15 | 16 | /// Performs asynchronous operation 17 | #[rpc(name = "beFancy")] 18 | fn call(&self, a: One) -> BoxFuture>; 19 | } 20 | 21 | struct RpcImpl; 22 | 23 | impl Rpc for RpcImpl { 24 | fn set_two(&self, x: String) -> Result> { 25 | println!("{}", x); 26 | Ok(Default::default()) 27 | } 28 | 29 | fn call(&self, num: u64) -> BoxFuture> { 30 | Box::pin(future::ready(Ok((num + 999, "hello".into())))) 31 | } 32 | } 33 | 34 | fn main() { 35 | let mut io = IoHandler::new(); 36 | let rpc = RpcImpl; 37 | 38 | io.extend_with(rpc.to_delegate()) 39 | } 40 | -------------------------------------------------------------------------------- /derive/tests/run-pass/pubsub-dependency-not-required-for-vanilla-rpc.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | use jsonrpc_core::{Result, IoHandler}; 3 | 4 | #[rpc] 5 | pub trait Rpc { 6 | /// Returns a protocol version 7 | #[rpc(name = "protocolVersion")] 8 | fn protocol_version(&self) -> Result; 9 | 10 | /// Adds two numbers and returns a result 11 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 12 | fn add(&self, a: u64, b: u64) -> Result; 13 | } 14 | 15 | struct RpcImpl; 16 | 17 | impl Rpc for RpcImpl { 18 | fn protocol_version(&self) -> Result { 19 | Ok("version1".into()) 20 | } 21 | 22 | fn add(&self, a: u64, b: u64) -> Result { 23 | Ok(a + b) 24 | } 25 | } 26 | 27 | fn main() { 28 | let mut io = IoHandler::new(); 29 | let rpc = RpcImpl; 30 | 31 | io.extend_with(rpc.to_delegate()) 32 | } 33 | -------------------------------------------------------------------------------- /derive/tests/run-pass/pubsub-subscription-generic-type-with-deserialize.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | use std::sync::Arc; 5 | use jsonrpc_core::Result; 6 | use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId, Session, PubSubHandler}; 7 | 8 | #[derive(Serialize, Deserialize)] 9 | pub struct Wrapper { 10 | inner: T, 11 | inner2: U, 12 | } 13 | 14 | #[rpc] 15 | pub trait Rpc { 16 | type Metadata; 17 | 18 | /// Hello subscription 19 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 20 | fn subscribe(&self, _: Self::Metadata, _: Subscriber>); 21 | 22 | /// Unsubscribe from hello subscription. 23 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 24 | fn unsubscribe(&self, a: Option, b: SubscriptionId) -> Result; 25 | } 26 | 27 | #[derive(Serialize, Deserialize)] 28 | struct SerializeAndDeserialize { 29 | foo: String, 30 | } 31 | 32 | struct RpcImpl; 33 | impl Rpc for RpcImpl { 34 | type Metadata = Arc; 35 | 36 | fn subscribe(&self, _: Self::Metadata, _: Subscriber>) { 37 | unimplemented!(); 38 | } 39 | 40 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result { 41 | unimplemented!(); 42 | } 43 | } 44 | 45 | fn main() { 46 | let mut io = PubSubHandler::default(); 47 | let rpc = RpcImpl; 48 | io.extend_with(rpc.to_delegate()); 49 | } 50 | -------------------------------------------------------------------------------- /derive/tests/run-pass/pubsub-subscription-type-with-deserialize.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | use std::sync::Arc; 5 | use jsonrpc_core::Result; 6 | use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId, Session, PubSubHandler}; 7 | 8 | #[rpc] 9 | pub trait Rpc { 10 | type Metadata; 11 | 12 | /// Hello subscription 13 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 14 | fn subscribe(&self, _: Self::Metadata, _: Subscriber); 15 | 16 | /// Unsubscribe from hello subscription. 17 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 18 | fn unsubscribe(&self, a: Option, b: SubscriptionId) -> Result; 19 | } 20 | 21 | #[derive(Serialize, Deserialize)] 22 | struct SerializeAndDeserialize { 23 | foo: String, 24 | } 25 | 26 | struct RpcImpl; 27 | impl Rpc for RpcImpl { 28 | type Metadata = Arc; 29 | 30 | fn subscribe(&self, _: Self::Metadata, _: Subscriber) { 31 | unimplemented!(); 32 | } 33 | 34 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result { 35 | unimplemented!(); 36 | } 37 | } 38 | 39 | fn main() { 40 | let mut io = PubSubHandler::default(); 41 | let rpc = RpcImpl; 42 | io.extend_with(rpc.to_delegate()); 43 | } 44 | -------------------------------------------------------------------------------- /derive/tests/run-pass/pubsub-subscription-type-without-deserialize.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | use serde::Serialize; 3 | 4 | use std::sync::Arc; 5 | use jsonrpc_core::Result; 6 | use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId, Session, PubSubHandler}; 7 | 8 | #[rpc(server)] 9 | pub trait Rpc { 10 | type Metadata; 11 | 12 | /// Hello subscription 13 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 14 | fn subscribe(&self, _: Self::Metadata, _: Subscriber); 15 | 16 | /// Unsubscribe from hello subscription. 17 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 18 | fn unsubscribe(&self, a: Option, b: SubscriptionId) -> Result; 19 | } 20 | 21 | // One way serialization 22 | #[derive(Serialize)] 23 | struct SerializeOnly { 24 | foo: String, 25 | } 26 | 27 | struct RpcImpl; 28 | impl Rpc for RpcImpl { 29 | type Metadata = Arc; 30 | 31 | fn subscribe(&self, _: Self::Metadata, _: Subscriber) { 32 | unimplemented!(); 33 | } 34 | 35 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result { 36 | unimplemented!(); 37 | } 38 | } 39 | 40 | fn main() { 41 | let mut io = PubSubHandler::default(); 42 | let rpc = RpcImpl; 43 | io.extend_with(rpc.to_delegate()); 44 | } 45 | -------------------------------------------------------------------------------- /derive/tests/run-pass/server_only.rs: -------------------------------------------------------------------------------- 1 | extern crate jsonrpc_core; 2 | #[macro_use] 3 | extern crate jsonrpc_derive; 4 | 5 | use jsonrpc_core::{Result, IoHandler}; 6 | 7 | #[rpc(server)] 8 | pub trait Rpc { 9 | /// Returns a protocol version 10 | #[rpc(name = "protocolVersion")] 11 | fn protocol_version(&self) -> Result; 12 | 13 | /// Adds two numbers and returns a result 14 | #[rpc(name = "add", alias("callAsyncMetaAlias"))] 15 | fn add(&self, a: u64, b: u64) -> Result; 16 | } 17 | 18 | struct RpcImpl; 19 | 20 | impl Rpc for RpcImpl { 21 | fn protocol_version(&self) -> Result { 22 | Ok("version1".into()) 23 | } 24 | 25 | fn add(&self, a: u64, b: u64) -> Result { 26 | Ok(a + b) 27 | } 28 | } 29 | 30 | fn main() { 31 | let mut io = IoHandler::new(); 32 | let rpc = RpcImpl; 33 | 34 | io.extend_with(rpc.to_delegate()) 35 | } 36 | -------------------------------------------------------------------------------- /derive/tests/trailing.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::{IoHandler, Response, Result}; 2 | use jsonrpc_derive::rpc; 3 | use serde_json; 4 | 5 | #[rpc] 6 | pub trait Rpc { 7 | /// Multiplies two numbers. Second number is optional. 8 | #[rpc(name = "mul")] 9 | fn mul(&self, a: u64, b: Option) -> Result; 10 | 11 | /// Echos back the message, example of a single param trailing 12 | #[rpc(name = "echo")] 13 | fn echo(&self, a: Option) -> Result; 14 | 15 | /// Adds up to three numbers and returns a result 16 | #[rpc(name = "add_multi")] 17 | fn add_multi(&self, a: Option, b: Option, c: Option) -> Result; 18 | } 19 | 20 | #[derive(Default)] 21 | struct RpcImpl; 22 | 23 | impl Rpc for RpcImpl { 24 | fn mul(&self, a: u64, b: Option) -> Result { 25 | Ok(a * b.unwrap_or(1)) 26 | } 27 | 28 | fn echo(&self, x: Option) -> Result { 29 | Ok(x.unwrap_or("".into())) 30 | } 31 | 32 | fn add_multi(&self, a: Option, b: Option, c: Option) -> Result { 33 | Ok(a.unwrap_or_default() + b.unwrap_or_default() + c.unwrap_or_default()) 34 | } 35 | } 36 | 37 | #[test] 38 | fn should_accept_trailing_param() { 39 | let mut io = IoHandler::new(); 40 | let rpc = RpcImpl::default(); 41 | io.extend_with(rpc.to_delegate()); 42 | 43 | // when 44 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"mul","params":[2, 2]}"#; 45 | let res = io.handle_request_sync(req); 46 | 47 | // then 48 | let result: Response = serde_json::from_str(&res.unwrap()).unwrap(); 49 | assert_eq!( 50 | result, 51 | serde_json::from_str( 52 | r#"{ 53 | "jsonrpc": "2.0", 54 | "result": 4, 55 | "id": 1 56 | }"# 57 | ) 58 | .unwrap() 59 | ); 60 | } 61 | 62 | #[test] 63 | fn should_accept_missing_trailing_param() { 64 | let mut io = IoHandler::new(); 65 | let rpc = RpcImpl::default(); 66 | io.extend_with(rpc.to_delegate()); 67 | 68 | // when 69 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"mul","params":[2]}"#; 70 | let res = io.handle_request_sync(req); 71 | 72 | // then 73 | let result: Response = serde_json::from_str(&res.unwrap()).unwrap(); 74 | assert_eq!( 75 | result, 76 | serde_json::from_str( 77 | r#"{ 78 | "jsonrpc": "2.0", 79 | "result": 2, 80 | "id": 1 81 | }"# 82 | ) 83 | .unwrap() 84 | ); 85 | } 86 | 87 | #[test] 88 | fn should_accept_single_trailing_param() { 89 | let mut io = IoHandler::new(); 90 | let rpc = RpcImpl::default(); 91 | io.extend_with(rpc.to_delegate()); 92 | 93 | // when 94 | let req1 = r#"{"jsonrpc":"2.0","id":1,"method":"echo","params":["hello"]}"#; 95 | let req2 = r#"{"jsonrpc":"2.0","id":1,"method":"echo","params":[]}"#; 96 | 97 | let res1 = io.handle_request_sync(req1); 98 | let res2 = io.handle_request_sync(req2); 99 | 100 | // then 101 | let result1: Response = serde_json::from_str(&res1.unwrap()).unwrap(); 102 | assert_eq!( 103 | result1, 104 | serde_json::from_str( 105 | r#"{ 106 | "jsonrpc": "2.0", 107 | "result": "hello", 108 | "id": 1 109 | }"# 110 | ) 111 | .unwrap() 112 | ); 113 | 114 | let result2: Response = serde_json::from_str(&res2.unwrap()).unwrap(); 115 | assert_eq!( 116 | result2, 117 | serde_json::from_str( 118 | r#"{ 119 | "jsonrpc": "2.0", 120 | "result": "", 121 | "id": 1 122 | }"# 123 | ) 124 | .unwrap() 125 | ); 126 | } 127 | 128 | #[test] 129 | fn should_accept_multiple_trailing_params() { 130 | let mut io = IoHandler::new(); 131 | let rpc = RpcImpl::default(); 132 | io.extend_with(rpc.to_delegate()); 133 | 134 | // when 135 | let req1 = r#"{"jsonrpc":"2.0","id":1,"method":"add_multi","params":[]}"#; 136 | let req2 = r#"{"jsonrpc":"2.0","id":1,"method":"add_multi","params":[1]}"#; 137 | let req3 = r#"{"jsonrpc":"2.0","id":1,"method":"add_multi","params":[1, 2]}"#; 138 | let req4 = r#"{"jsonrpc":"2.0","id":1,"method":"add_multi","params":[1, 2, 3]}"#; 139 | 140 | let res1 = io.handle_request_sync(req1); 141 | let res2 = io.handle_request_sync(req2); 142 | let res3 = io.handle_request_sync(req3); 143 | let res4 = io.handle_request_sync(req4); 144 | 145 | // then 146 | let result1: Response = serde_json::from_str(&res1.unwrap()).unwrap(); 147 | assert_eq!( 148 | result1, 149 | serde_json::from_str( 150 | r#"{ 151 | "jsonrpc": "2.0", 152 | "result": 0, 153 | "id": 1 154 | }"# 155 | ) 156 | .unwrap() 157 | ); 158 | 159 | let result2: Response = serde_json::from_str(&res2.unwrap()).unwrap(); 160 | assert_eq!( 161 | result2, 162 | serde_json::from_str( 163 | r#"{ 164 | "jsonrpc": "2.0", 165 | "result": 1, 166 | "id": 1 167 | }"# 168 | ) 169 | .unwrap() 170 | ); 171 | 172 | let result3: Response = serde_json::from_str(&res3.unwrap()).unwrap(); 173 | assert_eq!( 174 | result3, 175 | serde_json::from_str( 176 | r#"{ 177 | "jsonrpc": "2.0", 178 | "result": 3, 179 | "id": 1 180 | }"# 181 | ) 182 | .unwrap() 183 | ); 184 | 185 | let result4: Response = serde_json::from_str(&res4.unwrap()).unwrap(); 186 | assert_eq!( 187 | result4, 188 | serde_json::from_str( 189 | r#"{ 190 | "jsonrpc": "2.0", 191 | "result": 6, 192 | "id": 1 193 | }"# 194 | ) 195 | .unwrap() 196 | ); 197 | } 198 | -------------------------------------------------------------------------------- /derive/tests/trybuild.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn compile_test() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/*.rs"); 5 | t.pass("tests/run-pass/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-meta-list-names.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc(name = "protocolVersion", Xalias("alias"))] 7 | fn protocol_version(&self) -> Result; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-meta-list-names.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xalias'. Expected 'alias' 2 | --> $DIR/attr-invalid-meta-list-names.rs:5:2 3 | | 4 | 5 | / /// Returns a protocol version 5 | 6 | | #[rpc(name = "protocolVersion", Xalias("alias"))] 6 | 7 | | fn protocol_version(&self) -> Result; 7 | | |_________________________________________________^ 8 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-meta-words.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc(name = "protocolVersion", Xmeta)] 7 | fn protocol_version(&self) -> Result; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-meta-words.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xmeta'. Expected 'meta, raw_params' 2 | --> $DIR/attr-invalid-meta-words.rs:5:2 3 | | 4 | 5 | / /// Returns a protocol version 5 | 6 | | #[rpc(name = "protocolVersion", Xmeta)] 6 | 7 | | fn protocol_version(&self) -> Result; 7 | | |_________________________________________________^ 8 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-name-values.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc(Xname = "protocolVersion")] 7 | fn protocol_version(&self) -> Result; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-invalid-name-values.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xname'. Expected 'name, returns, params' 2 | --> $DIR/attr-invalid-name-values.rs:5:2 3 | | 4 | 5 | / /// Returns a protocol version 5 | 6 | | #[rpc(Xname = "protocolVersion")] 6 | 7 | | fn protocol_version(&self) -> Result; 7 | | |_________________________________________________^ 8 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-missing-rpc-name.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc] 7 | fn protocol_version(&self) -> Result; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-missing-rpc-name.stderr: -------------------------------------------------------------------------------- 1 | error: rpc attribute should have a name e.g. `name = "method_name"` 2 | --> $DIR/attr-missing-rpc-name.rs:5:2 3 | | 4 | 5 | / /// Returns a protocol version 5 | 6 | | #[rpc] 6 | 7 | | fn protocol_version(&self) -> Result; 7 | | |_________________________________________________^ 8 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-named-params-on-server.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc(name = "add", params = "named")] 7 | fn add(&self, a: u32, b: u32) -> Result; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /derive/tests/ui/attr-named-params-on-server.stderr: -------------------------------------------------------------------------------- 1 | error: `params = "named"` can only be used to generate a client (on a trait annotated with #[rpc(client)]). At this time the server does not support named parameters. 2 | --> $DIR/attr-named-params-on-server.rs:4:1 3 | | 4 | 4 | / pub trait Rpc { 5 | 5 | | /// Returns a protocol version 6 | 6 | | #[rpc(name = "add", params = "named")] 7 | 7 | | fn add(&self, a: u32, b: u32) -> Result; 8 | 8 | | } 9 | | |_^ 10 | -------------------------------------------------------------------------------- /derive/tests/ui/multiple-rpc-attributes.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Returns a protocol version 6 | #[rpc(name = "protocolVersion")] 7 | #[rpc(name = "protocolVersionAgain")] 8 | fn protocol_version(&self) -> Result; 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /derive/tests/ui/multiple-rpc-attributes.stderr: -------------------------------------------------------------------------------- 1 | error: Expected only a single rpc attribute per method 2 | --> $DIR/multiple-rpc-attributes.rs:5:2 3 | | 4 | 5 | / /// Returns a protocol version 5 | 6 | | #[rpc(name = "protocolVersion")] 6 | 7 | | #[rpc(name = "protocolVersionAgain")] 7 | 8 | | fn protocol_version(&self) -> Result; 8 | | |_________________________________________________^ 9 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-both-subscribe-and-unsubscribe.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", subscribe, unsubscribe, name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-both-subscribe-and-unsubscribe.stderr: -------------------------------------------------------------------------------- 1 | error: pubsub attribute annotated with both subscribe and unsubscribe 2 | --> $DIR/attr-both-subscribe-and-unsubscribe.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscription = "hello", subscribe, unsubscribe, name = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-meta-list-names.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", Xalias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-meta-list-names.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xalias'. Expected 'alias' 2 | --> $DIR/attr-invalid-meta-list-names.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", Xalias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-meta-words.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", Xsubscribe, name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-meta-words.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xsubscribe'. Expected 'subscribe, unsubscribe' 2 | --> $DIR/attr-invalid-meta-words.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscription = "hello", Xsubscribe, name = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-name-values.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(Xsubscription = "hello", subscribe, Xname = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-invalid-name-values.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute parameter(s): 'Xsubscription, Xname'. Expected 'subscription, name' 2 | --> $DIR/attr-invalid-name-values.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(Xsubscription = "hello", subscribe, Xname = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-missing-subscription-name.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscribe, name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-missing-subscription-name.stderr: -------------------------------------------------------------------------------- 1 | error: pubsub attribute should have a subscription name 2 | --> $DIR/attr-missing-subscription-name.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscribe, name = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-neither-subscribe-or-unsubscribe.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Unsubscribe from hello subscription. 15 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 16 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/attr-neither-subscribe-or-unsubscribe.stderr: -------------------------------------------------------------------------------- 1 | error: pubsub attribute not annotated with either subscribe or unsubscribe 2 | --> $DIR/attr-neither-subscribe-or-unsubscribe.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscription = "hello", name = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/missing-subscribe.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | // note that a subscribe method is missing 11 | 12 | /// Unsubscribe from hello subscription. 13 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 14 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/missing-subscribe.stderr: -------------------------------------------------------------------------------- 1 | error: subscription 'hello'. Can't find subscribe method, expected a method annotated with `subscribe` e.g. `#[pubsub(subscription = "hello", subscribe, name = "hello_subscribe")]` 2 | --> $DIR/missing-subscribe.rs:12:2 3 | | 4 | 12 | /// Unsubscribe from hello subscription. 5 | | _____^ 6 | 13 | | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 7 | 14 | | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 8 | | |________________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/missing-unsubscribe.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | // note that the unsubscribe method is missing 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/missing-unsubscribe.stderr: -------------------------------------------------------------------------------- 1 | error: subscription 'hello'. Can't find unsubscribe method, expected a method annotated with `unsubscribe` e.g. `#[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")]` 2 | --> $DIR/missing-unsubscribe.rs:10:2 3 | | 4 | 10 | /// Hello subscription 5 | | _____^ 6 | 11 | | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 7 | 12 | | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 8 | | |_________________________________________________________________________________^ 9 | 10 | error: aborting due to previous error 11 | 12 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/mixed-subscriber-signatures.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate jsonrpc_derive; 3 | extern crate jsonrpc_core; 4 | extern crate jsonrpc_pubsub; 5 | 6 | #[rpc] 7 | pub trait Rpc { 8 | type Metadata; 9 | 10 | /// Hello subscription 11 | #[pubsub(subscription = "hello", subscribe, name = "hello_subscribe", alias("hello_sub"))] 12 | fn subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 13 | 14 | /// Hello subscription with probably different impl and mismatched Subscriber type 15 | #[pubsub(subscription = "hello", subscribe, name = "hello_anotherSubscribe")] 16 | fn another_subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 17 | 18 | /// Unsubscribe from hello subscription. 19 | #[pubsub(subscription = "hello", unsubscribe, name = "hello_unsubscribe")] 20 | fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; 21 | // note that the unsubscribe method is missing 22 | } 23 | 24 | fn main() {} 25 | -------------------------------------------------------------------------------- /derive/tests/ui/pubsub/mixed-subscriber-signatures.stderr: -------------------------------------------------------------------------------- 1 | error: Inconsistent signature for 'Subscriber' argument: typed :: Subscriber < usize >, previously defined: typed :: Subscriber < String > 2 | --> $DIR/mixed-subscriber-signatures.rs:16:52 3 | | 4 | 16 | fn another_subscribe(&self, _: Self::Metadata, _: typed::Subscriber, _: u64); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /derive/tests/ui/too-many-params.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc] 4 | pub trait Rpc { 5 | /// Has too many params 6 | #[rpc(name = "tooManyParams")] 7 | fn to_many_params( 8 | &self, 9 | a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, g: u64, h: u64, i: u64, j: u64, 10 | k: u64, l: u64, m: u64, n: u64, o: u64, p: u64, q: u64, 11 | ) -> Result; 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /derive/tests/ui/too-many-params.stderr: -------------------------------------------------------------------------------- 1 | error: Maximum supported number of params is 16 2 | --> $DIR/too-many-params.rs:5:2 3 | | 4 | 5 | / /// Has too many params 5 | 6 | | #[rpc(name = "tooManyParams")] 6 | 7 | | fn to_many_params( 7 | 8 | | &self, 8 | 9 | | a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, g: u64, h: u64, i: u64, j: u64, 9 | 10 | | k: u64, l: u64, m: u64, n: u64, o: u64, p: u64, q: u64, 10 | 11 | | ) -> Result; 11 | | |________________________^ 12 | -------------------------------------------------------------------------------- /derive/tests/ui/trait-attr-named-params-on-server.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_derive::rpc; 2 | 3 | #[rpc(server, params = "named")] 4 | pub trait Rpc { 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /derive/tests/ui/trait-attr-named-params-on-server.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/trait-attr-named-params-on-server.rs:3:1 3 | | 4 | 3 | #[rpc(server, params = "named")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: Server code generation only supports `params = "positional"` (default) or `params = "raw" at this time. 8 | -------------------------------------------------------------------------------- /http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Rust http server using JSONRPC 2.0." 4 | documentation = "https://docs.rs/jsonrpc-http-server/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "server"] 8 | license = "MIT" 9 | name = "jsonrpc-http-server" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | [dependencies] 14 | futures = "0.3" 15 | hyper = { version = "0.14", features = ["http1", "tcp", "server", "stream"] } 16 | jsonrpc-core = { version = "18.0.0", path = "../core" } 17 | jsonrpc-server-utils = { version = "18.0.0", path = "../server-utils" } 18 | log = "0.4" 19 | net2 = "0.2" 20 | parking_lot = "0.11.0" 21 | unicase = "2.0" 22 | 23 | [dev-dependencies] 24 | env_logger = "0.7" 25 | 26 | [badges] 27 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 28 | -------------------------------------------------------------------------------- /http/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-http-server 2 | Rust http server using JSON-RPC 2.0. 3 | 4 | [Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_http_server/index.html) 5 | 6 | ## Example 7 | 8 | `Cargo.toml` 9 | 10 | ``` 11 | [dependencies] 12 | jsonrpc-http-server = "18.0" 13 | ``` 14 | 15 | `main.rs` 16 | 17 | ```rust 18 | use jsonrpc_http_server::jsonrpc_core::{IoHandler, Value, Params}; 19 | use jsonrpc_http_server::ServerBuilder; 20 | 21 | fn main() { 22 | let mut io = IoHandler::default(); 23 | io.add_method("say_hello", |_params: Params| async { 24 | Ok(Value::String("hello".to_owned())) 25 | }); 26 | 27 | let server = ServerBuilder::new(io) 28 | .threads(3) 29 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 30 | .unwrap(); 31 | 32 | server.wait(); 33 | } 34 | ``` 35 | You can now test the above server by running `cargo run` in one terminal, and from another terminal issue the following POST request to your server: 36 | ``` 37 | $ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "say_hello", "id":123 }' 127.0.0.1:3030 38 | ``` 39 | to which the server will respond with the following: 40 | ``` 41 | {"jsonrpc":"2.0","result":"hello","id":123} 42 | ``` 43 | If you omit any of the fields above, or invoke a different method you will get an informative error message: 44 | ``` 45 | $ curl -X POST -H "Content-Type: application/json" -d '{"method": "say_hello", "id":123 }' 127.0.0.1:3030 46 | {"error":{"code":-32600,"message":"Unsupported JSON-RPC protocol version"},"id":123} 47 | $ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "say_bye", "id":123 }' 127.0.0.1:3030 48 | {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":123} 49 | ``` 50 | -------------------------------------------------------------------------------- /http/examples/http_async.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_http_server::jsonrpc_core::*; 2 | use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; 3 | 4 | fn main() { 5 | let mut io = IoHandler::default(); 6 | io.add_method("say_hello", |_params| { 7 | futures::future::ready(Ok(Value::String("hello".to_owned()))) 8 | }); 9 | 10 | let server = ServerBuilder::new(io) 11 | .cors(DomainsValidation::AllowOnly(vec![AccessControlAllowOrigin::Null])) 12 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 13 | .expect("Unable to start RPC server"); 14 | 15 | server.wait(); 16 | } 17 | -------------------------------------------------------------------------------- /http/examples/http_meta.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_http_server::jsonrpc_core::*; 2 | use jsonrpc_http_server::{cors::AccessControlAllowHeaders, hyper, RestApi, ServerBuilder}; 3 | 4 | #[derive(Default, Clone)] 5 | struct Meta { 6 | auth: Option, 7 | } 8 | 9 | impl Metadata for Meta {} 10 | 11 | fn main() { 12 | let mut io = MetaIoHandler::default(); 13 | 14 | io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| { 15 | let auth = meta.auth.unwrap_or_else(String::new); 16 | futures::future::ready(if auth.as_str() == "let-me-in" { 17 | Ok(Value::String("Hello World!".to_owned())) 18 | } else { 19 | Ok(Value::String( 20 | "Please send a valid Bearer token in Authorization header.".to_owned(), 21 | )) 22 | }) 23 | }); 24 | 25 | let server = ServerBuilder::new(io) 26 | .cors_allow_headers(AccessControlAllowHeaders::Only(vec!["Authorization".to_owned()])) 27 | .rest_api(RestApi::Unsecure) 28 | // You can also implement `MetaExtractor` trait and pass a struct here. 29 | .meta_extractor(|req: &hyper::Request| { 30 | let auth = req 31 | .headers() 32 | .get(hyper::header::AUTHORIZATION) 33 | .map(|h| h.to_str().unwrap_or("").to_owned()); 34 | 35 | Meta { auth } 36 | }) 37 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 38 | .expect("Unable to start RPC server"); 39 | 40 | server.wait(); 41 | } 42 | -------------------------------------------------------------------------------- /http/examples/http_middleware.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_http_server::jsonrpc_core::futures; 2 | use jsonrpc_http_server::jsonrpc_core::{IoHandler, Value}; 3 | use jsonrpc_http_server::{hyper, AccessControlAllowOrigin, DomainsValidation, Response, RestApi, ServerBuilder}; 4 | 5 | fn main() { 6 | let mut io = IoHandler::default(); 7 | io.add_method("say_hello", |_params| { 8 | futures::future::ready(Ok(Value::String("hello".to_owned()))) 9 | }); 10 | 11 | let server = ServerBuilder::new(io) 12 | .cors(DomainsValidation::AllowOnly(vec![AccessControlAllowOrigin::Null])) 13 | .request_middleware(|request: hyper::Request| { 14 | if request.uri() == "/status" { 15 | Response::ok("Server running OK.").into() 16 | } else { 17 | request.into() 18 | } 19 | }) 20 | .rest_api(RestApi::Unsecure) 21 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 22 | .expect("Unable to start RPC server"); 23 | 24 | server.wait(); 25 | } 26 | -------------------------------------------------------------------------------- /http/examples/server.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_http_server::jsonrpc_core::*; 2 | use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, RestApi, ServerBuilder}; 3 | 4 | fn main() { 5 | env_logger::init(); 6 | 7 | let mut io = IoHandler::default(); 8 | io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); 9 | 10 | let server = ServerBuilder::new(io) 11 | .threads(3) 12 | .rest_api(RestApi::Unsecure) 13 | .cors(DomainsValidation::AllowOnly(vec![AccessControlAllowOrigin::Any])) 14 | .start_http(&"127.0.0.1:3030".parse().unwrap()) 15 | .expect("Unable to start RPC server"); 16 | 17 | server.wait(); 18 | } 19 | -------------------------------------------------------------------------------- /http/src/response.rs: -------------------------------------------------------------------------------- 1 | //! Basic Request/Response structures used internally. 2 | 3 | pub use hyper::{self, header::HeaderValue, Body, Method, StatusCode}; 4 | 5 | /// Simple server response structure 6 | #[derive(Debug)] 7 | pub struct Response { 8 | /// Response code 9 | pub code: StatusCode, 10 | /// Response content type 11 | pub content_type: HeaderValue, 12 | /// Response body 13 | pub content: String, 14 | } 15 | 16 | impl Response { 17 | /// Create a response with empty body and 200 OK status code. 18 | pub fn empty() -> Self { 19 | Self::ok(String::new()) 20 | } 21 | 22 | /// Create a response with given body and 200 OK status code. 23 | pub fn ok>(response: T) -> Self { 24 | Response { 25 | code: StatusCode::OK, 26 | content_type: HeaderValue::from_static("application/json; charset=utf-8"), 27 | content: response.into(), 28 | } 29 | } 30 | 31 | /// Create a response for plaintext internal error. 32 | pub fn internal_error>(msg: T) -> Self { 33 | Response { 34 | code: StatusCode::INTERNAL_SERVER_ERROR, 35 | content_type: plain_text(), 36 | content: format!("Internal Server Error: {}", msg.into()), 37 | } 38 | } 39 | 40 | /// Create a json response for service unavailable. 41 | pub fn service_unavailable>(msg: T) -> Self { 42 | Response { 43 | code: StatusCode::SERVICE_UNAVAILABLE, 44 | content_type: HeaderValue::from_static("application/json; charset=utf-8"), 45 | content: msg.into(), 46 | } 47 | } 48 | 49 | /// Create a response for not allowed hosts. 50 | pub fn host_not_allowed() -> Self { 51 | Response { 52 | code: StatusCode::FORBIDDEN, 53 | content_type: plain_text(), 54 | content: "Provided Host header is not whitelisted.\n".to_owned(), 55 | } 56 | } 57 | 58 | /// Create a response for unsupported content type. 59 | pub fn unsupported_content_type() -> Self { 60 | Response { 61 | code: StatusCode::UNSUPPORTED_MEDIA_TYPE, 62 | content_type: plain_text(), 63 | content: "Supplied content type is not allowed. Content-Type: application/json is required\n".to_owned(), 64 | } 65 | } 66 | 67 | /// Create a response for disallowed method used. 68 | pub fn method_not_allowed() -> Self { 69 | Response { 70 | code: StatusCode::METHOD_NOT_ALLOWED, 71 | content_type: plain_text(), 72 | content: "Used HTTP Method is not allowed. POST or OPTIONS is required\n".to_owned(), 73 | } 74 | } 75 | 76 | /// CORS invalid 77 | pub fn invalid_allow_origin() -> Self { 78 | Response { 79 | code: StatusCode::FORBIDDEN, 80 | content_type: plain_text(), 81 | content: "Origin of the request is not whitelisted. CORS headers would not be sent and any side-effects were cancelled as well.\n".to_owned(), 82 | } 83 | } 84 | 85 | /// CORS header invalid 86 | pub fn invalid_allow_headers() -> Self { 87 | Response { 88 | code: StatusCode::FORBIDDEN, 89 | content_type: plain_text(), 90 | content: "Requested headers are not allowed for CORS. CORS headers would not be sent and any side-effects were cancelled as well.\n".to_owned(), 91 | } 92 | } 93 | 94 | /// Create a response for bad request 95 | pub fn bad_request>(msg: S) -> Self { 96 | Response { 97 | code: StatusCode::BAD_REQUEST, 98 | content_type: plain_text(), 99 | content: msg.into(), 100 | } 101 | } 102 | 103 | /// Create a response for too large (413) 104 | pub fn too_large>(msg: S) -> Self { 105 | Response { 106 | code: StatusCode::PAYLOAD_TOO_LARGE, 107 | content_type: plain_text(), 108 | content: msg.into(), 109 | } 110 | } 111 | 112 | /// Create a 500 response when server is closing. 113 | pub(crate) fn closing() -> Self { 114 | Response { 115 | code: StatusCode::SERVICE_UNAVAILABLE, 116 | content_type: plain_text(), 117 | content: "Server is closing.".into(), 118 | } 119 | } 120 | } 121 | 122 | fn plain_text() -> HeaderValue { 123 | HeaderValue::from_static("text/plain; charset=utf-8") 124 | } 125 | 126 | // TODO: Consider switching to a `TryFrom` conversion once it stabilizes. 127 | impl From for hyper::Response { 128 | /// Converts from a jsonrpc `Response` to a `hyper::Response` 129 | /// 130 | /// ## Panics 131 | /// 132 | /// Panics if the response cannot be converted due to failure to parse 133 | /// body content. 134 | /// 135 | fn from(res: Response) -> hyper::Response { 136 | hyper::Response::builder() 137 | .status(res.code) 138 | .header("content-type", res.content_type) 139 | .body(res.content.into()) 140 | // Parsing `StatusCode` and `HeaderValue` is infalliable but 141 | // parsing body content is not. 142 | .expect("Unable to parse response body for type conversion") 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /http/src/utils.rs: -------------------------------------------------------------------------------- 1 | use hyper::{self, header}; 2 | 3 | use crate::server_utils::{cors, hosts}; 4 | 5 | /// Extracts string value of a single header in request. 6 | fn read_header<'a>(req: &'a hyper::Request, header_name: &str) -> Option<&'a str> { 7 | req.headers().get(header_name).and_then(|v| v.to_str().ok()) 8 | } 9 | 10 | /// Returns `true` if Host header in request matches a list of allowed hosts. 11 | pub fn is_host_allowed(request: &hyper::Request, allowed_hosts: &Option>) -> bool { 12 | hosts::is_host_valid(read_header(request, "host"), allowed_hosts) 13 | } 14 | 15 | /// Returns a CORS AllowOrigin header that should be returned with that request. 16 | pub fn cors_allow_origin( 17 | request: &hyper::Request, 18 | cors_domains: &Option>, 19 | ) -> cors::AllowCors { 20 | cors::get_cors_allow_origin( 21 | read_header(request, "origin"), 22 | read_header(request, "host"), 23 | cors_domains, 24 | ) 25 | .map(|origin| { 26 | use self::cors::AccessControlAllowOrigin::*; 27 | match origin { 28 | Value(ref val) => { 29 | header::HeaderValue::from_str(val).unwrap_or_else(|_| header::HeaderValue::from_static("null")) 30 | } 31 | Null => header::HeaderValue::from_static("null"), 32 | Any => header::HeaderValue::from_static("*"), 33 | } 34 | }) 35 | } 36 | 37 | /// Returns the CORS AllowHeaders header that should be returned with that request. 38 | pub fn cors_allow_headers( 39 | request: &hyper::Request, 40 | cors_allow_headers: &cors::AccessControlAllowHeaders, 41 | ) -> cors::AllowCors> { 42 | let headers = request.headers().keys().map(|name| name.as_str()); 43 | let requested_headers = request 44 | .headers() 45 | .get_all("access-control-request-headers") 46 | .iter() 47 | .filter_map(|val| val.to_str().ok()) 48 | .flat_map(|val| val.split(", ")) 49 | .flat_map(|val| val.split(',')); 50 | 51 | cors::get_cors_allow_headers(headers, requested_headers, cors_allow_headers, |name| { 52 | header::HeaderValue::from_str(name).unwrap_or_else(|_| header::HeaderValue::from_static("unknown")) 53 | }) 54 | } 55 | 56 | /// Returns an optional value of `Connection` header that should be included in the response. 57 | /// The second parameter defines if server is configured with keep-alive option. 58 | /// Return value of `true` indicates that no `Connection` header should be returned, 59 | /// `false` indicates `Connection: close`. 60 | pub fn keep_alive(request: &hyper::Request, keep_alive: bool) -> bool { 61 | read_header(request, "connection") 62 | .map(|val| !matches!((keep_alive, val), (false, _) | (_, "close"))) 63 | // if the client header is not present, close connection if we don't keep_alive 64 | .unwrap_or(keep_alive) 65 | } 66 | -------------------------------------------------------------------------------- /ipc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "IPC server for JSON-RPC" 4 | documentation = "https://docs.rs/jsonrpc-ipc-server/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | license = "MIT" 8 | name = "jsonrpc-ipc-server" 9 | repository = "https://github.com/paritytech/jsonrpc" 10 | version = "18.0.0" 11 | 12 | [dependencies] 13 | futures = "0.3" 14 | log = "0.4" 15 | tower-service = "0.3" 16 | jsonrpc-core = { version = "18.0.0", path = "../core" } 17 | jsonrpc-server-utils = { version = "18.0.0", path = "../server-utils", default-features = false } 18 | parity-tokio-ipc = "0.9" 19 | parking_lot = "0.11.0" 20 | 21 | [dev-dependencies] 22 | env_logger = "0.7" 23 | lazy_static = "1.0" 24 | 25 | [target.'cfg(not(windows))'.dev-dependencies] 26 | tokio = { version = "1", default-features = false, features = ["net", "time", "rt-multi-thread"] } 27 | 28 | [badges] 29 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 30 | -------------------------------------------------------------------------------- /ipc/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-ipc-server 2 | IPC server (Windows & Linux) for JSON-RPC 2.0. 3 | 4 | [Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_ipc_server/index.html) 5 | 6 | ## Example 7 | 8 | `Cargo.toml` 9 | 10 | ``` 11 | [dependencies] 12 | jsonrpc-ipc-server = "15.0" 13 | ``` 14 | 15 | `main.rs` 16 | 17 | ```rust 18 | extern crate jsonrpc_ipc_server; 19 | 20 | use jsonrpc_ipc_server::ServerBuilder; 21 | use jsonrpc_ipc_server::jsonrpc_core::*; 22 | 23 | fn main() { 24 | let mut io = IoHandler::new(); 25 | io.add_method("say_hello", |_params| { 26 | Ok(Value::String("hello".into())) 27 | }); 28 | 29 | let builder = ServerBuilder::new(io); 30 | let server = builder.start("/tmp/json-ipc-test.ipc").expect("Couldn't open socket"); 31 | server.wait(); 32 | } 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /ipc/examples/ipc.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_ipc_server; 2 | 3 | use jsonrpc_ipc_server::jsonrpc_core::*; 4 | 5 | fn main() { 6 | let mut io = MetaIoHandler::<()>::default(); 7 | io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); 8 | let _server = jsonrpc_ipc_server::ServerBuilder::new(io) 9 | .start("/tmp/parity-example.ipc") 10 | .expect("Server should start ok"); 11 | } 12 | -------------------------------------------------------------------------------- /ipc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cross-platform JSON-RPC IPC transport. 2 | 3 | #![deny(missing_docs)] 4 | 5 | use jsonrpc_server_utils as server_utils; 6 | 7 | pub use jsonrpc_core; 8 | 9 | #[macro_use] 10 | extern crate log; 11 | 12 | #[cfg(test)] 13 | #[macro_use] 14 | extern crate lazy_static; 15 | 16 | #[cfg(test)] 17 | mod logger; 18 | 19 | mod meta; 20 | mod select_with_weak; 21 | mod server; 22 | 23 | use jsonrpc_core as jsonrpc; 24 | 25 | pub use crate::meta::{MetaExtractor, NoopExtractor, RequestContext}; 26 | pub use crate::server::{CloseHandle, SecurityAttributes, Server, ServerBuilder}; 27 | 28 | pub use self::server_utils::session::{SessionId, SessionStats}; 29 | pub use self::server_utils::{codecs::Separator, tokio}; 30 | -------------------------------------------------------------------------------- /ipc/src/logger.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use env_logger::Builder; 4 | use log::LevelFilter; 5 | use std::env; 6 | 7 | lazy_static! { 8 | static ref LOG_DUMMY: bool = { 9 | let mut builder = Builder::new(); 10 | builder.filter(None, LevelFilter::Info); 11 | 12 | if let Ok(log) = env::var("RUST_LOG") { 13 | builder.parse_filters(&log); 14 | } 15 | 16 | if let Ok(_) = builder.try_init() { 17 | println!("logger initialized"); 18 | } 19 | true 20 | }; 21 | } 22 | 23 | /// Intialize log with default settings 24 | pub fn init_log() { 25 | let _ = *LOG_DUMMY; 26 | } 27 | -------------------------------------------------------------------------------- /ipc/src/meta.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use crate::jsonrpc::futures::channel::mpsc; 4 | use crate::jsonrpc::Metadata; 5 | use crate::server_utils::session; 6 | 7 | /// Request context 8 | pub struct RequestContext<'a> { 9 | /// Session ID 10 | pub session_id: session::SessionId, 11 | /// Remote UDS endpoint 12 | pub endpoint_addr: &'a Path, 13 | /// Direct pipe sender 14 | pub sender: mpsc::UnboundedSender, 15 | } 16 | 17 | /// Metadata extractor (per session) 18 | pub trait MetaExtractor: Send + Sync + 'static { 19 | /// Extracts metadata from request context 20 | fn extract(&self, context: &RequestContext) -> M; 21 | } 22 | 23 | impl MetaExtractor for F 24 | where 25 | M: Metadata, 26 | F: Fn(&RequestContext) -> M + Send + Sync + 'static, 27 | { 28 | fn extract(&self, context: &RequestContext) -> M { 29 | (*self)(context) 30 | } 31 | } 32 | 33 | /// Noop-extractor 34 | pub struct NoopExtractor; 35 | impl MetaExtractor for NoopExtractor { 36 | fn extract(&self, _context: &RequestContext) -> M { 37 | M::default() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ipc/src/select_with_weak.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::task::Context; 3 | use std::task::Poll; 4 | 5 | use futures::stream::{Fuse, Stream}; 6 | 7 | pub trait SelectWithWeakExt: Stream { 8 | fn select_with_weak(self, other: S) -> SelectWithWeak 9 | where 10 | S: Stream, 11 | Self: Sized; 12 | } 13 | 14 | impl SelectWithWeakExt for T 15 | where 16 | T: Stream, 17 | { 18 | fn select_with_weak(self, other: S) -> SelectWithWeak 19 | where 20 | S: Stream, 21 | Self: Sized, 22 | { 23 | new(self, other) 24 | } 25 | } 26 | 27 | /// An adapter for merging the output of two streams. 28 | /// 29 | /// The merged stream produces items from either of the underlying streams as 30 | /// they become available, and the streams are polled in a round-robin fashion. 31 | /// Errors, however, are not merged: you get at most one error at a time. 32 | /// 33 | /// Finishes when strong stream finishes 34 | #[derive(Debug)] 35 | #[must_use = "streams do nothing unless polled"] 36 | pub struct SelectWithWeak { 37 | strong: Fuse, 38 | weak: Fuse, 39 | use_strong: bool, 40 | } 41 | 42 | fn new(stream1: S1, stream2: S2) -> SelectWithWeak 43 | where 44 | S1: Stream, 45 | S2: Stream, 46 | { 47 | use futures::StreamExt; 48 | SelectWithWeak { 49 | strong: stream1.fuse(), 50 | weak: stream2.fuse(), 51 | use_strong: false, 52 | } 53 | } 54 | 55 | impl Stream for SelectWithWeak 56 | where 57 | S1: Stream + Unpin, 58 | S2: Stream + Unpin, 59 | { 60 | type Item = S1::Item; 61 | 62 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 63 | let mut this = Pin::into_inner(self); 64 | let mut checked_strong = false; 65 | loop { 66 | if this.use_strong { 67 | match Pin::new(&mut this.strong).poll_next(cx) { 68 | Poll::Ready(Some(item)) => { 69 | this.use_strong = false; 70 | return Poll::Ready(Some(item)); 71 | } 72 | Poll::Ready(None) => return Poll::Ready(None), 73 | Poll::Pending => { 74 | if !checked_strong { 75 | this.use_strong = false; 76 | } else { 77 | return Poll::Pending; 78 | } 79 | } 80 | } 81 | checked_strong = true; 82 | } else { 83 | this.use_strong = true; 84 | match Pin::new(&mut this.weak).poll_next(cx) { 85 | Poll::Ready(Some(item)) => return Poll::Ready(Some(item)), 86 | Poll::Ready(None) | Poll::Pending => (), 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pubsub/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Publish-Subscribe extension for jsonrpc." 4 | documentation = "https://docs.rs/jsonrpc-pubsub/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "macros"] 8 | license = "MIT" 9 | name = "jsonrpc-pubsub" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | [dependencies] 14 | futures = { version = "0.3", features = ["thread-pool"] } 15 | jsonrpc-core = { version = "18.0.0", path = "../core" } 16 | lazy_static = "1.4" 17 | log = "0.4" 18 | parking_lot = "0.11.0" 19 | rand = "0.7" 20 | serde = "1.0" 21 | 22 | [dev-dependencies] 23 | jsonrpc-tcp-server = { version = "18.0.0", path = "../tcp" } 24 | serde_json = "1.0" 25 | 26 | [badges] 27 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 28 | -------------------------------------------------------------------------------- /pubsub/examples/pubsub.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{atomic, Arc}; 2 | use std::{thread, time}; 3 | 4 | use jsonrpc_core::*; 5 | use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; 6 | use jsonrpc_tcp_server::{RequestContext, ServerBuilder}; 7 | 8 | /// To test the server: 9 | /// 10 | /// ```bash 11 | /// $ netcat localhost 3030 - 12 | /// {"id":1,"jsonrpc":"2.0","method":"hello_subscribe","params":[10]} 13 | /// 14 | /// ``` 15 | fn main() { 16 | let mut io = PubSubHandler::new(MetaIoHandler::default()); 17 | io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); 18 | 19 | let is_done = Arc::new(atomic::AtomicBool::default()); 20 | let is_done2 = is_done.clone(); 21 | io.add_subscription( 22 | "hello", 23 | ("subscribe_hello", move |params: Params, _, subscriber: Subscriber| { 24 | if params != Params::None { 25 | subscriber 26 | .reject(Error { 27 | code: ErrorCode::ParseError, 28 | message: "Invalid parameters. Subscription rejected.".into(), 29 | data: None, 30 | }) 31 | .unwrap(); 32 | return; 33 | } 34 | 35 | let is_done = is_done.clone(); 36 | thread::spawn(move || { 37 | let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); 38 | // or subscriber.reject(Error {} ); 39 | // or drop(subscriber) 40 | 41 | loop { 42 | if is_done.load(atomic::Ordering::SeqCst) { 43 | return; 44 | } 45 | 46 | thread::sleep(time::Duration::from_millis(100)); 47 | match sink.notify(Params::Array(vec![Value::Number(10.into())])) { 48 | Ok(_) => {} 49 | Err(_) => { 50 | println!("Subscription has ended, finishing."); 51 | break; 52 | } 53 | } 54 | } 55 | }); 56 | }), 57 | ("remove_hello", move |_id: SubscriptionId, _| { 58 | println!("Closing subscription"); 59 | is_done2.store(true, atomic::Ordering::SeqCst); 60 | futures::future::ok(Value::Bool(true)) 61 | }), 62 | ); 63 | 64 | let server = ServerBuilder::new(io) 65 | .session_meta_extractor(|context: &RequestContext| Some(Arc::new(Session::new(context.sender.clone())))) 66 | .start(&"127.0.0.1:3030".parse().unwrap()) 67 | .expect("Unable to start RPC server"); 68 | 69 | server.wait(); 70 | } 71 | -------------------------------------------------------------------------------- /pubsub/examples/pubsub_simple.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::{thread, time}; 3 | 4 | use jsonrpc_core::*; 5 | use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; 6 | use jsonrpc_tcp_server::{RequestContext, ServerBuilder}; 7 | 8 | /// To test the server: 9 | /// 10 | /// ```bash 11 | /// $ netcat localhost 3030 12 | /// > {"id":1,"jsonrpc":"2.0","method":"subscribe_hello","params":null} 13 | /// < {"id":1,"jsonrpc":"2.0","result":5,"id":1} 14 | /// < {"jsonrpc":"2.0","method":"hello","params":[10]} 15 | /// 16 | /// ``` 17 | fn main() { 18 | let mut io = PubSubHandler::new(MetaIoHandler::default()); 19 | io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); 20 | 21 | io.add_subscription( 22 | "hello", 23 | ("subscribe_hello", |params: Params, _, subscriber: Subscriber| { 24 | if params != Params::None { 25 | subscriber 26 | .reject(Error { 27 | code: ErrorCode::ParseError, 28 | message: "Invalid parameters. Subscription rejected.".into(), 29 | data: None, 30 | }) 31 | .unwrap(); 32 | return; 33 | } 34 | 35 | thread::spawn(move || { 36 | let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); 37 | // or subscriber.reject(Error {} ); 38 | // or drop(subscriber) 39 | 40 | loop { 41 | thread::sleep(time::Duration::from_millis(100)); 42 | match sink.notify(Params::Array(vec![Value::Number(10.into())])) { 43 | Ok(_) => {} 44 | Err(_) => { 45 | println!("Subscription has ended, finishing."); 46 | break; 47 | } 48 | } 49 | } 50 | }); 51 | }), 52 | ("remove_hello", |_id: SubscriptionId, _| { 53 | println!("Closing subscription"); 54 | futures::future::ok(Value::Bool(true)) 55 | }), 56 | ); 57 | 58 | let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| { 59 | Arc::new(Session::new(context.sender.clone())) 60 | }) 61 | .start(&"127.0.0.1:3030".parse().unwrap()) 62 | .expect("Unable to start RPC server"); 63 | 64 | server.wait(); 65 | } 66 | -------------------------------------------------------------------------------- /pubsub/more-examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpc-pubsub-examples" 3 | description = "Examples of Publish-Subscribe extension for jsonrpc." 4 | homepage = "https://github.com/paritytech/jsonrpc" 5 | repository = "https://github.com/paritytech/jsonrpc" 6 | version = "18.0.0" 7 | authors = ["tomusdrw "] 8 | license = "MIT" 9 | 10 | [dependencies] 11 | jsonrpc-core = { version = "18.0.0", path = "../../core" } 12 | jsonrpc-pubsub = { version = "18.0.0", path = "../" } 13 | jsonrpc-ws-server = { version = "18.0.0", path = "../../ws" } 14 | jsonrpc-ipc-server = { version = "18.0.0", path = "../../ipc" } 15 | -------------------------------------------------------------------------------- /pubsub/more-examples/examples/pubsub_ipc.rs: -------------------------------------------------------------------------------- 1 | extern crate jsonrpc_core; 2 | extern crate jsonrpc_ipc_server; 3 | extern crate jsonrpc_pubsub; 4 | 5 | use std::sync::Arc; 6 | use std::{thread, time}; 7 | 8 | use jsonrpc_core::*; 9 | use jsonrpc_ipc_server::{RequestContext, ServerBuilder, SessionId, SessionStats}; 10 | use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; 11 | 12 | /// To test the server: 13 | /// 14 | /// ```bash 15 | /// $ netcat localhost 3030 - 16 | /// {"id":1,"jsonrpc":"2.0","method":"hello_subscribe","params":[10]} 17 | /// 18 | /// ``` 19 | fn main() { 20 | let mut io = PubSubHandler::new(MetaIoHandler::default()); 21 | io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); 22 | 23 | io.add_subscription( 24 | "hello", 25 | ("subscribe_hello", |params: Params, _, subscriber: Subscriber| { 26 | if params != Params::None { 27 | subscriber 28 | .reject(Error { 29 | code: ErrorCode::ParseError, 30 | message: "Invalid parameters. Subscription rejected.".into(), 31 | data: None, 32 | }) 33 | .unwrap(); 34 | return; 35 | } 36 | 37 | thread::spawn(move || { 38 | let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); 39 | // or subscriber.reject(Error {} ); 40 | // or drop(subscriber) 41 | 42 | loop { 43 | thread::sleep(time::Duration::from_millis(100)); 44 | match sink.notify(Params::Array(vec![Value::Number(10.into())])) { 45 | Ok(_) => {} 46 | Err(_) => { 47 | println!("Subscription has ended, finishing."); 48 | break; 49 | } 50 | } 51 | } 52 | }); 53 | }), 54 | ("remove_hello", |_id: SubscriptionId, _meta| { 55 | println!("Closing subscription"); 56 | futures::future::ready(Ok(Value::Bool(true))) 57 | }), 58 | ); 59 | 60 | let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| { 61 | Arc::new(Session::new(context.sender.clone())) 62 | }) 63 | .session_stats(Stats) 64 | .start("./test.ipc") 65 | .expect("Unable to start RPC server"); 66 | 67 | server.wait(); 68 | } 69 | 70 | struct Stats; 71 | impl SessionStats for Stats { 72 | fn open_session(&self, id: SessionId) { 73 | println!("Opening new session: {}", id); 74 | } 75 | 76 | fn close_session(&self, id: SessionId) { 77 | println!("Closing session: {}", id); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pubsub/more-examples/examples/pubsub_ws.rs: -------------------------------------------------------------------------------- 1 | extern crate jsonrpc_core; 2 | extern crate jsonrpc_pubsub; 3 | extern crate jsonrpc_ws_server; 4 | 5 | use std::sync::Arc; 6 | use std::{thread, time}; 7 | 8 | use jsonrpc_core::*; 9 | use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; 10 | use jsonrpc_ws_server::{RequestContext, ServerBuilder}; 11 | 12 | /// Use following node.js code to test: 13 | /// 14 | /// ```js 15 | /// const WebSocket = require('websocket').w3cwebsocket; 16 | /// 17 | /// const ws = new WebSocket('ws://localhost:3030'); 18 | /// ws.addEventListener('open', () => { 19 | /// console.log('Sending request'); 20 | /// 21 | /// ws.send(JSON.stringify({ 22 | /// jsonrpc: "2.0", 23 | /// id: 1, 24 | /// method: "subscribe_hello", 25 | /// params: null, 26 | /// })); 27 | /// }); 28 | /// 29 | /// ws.addEventListener('message', (message) => { 30 | /// console.log('Received: ', message.data); 31 | /// }); 32 | /// 33 | /// console.log('Starting'); 34 | /// ``` 35 | fn main() { 36 | let mut io = PubSubHandler::new(MetaIoHandler::default()); 37 | io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); 38 | 39 | io.add_subscription( 40 | "hello", 41 | ("subscribe_hello", |params: Params, _, subscriber: Subscriber| { 42 | if params != Params::None { 43 | subscriber 44 | .reject(Error { 45 | code: ErrorCode::ParseError, 46 | message: "Invalid parameters. Subscription rejected.".into(), 47 | data: None, 48 | }) 49 | .unwrap(); 50 | return; 51 | } 52 | 53 | thread::spawn(move || { 54 | let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); 55 | // or subscriber.reject(Error {} ); 56 | // or drop(subscriber) 57 | 58 | loop { 59 | thread::sleep(time::Duration::from_millis(1000)); 60 | match sink.notify(Params::Array(vec![Value::Number(10.into())])) { 61 | Ok(_) => {} 62 | Err(_) => { 63 | println!("Subscription has ended, finishing."); 64 | break; 65 | } 66 | } 67 | } 68 | }); 69 | }), 70 | ( 71 | "remove_hello", 72 | |_id: SubscriptionId, _meta| -> BoxFuture> { 73 | println!("Closing subscription"); 74 | Box::pin(futures::future::ready(Ok(Value::Bool(true)))) 75 | }, 76 | ), 77 | ); 78 | 79 | let server = 80 | ServerBuilder::with_meta_extractor(io, |context: &RequestContext| Arc::new(Session::new(context.sender()))) 81 | .start(&"127.0.0.1:3030".parse().unwrap()) 82 | .expect("Unable to start RPC server"); 83 | 84 | let _ = server.wait(); 85 | } 86 | -------------------------------------------------------------------------------- /pubsub/more-examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pubsub/src/delegates.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::sync::Arc; 3 | 4 | use crate::core::futures::Future; 5 | use crate::core::{self, Metadata, Params, RemoteProcedure, RpcMethod, Value}; 6 | use crate::handler::{SubscribeRpcMethod, UnsubscribeRpcMethod}; 7 | use crate::subscription::{new_subscription, Subscriber}; 8 | use crate::types::{PubSubMetadata, SubscriptionId}; 9 | 10 | struct DelegateSubscription { 11 | delegate: Arc, 12 | closure: F, 13 | } 14 | 15 | impl SubscribeRpcMethod for DelegateSubscription 16 | where 17 | M: PubSubMetadata, 18 | F: Fn(&T, Params, M, Subscriber), 19 | T: Send + Sync + 'static, 20 | F: Send + Sync + 'static, 21 | { 22 | fn call(&self, params: Params, meta: M, subscriber: Subscriber) { 23 | let closure = &self.closure; 24 | closure(&self.delegate, params, meta, subscriber) 25 | } 26 | } 27 | 28 | impl UnsubscribeRpcMethod for DelegateSubscription 29 | where 30 | M: PubSubMetadata, 31 | F: Fn(&T, SubscriptionId, Option) -> I, 32 | I: Future> + Send + 'static, 33 | T: Send + Sync + 'static, 34 | F: Send + Sync + 'static, 35 | { 36 | type Out = I; 37 | fn call(&self, id: SubscriptionId, meta: Option) -> Self::Out { 38 | let closure = &self.closure; 39 | closure(&self.delegate, id, meta) 40 | } 41 | } 42 | 43 | /// Wire up rpc subscriptions to `delegate` struct 44 | pub struct IoDelegate 45 | where 46 | T: Send + Sync + 'static, 47 | M: Metadata, 48 | { 49 | inner: core::IoDelegate, 50 | delegate: Arc, 51 | _data: PhantomData, 52 | } 53 | 54 | impl IoDelegate 55 | where 56 | T: Send + Sync + 'static, 57 | M: PubSubMetadata, 58 | { 59 | /// Creates new `PubSubIoDelegate`, wrapping the core IoDelegate 60 | pub fn new(delegate: Arc) -> Self { 61 | IoDelegate { 62 | inner: core::IoDelegate::new(delegate.clone()), 63 | delegate, 64 | _data: PhantomData, 65 | } 66 | } 67 | 68 | /// Adds subscription to the delegate. 69 | pub fn add_subscription(&mut self, name: &str, subscribe: (&str, Sub), unsubscribe: (&str, Unsub)) 70 | where 71 | Sub: Fn(&T, Params, M, Subscriber), 72 | Sub: Send + Sync + 'static, 73 | Unsub: Fn(&T, SubscriptionId, Option) -> I, 74 | I: Future> + Send + 'static, 75 | Unsub: Send + Sync + 'static, 76 | { 77 | let (sub, unsub) = new_subscription( 78 | name, 79 | DelegateSubscription { 80 | delegate: self.delegate.clone(), 81 | closure: subscribe.1, 82 | }, 83 | DelegateSubscription { 84 | delegate: self.delegate.clone(), 85 | closure: unsubscribe.1, 86 | }, 87 | ); 88 | self.inner 89 | .add_method_with_meta(subscribe.0, move |_, params, meta| sub.call(params, meta)); 90 | self.inner 91 | .add_method_with_meta(unsubscribe.0, move |_, params, meta| unsub.call(params, meta)); 92 | } 93 | 94 | /// Adds an alias to existing method. 95 | pub fn add_alias(&mut self, from: &str, to: &str) { 96 | self.inner.add_alias(from, to) 97 | } 98 | 99 | // TODO [ToDr] Consider sync? 100 | /// Adds async method to the delegate. 101 | pub fn add_method(&mut self, name: &str, method: F) 102 | where 103 | F: Fn(&T, Params) -> I, 104 | I: Future> + Send + 'static, 105 | F: Send + Sync + 'static, 106 | { 107 | self.inner.add_method(name, method) 108 | } 109 | 110 | /// Adds async method with metadata to the delegate. 111 | pub fn add_method_with_meta(&mut self, name: &str, method: F) 112 | where 113 | F: Fn(&T, Params, M) -> I, 114 | I: Future> + Send + 'static, 115 | F: Send + Sync + 'static, 116 | { 117 | self.inner.add_method_with_meta(name, method) 118 | } 119 | 120 | /// Adds notification to the delegate. 121 | pub fn add_notification(&mut self, name: &str, notification: F) 122 | where 123 | F: Fn(&T, Params), 124 | F: Send + Sync + 'static, 125 | { 126 | self.inner.add_notification(name, notification) 127 | } 128 | } 129 | 130 | impl core::IoHandlerExtension for IoDelegate 131 | where 132 | T: Send + Sync + 'static, 133 | M: Metadata, 134 | { 135 | fn augment>(self, handler: &mut core::MetaIoHandler) { 136 | handler.extend_with(self.inner) 137 | } 138 | } 139 | 140 | impl IntoIterator for IoDelegate 141 | where 142 | T: Send + Sync + 'static, 143 | M: Metadata, 144 | { 145 | type Item = (String, RemoteProcedure); 146 | type IntoIter = as IntoIterator>::IntoIter; 147 | 148 | fn into_iter(self) -> Self::IntoIter { 149 | self.inner.into_iter() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pubsub/src/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::core; 2 | use crate::core::futures::Future; 3 | 4 | use crate::subscription::{new_subscription, Subscriber}; 5 | use crate::types::{PubSubMetadata, SubscriptionId}; 6 | 7 | /// Subscribe handler 8 | pub trait SubscribeRpcMethod: Send + Sync + 'static { 9 | /// Called when client is requesting new subscription to be started. 10 | fn call(&self, params: core::Params, meta: M, subscriber: Subscriber); 11 | } 12 | 13 | impl SubscribeRpcMethod for F 14 | where 15 | F: Fn(core::Params, M, Subscriber) + Send + Sync + 'static, 16 | M: PubSubMetadata, 17 | { 18 | fn call(&self, params: core::Params, meta: M, subscriber: Subscriber) { 19 | (*self)(params, meta, subscriber) 20 | } 21 | } 22 | 23 | /// Unsubscribe handler 24 | pub trait UnsubscribeRpcMethod: Send + Sync + 'static { 25 | /// Output type 26 | type Out: Future> + Send + 'static; 27 | /// Called when client is requesting to cancel existing subscription. 28 | /// 29 | /// Metadata is not available if the session was closed without unsubscribing. 30 | fn call(&self, id: SubscriptionId, meta: Option) -> Self::Out; 31 | } 32 | 33 | impl UnsubscribeRpcMethod for F 34 | where 35 | F: Fn(SubscriptionId, Option) -> I + Send + Sync + 'static, 36 | I: Future> + Send + 'static, 37 | { 38 | type Out = I; 39 | fn call(&self, id: SubscriptionId, meta: Option) -> Self::Out { 40 | (*self)(id, meta) 41 | } 42 | } 43 | 44 | /// Publish-Subscribe extension of `IoHandler`. 45 | pub struct PubSubHandler = core::middleware::Noop> { 46 | handler: core::MetaIoHandler, 47 | } 48 | 49 | impl Default for PubSubHandler { 50 | fn default() -> Self { 51 | PubSubHandler { 52 | handler: Default::default(), 53 | } 54 | } 55 | } 56 | 57 | impl> PubSubHandler { 58 | /// Creates new `PubSubHandler` 59 | pub fn new(handler: core::MetaIoHandler) -> Self { 60 | PubSubHandler { handler } 61 | } 62 | 63 | /// Adds new subscription. 64 | pub fn add_subscription(&mut self, notification: &str, subscribe: (&str, F), unsubscribe: (&str, G)) 65 | where 66 | F: SubscribeRpcMethod, 67 | G: UnsubscribeRpcMethod, 68 | { 69 | let (sub, unsub) = new_subscription(notification, subscribe.1, unsubscribe.1); 70 | self.handler.add_method_with_meta(subscribe.0, sub); 71 | self.handler.add_method_with_meta(unsubscribe.0, unsub); 72 | } 73 | } 74 | 75 | impl> ::std::ops::Deref for PubSubHandler { 76 | type Target = core::MetaIoHandler; 77 | 78 | fn deref(&self) -> &Self::Target { 79 | &self.handler 80 | } 81 | } 82 | 83 | impl> ::std::ops::DerefMut for PubSubHandler { 84 | fn deref_mut(&mut self) -> &mut Self::Target { 85 | &mut self.handler 86 | } 87 | } 88 | 89 | impl> Into> for PubSubHandler { 90 | fn into(self) -> core::MetaIoHandler { 91 | self.handler 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use std::sync::atomic::{AtomicBool, Ordering}; 98 | use std::sync::Arc; 99 | 100 | use crate::core; 101 | use crate::core::futures::channel::mpsc; 102 | use crate::core::futures::future; 103 | use crate::subscription::{Session, Subscriber}; 104 | use crate::types::{PubSubMetadata, SubscriptionId}; 105 | 106 | use super::PubSubHandler; 107 | 108 | #[derive(Clone)] 109 | struct Metadata(Arc); 110 | impl core::Metadata for Metadata {} 111 | impl PubSubMetadata for Metadata { 112 | fn session(&self) -> Option> { 113 | Some(self.0.clone()) 114 | } 115 | } 116 | 117 | #[test] 118 | fn should_handle_subscription() { 119 | // given 120 | let mut handler = PubSubHandler::default(); 121 | let called = Arc::new(AtomicBool::new(false)); 122 | let called2 = called.clone(); 123 | handler.add_subscription( 124 | "hello", 125 | ("subscribe_hello", |params, _meta, subscriber: Subscriber| { 126 | assert_eq!(params, core::Params::None); 127 | let _sink = subscriber.assign_id(SubscriptionId::Number(5)); 128 | }), 129 | ("unsubscribe_hello", move |id, _meta| { 130 | // Should be called because session is dropped. 131 | called2.store(true, Ordering::SeqCst); 132 | assert_eq!(id, SubscriptionId::Number(5)); 133 | future::ok(core::Value::Bool(true)) 134 | }), 135 | ); 136 | 137 | // when 138 | let (tx, _rx) = mpsc::unbounded(); 139 | let meta = Metadata(Arc::new(Session::new(tx))); 140 | let req = r#"{"jsonrpc":"2.0","id":1,"method":"subscribe_hello","params":null}"#; 141 | let res = handler.handle_request_sync(req, meta); 142 | 143 | // then 144 | let response = r#"{"jsonrpc":"2.0","result":5,"id":1}"#; 145 | assert_eq!(res, Some(response.into())); 146 | assert_eq!(called.load(Ordering::SeqCst), true); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /pubsub/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Publish-Subscribe extension for JSON-RPC 2 | 3 | #![deny(missing_docs)] 4 | 5 | use jsonrpc_core as core; 6 | 7 | #[macro_use] 8 | extern crate log; 9 | 10 | mod delegates; 11 | mod handler; 12 | pub mod manager; 13 | pub mod oneshot; 14 | mod subscription; 15 | pub mod typed; 16 | mod types; 17 | 18 | pub use self::delegates::IoDelegate; 19 | pub use self::handler::{PubSubHandler, SubscribeRpcMethod, UnsubscribeRpcMethod}; 20 | pub use self::subscription::{new_subscription, Session, Sink, Subscriber}; 21 | pub use self::types::{PubSubMetadata, SinkResult, SubscriptionId, TransportError}; 22 | -------------------------------------------------------------------------------- /pubsub/src/oneshot.rs: -------------------------------------------------------------------------------- 1 | //! A futures oneshot channel that can be used for rendezvous. 2 | 3 | use crate::core::futures::{self, channel::oneshot, future, Future, FutureExt, TryFutureExt}; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | /// Create a new future-base rendezvous channel. 7 | /// 8 | /// The returned `Sender` and `Receiver` objects are wrapping 9 | /// the regular `futures::channel::oneshot` counterparts and have the same functionality. 10 | /// Additionaly `Sender::send_and_wait` allows you to send a message to the channel 11 | /// and get a future that resolves when the message is consumed. 12 | pub fn channel() -> (Sender, Receiver) { 13 | let (sender, receiver) = oneshot::channel(); 14 | let (receipt_tx, receipt_rx) = oneshot::channel(); 15 | 16 | ( 17 | Sender { 18 | sender, 19 | receipt: receipt_rx, 20 | }, 21 | Receiver { 22 | receiver, 23 | receipt: Some(receipt_tx), 24 | }, 25 | ) 26 | } 27 | 28 | /// A sender part of the channel. 29 | #[derive(Debug)] 30 | pub struct Sender { 31 | sender: oneshot::Sender, 32 | receipt: oneshot::Receiver<()>, 33 | } 34 | 35 | impl Sender { 36 | /// Consume the sender and queue up an item to send. 37 | /// 38 | /// This method returns right away and never blocks, 39 | /// there is no guarantee though that the message is received 40 | /// by the other end. 41 | pub fn send(self, t: T) -> Result<(), T> { 42 | self.sender.send(t) 43 | } 44 | 45 | /// Consume the sender and send an item. 46 | /// 47 | /// The returned future will resolve when the message is received 48 | /// on the other end. Note that polling the future is actually not required 49 | /// to send the message as that happens synchronously. 50 | /// The future resolves to error in case the receiving end was dropped before 51 | /// being able to process the message. 52 | pub fn send_and_wait(self, t: T) -> impl Future> { 53 | let Self { sender, receipt } = self; 54 | 55 | if sender.send(t).is_err() { 56 | return future::Either::Left(future::ready(Err(()))); 57 | } 58 | 59 | future::Either::Right(receipt.map_err(|_| ())) 60 | } 61 | } 62 | 63 | impl Deref for Sender { 64 | type Target = oneshot::Sender; 65 | 66 | fn deref(&self) -> &Self::Target { 67 | &self.sender 68 | } 69 | } 70 | 71 | impl DerefMut for Sender { 72 | fn deref_mut(&mut self) -> &mut Self::Target { 73 | &mut self.sender 74 | } 75 | } 76 | 77 | /// Receiving end of the channel. 78 | /// 79 | /// When this object is `polled` and the result is `Ready` 80 | /// the other end (`Sender`) is also notified about the fact 81 | /// that the item has been consumed and the future returned 82 | /// by `send_and_wait` resolves. 83 | #[must_use = "futures do nothing unless polled"] 84 | #[derive(Debug)] 85 | pub struct Receiver { 86 | receiver: oneshot::Receiver, 87 | receipt: Option>, 88 | } 89 | 90 | impl Future for Receiver { 91 | type Output = as Future>::Output; 92 | 93 | fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context) -> futures::task::Poll { 94 | let r = futures::ready!(self.receiver.poll_unpin(cx))?; 95 | if let Some(receipt) = self.receipt.take() { 96 | let _ = receipt.send(()); 97 | } 98 | Ok(r).into() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pubsub/src/typed.rs: -------------------------------------------------------------------------------- 1 | //! PUB-SUB auto-serializing structures. 2 | 3 | use std::marker::PhantomData; 4 | use std::pin::Pin; 5 | 6 | use crate::subscription; 7 | use crate::types::{SinkResult, SubscriptionId, TransportError}; 8 | 9 | use crate::core::futures::task::{Context, Poll}; 10 | use crate::core::futures::{self, channel}; 11 | use crate::core::{self, Error, Params, Value}; 12 | 13 | /// New PUB-SUB subscriber. 14 | #[derive(Debug)] 15 | pub struct Subscriber { 16 | subscriber: subscription::Subscriber, 17 | _data: PhantomData<(T, E)>, 18 | } 19 | 20 | impl Subscriber { 21 | /// Wrap non-typed subscriber. 22 | pub fn new(subscriber: subscription::Subscriber) -> Self { 23 | Subscriber { 24 | subscriber, 25 | _data: PhantomData, 26 | } 27 | } 28 | 29 | /// Create new subscriber for tests. 30 | pub fn new_test>( 31 | method: M, 32 | ) -> ( 33 | Self, 34 | crate::oneshot::Receiver>, 35 | channel::mpsc::UnboundedReceiver, 36 | ) { 37 | let (subscriber, id, subscription) = subscription::Subscriber::new_test(method); 38 | (Subscriber::new(subscriber), id, subscription) 39 | } 40 | 41 | /// Reject subscription with given error. 42 | pub fn reject(self, error: Error) -> Result<(), ()> { 43 | self.subscriber.reject(error) 44 | } 45 | 46 | /// Reject subscription with given error. 47 | /// 48 | /// The returned future will resolve when the response is sent to the client. 49 | pub async fn reject_async(self, error: Error) -> Result<(), ()> { 50 | self.subscriber.reject_async(error).await 51 | } 52 | 53 | /// Assign id to this subscriber. 54 | /// This method consumes `Subscriber` and returns `Sink` 55 | /// if the connection is still open or error otherwise. 56 | pub fn assign_id(self, id: SubscriptionId) -> Result, ()> { 57 | let sink = self.subscriber.assign_id(id.clone())?; 58 | Ok(Sink { 59 | id, 60 | sink, 61 | _data: PhantomData, 62 | }) 63 | } 64 | 65 | /// Assign id to this subscriber. 66 | /// This method consumes `Subscriber` and resolves to `Sink` 67 | /// if the connection is still open and the id has been sent or to error otherwise. 68 | pub async fn assign_id_async(self, id: SubscriptionId) -> Result, ()> { 69 | let sink = self.subscriber.assign_id_async(id.clone()).await?; 70 | Ok(Sink { 71 | id, 72 | sink, 73 | _data: PhantomData, 74 | }) 75 | } 76 | } 77 | 78 | /// Subscriber sink. 79 | #[derive(Debug, Clone)] 80 | pub struct Sink { 81 | sink: subscription::Sink, 82 | id: SubscriptionId, 83 | _data: PhantomData<(T, E)>, 84 | } 85 | 86 | impl Sink { 87 | /// Sends a notification to the subscriber. 88 | pub fn notify(&self, val: Result) -> SinkResult { 89 | self.sink.notify(self.val_to_params(val)) 90 | } 91 | 92 | fn to_value(value: V) -> Value 93 | where 94 | V: serde::Serialize, 95 | { 96 | core::to_value(value).expect("Expected always-serializable type.") 97 | } 98 | 99 | fn val_to_params(&self, val: Result) -> Params { 100 | let id = self.id.clone().into(); 101 | let val = val.map(Self::to_value).map_err(Self::to_value); 102 | 103 | Params::Map( 104 | vec![ 105 | ("subscription".to_owned(), id), 106 | match val { 107 | Ok(val) => ("result".to_owned(), val), 108 | Err(err) => ("error".to_owned(), err), 109 | }, 110 | ] 111 | .into_iter() 112 | .collect(), 113 | ) 114 | } 115 | } 116 | 117 | impl futures::sink::Sink> for Sink { 118 | type Error = TransportError; 119 | 120 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 121 | Pin::new(&mut self.sink).poll_ready(cx) 122 | } 123 | 124 | fn start_send(mut self: Pin<&mut Self>, item: Result) -> Result<(), Self::Error> { 125 | let val = self.val_to_params(item); 126 | Pin::new(&mut self.sink).start_send(val) 127 | } 128 | 129 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 130 | Pin::new(&mut self.sink).poll_flush(cx) 131 | } 132 | 133 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 134 | Pin::new(&mut self.sink).poll_close(cx) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /pubsub/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::core; 2 | use crate::core::futures::channel::mpsc; 3 | use std::sync::Arc; 4 | 5 | use crate::subscription::Session; 6 | 7 | /// Raw transport sink for specific client. 8 | pub type TransportSender = mpsc::UnboundedSender; 9 | /// Raw transport error. 10 | pub type TransportError = mpsc::SendError; 11 | /// Subscription send result. 12 | pub type SinkResult = Result<(), mpsc::TrySendError>; 13 | 14 | /// Metadata extension for pub-sub method handling. 15 | /// 16 | /// NOTE storing `PubSubMetadata` (or rather storing `Arc`) in 17 | /// any other place outside of the handler will prevent `unsubscribe` methods 18 | /// to be called in case the `Session` is dropped (i.e. transport connection is closed). 19 | pub trait PubSubMetadata: core::Metadata { 20 | /// Returns session object associated with given request/client. 21 | /// `None` indicates that sessions are not supported on the used transport. 22 | fn session(&self) -> Option>; 23 | } 24 | 25 | impl PubSubMetadata for Arc { 26 | fn session(&self) -> Option> { 27 | Some(self.clone()) 28 | } 29 | } 30 | 31 | impl PubSubMetadata for Option { 32 | fn session(&self) -> Option> { 33 | self.as_ref().and_then(|s| s.session()) 34 | } 35 | } 36 | 37 | /// Unique subscription id. 38 | /// 39 | /// NOTE Assigning same id to different requests will cause the previous request to be unsubscribed. 40 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 41 | pub enum SubscriptionId { 42 | /// A numerical ID, represented by a `u64`. 43 | Number(u64), 44 | /// A non-numerical ID, for example a hash. 45 | String(String), 46 | } 47 | 48 | impl SubscriptionId { 49 | /// Parses `core::Value` into unique subscription id. 50 | pub fn parse_value(val: &core::Value) -> Option { 51 | match *val { 52 | core::Value::String(ref val) => Some(SubscriptionId::String(val.clone())), 53 | core::Value::Number(ref val) => val.as_u64().map(SubscriptionId::Number), 54 | _ => None, 55 | } 56 | } 57 | } 58 | 59 | impl From for SubscriptionId { 60 | fn from(other: String) -> Self { 61 | SubscriptionId::String(other) 62 | } 63 | } 64 | 65 | impl From for core::Value { 66 | fn from(sub: SubscriptionId) -> Self { 67 | match sub { 68 | SubscriptionId::Number(val) => core::Value::Number(val.into()), 69 | SubscriptionId::String(val) => core::Value::String(val), 70 | } 71 | } 72 | } 73 | 74 | macro_rules! impl_from_num { 75 | ($num:ty) => { 76 | impl From<$num> for SubscriptionId { 77 | fn from(other: $num) -> Self { 78 | SubscriptionId::Number(other.into()) 79 | } 80 | } 81 | }; 82 | } 83 | 84 | impl_from_num!(u8); 85 | impl_from_num!(u16); 86 | impl_from_num!(u32); 87 | impl_from_num!(u64); 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::SubscriptionId; 92 | use crate::core::Value; 93 | 94 | #[test] 95 | fn should_convert_between_number_value_and_subscription_id() { 96 | let val = Value::Number(5.into()); 97 | let res = SubscriptionId::parse_value(&val); 98 | 99 | assert_eq!(res, Some(SubscriptionId::Number(5))); 100 | assert_eq!(Value::from(res.unwrap()), val); 101 | } 102 | 103 | #[test] 104 | fn should_convert_between_string_value_and_subscription_id() { 105 | let val = Value::String("asdf".into()); 106 | let res = SubscriptionId::parse_value(&val); 107 | 108 | assert_eq!(res, Some(SubscriptionId::String("asdf".into()))); 109 | assert_eq!(Value::from(res.unwrap()), val); 110 | } 111 | 112 | #[test] 113 | fn should_convert_between_null_value_and_subscription_id() { 114 | let val = Value::Null; 115 | let res = SubscriptionId::parse_value(&val); 116 | assert_eq!(res, None); 117 | } 118 | 119 | #[test] 120 | fn should_convert_from_u8_to_subscription_id() { 121 | let val = 5u8; 122 | let res: SubscriptionId = val.into(); 123 | assert_eq!(res, SubscriptionId::Number(5)); 124 | } 125 | 126 | #[test] 127 | fn should_convert_from_u16_to_subscription_id() { 128 | let val = 5u16; 129 | let res: SubscriptionId = val.into(); 130 | assert_eq!(res, SubscriptionId::Number(5)); 131 | } 132 | 133 | #[test] 134 | fn should_convert_from_u32_to_subscription_id() { 135 | let val = 5u32; 136 | let res: SubscriptionId = val.into(); 137 | assert_eq!(res, SubscriptionId::Number(5)); 138 | } 139 | 140 | #[test] 141 | fn should_convert_from_u64_to_subscription_id() { 142 | let val = 5u64; 143 | let res: SubscriptionId = val.into(); 144 | assert_eq!(res, SubscriptionId::Number(5)); 145 | } 146 | 147 | #[test] 148 | fn should_convert_from_string_to_subscription_id() { 149 | let val = "String".to_string(); 150 | let res: SubscriptionId = val.into(); 151 | assert_eq!(res, SubscriptionId::String("String".to_string())); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | hard_tabs = true -------------------------------------------------------------------------------- /server-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "Server utils for jsonrpc-core crate." 4 | documentation = "https://docs.rs/jsonrpc-server-utils/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] 8 | license = "MIT" 9 | name = "jsonrpc-server-utils" 10 | repository = "https://github.com/paritytech/jsonrpc" 11 | version = "18.0.0" 12 | 13 | [dependencies] 14 | bytes = "1.0" 15 | futures = "0.3" 16 | globset = "0.4" 17 | jsonrpc-core = { version = "18.0.0", path = "../core" } 18 | lazy_static = "1.1.0" 19 | log = "0.4" 20 | tokio = { version = "1", features = ["rt-multi-thread", "io-util", "time", "net"] } 21 | tokio-util = { version = "0.6", features = ["codec"] } 22 | tokio-stream = { version = "0.1", features = ["net"] } 23 | 24 | unicase = "2.0" 25 | 26 | [badges] 27 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 28 | -------------------------------------------------------------------------------- /server-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! JSON-RPC servers utilities. 2 | 3 | #![deny(missing_docs)] 4 | 5 | #[macro_use] 6 | extern crate log; 7 | 8 | #[macro_use] 9 | extern crate lazy_static; 10 | 11 | pub use tokio; 12 | pub use tokio_stream; 13 | pub use tokio_util; 14 | 15 | pub mod cors; 16 | pub mod hosts; 17 | mod matcher; 18 | pub mod reactor; 19 | pub mod session; 20 | mod stream_codec; 21 | mod suspendable_stream; 22 | 23 | pub use crate::matcher::Pattern; 24 | pub use crate::suspendable_stream::SuspendableStream; 25 | 26 | /// Codecs utilities 27 | pub mod codecs { 28 | pub use crate::stream_codec::{Separator, StreamCodec}; 29 | } 30 | -------------------------------------------------------------------------------- /server-utils/src/matcher.rs: -------------------------------------------------------------------------------- 1 | use globset::{GlobBuilder, GlobMatcher}; 2 | use std::{fmt, hash}; 3 | 4 | /// Pattern that can be matched to string. 5 | pub trait Pattern { 6 | /// Returns true if given string matches the pattern. 7 | fn matches>(&self, other: T) -> bool; 8 | } 9 | 10 | #[derive(Clone)] 11 | pub struct Matcher(Option, String); 12 | impl Matcher { 13 | pub fn new(string: &str) -> Matcher { 14 | Matcher( 15 | GlobBuilder::new(string) 16 | .case_insensitive(true) 17 | .build() 18 | .map(|g| g.compile_matcher()) 19 | .map_err(|e| warn!("Invalid glob pattern for {}: {:?}", string, e)) 20 | .ok(), 21 | string.into(), 22 | ) 23 | } 24 | } 25 | 26 | impl Pattern for Matcher { 27 | fn matches>(&self, other: T) -> bool { 28 | let s = other.as_ref(); 29 | match self.0 { 30 | Some(ref matcher) => matcher.is_match(s), 31 | None => self.1.eq_ignore_ascii_case(s), 32 | } 33 | } 34 | } 35 | 36 | impl fmt::Debug for Matcher { 37 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 38 | write!(fmt, "{:?} ({})", self.1, self.0.is_some()) 39 | } 40 | } 41 | 42 | impl hash::Hash for Matcher { 43 | fn hash(&self, state: &mut H) 44 | where 45 | H: hash::Hasher, 46 | { 47 | self.1.hash(state) 48 | } 49 | } 50 | 51 | impl Eq for Matcher {} 52 | impl PartialEq for Matcher { 53 | fn eq(&self, other: &Matcher) -> bool { 54 | self.1.eq(&other.1) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server-utils/src/reactor.rs: -------------------------------------------------------------------------------- 1 | //! Event Loop Executor 2 | //! 3 | //! Either spawns a new event loop, or re-uses provided one. 4 | //! Spawned event loop is always single threaded (mostly for 5 | //! historical/backward compatibility reasons) despite the fact 6 | //! that `tokio::runtime` can be multi-threaded. 7 | 8 | use std::io; 9 | 10 | use tokio::runtime; 11 | /// Task executor for Tokio 0.2 runtime. 12 | pub type TaskExecutor = tokio::runtime::Handle; 13 | 14 | /// Possibly uninitialized event loop executor. 15 | #[derive(Debug)] 16 | pub enum UninitializedExecutor { 17 | /// Shared instance of executor. 18 | Shared(TaskExecutor), 19 | /// Event Loop should be spawned by the transport. 20 | Unspawned, 21 | } 22 | 23 | impl UninitializedExecutor { 24 | /// Initializes executor. 25 | /// In case there is no shared executor, will spawn a new event loop. 26 | /// Dropping `Executor` closes the loop. 27 | pub fn initialize(self) -> io::Result { 28 | self.init_with_name("event.loop") 29 | } 30 | 31 | /// Initializes executor. 32 | /// In case there is no shared executor, will spawn a new event loop. 33 | /// Dropping `Executor` closes the loop. 34 | pub fn init_with_name>(self, name: T) -> io::Result { 35 | match self { 36 | UninitializedExecutor::Shared(executor) => Ok(Executor::Shared(executor)), 37 | UninitializedExecutor::Unspawned => RpcEventLoop::with_name(Some(name.into())).map(Executor::Spawned), 38 | } 39 | } 40 | } 41 | 42 | /// Initialized Executor 43 | #[derive(Debug)] 44 | pub enum Executor { 45 | /// Shared instance 46 | Shared(TaskExecutor), 47 | /// Spawned Event Loop 48 | Spawned(RpcEventLoop), 49 | } 50 | 51 | impl Executor { 52 | /// Get tokio executor associated with this event loop. 53 | pub fn executor(&self) -> TaskExecutor { 54 | match self { 55 | Executor::Shared(ref executor) => executor.clone(), 56 | Executor::Spawned(ref eloop) => eloop.executor(), 57 | } 58 | } 59 | 60 | /// Closes underlying event loop (if any!). 61 | pub fn close(self) { 62 | if let Executor::Spawned(eloop) = self { 63 | eloop.close() 64 | } 65 | } 66 | 67 | /// Wait for underlying event loop to finish (if any!). 68 | pub fn wait(self) { 69 | if let Executor::Spawned(eloop) = self { 70 | let _ = eloop.wait(); 71 | } 72 | } 73 | } 74 | 75 | /// A handle to running event loop. Dropping the handle will cause event loop to finish. 76 | #[derive(Debug)] 77 | pub struct RpcEventLoop { 78 | executor: TaskExecutor, 79 | close: Option>, 80 | runtime: Option, 81 | } 82 | 83 | impl Drop for RpcEventLoop { 84 | fn drop(&mut self) { 85 | self.close.take().map(|v| v.send(())); 86 | } 87 | } 88 | 89 | impl RpcEventLoop { 90 | /// Spawns a new thread with the `EventLoop`. 91 | pub fn spawn() -> io::Result { 92 | RpcEventLoop::with_name(None) 93 | } 94 | 95 | /// Spawns a new named thread with the `EventLoop`. 96 | pub fn with_name(name: Option) -> io::Result { 97 | let (stop, stopped) = futures::channel::oneshot::channel(); 98 | 99 | let mut tb = runtime::Builder::new_multi_thread(); 100 | tb.worker_threads(1); 101 | tb.enable_all(); 102 | 103 | if let Some(name) = name { 104 | tb.thread_name(name); 105 | } 106 | 107 | let runtime = tb.build()?; 108 | let executor = runtime.handle().to_owned(); 109 | 110 | runtime.spawn(async { 111 | let _ = stopped.await; 112 | }); 113 | 114 | Ok(RpcEventLoop { 115 | executor, 116 | close: Some(stop), 117 | runtime: Some(runtime), 118 | }) 119 | } 120 | 121 | /// Get executor for this event loop. 122 | pub fn executor(&self) -> runtime::Handle { 123 | self.runtime 124 | .as_ref() 125 | .expect("Runtime is only None if we're being dropped; qed") 126 | .handle() 127 | .clone() 128 | } 129 | 130 | /// Blocks current thread and waits until the event loop is finished. 131 | pub fn wait(mut self) -> Result<(), ()> { 132 | // Dropping Tokio 0.2 runtime waits for all spawned tasks to terminate 133 | let runtime = self.runtime.take().ok_or(())?; 134 | drop(runtime); 135 | Ok(()) 136 | } 137 | 138 | /// Finishes this event loop. 139 | pub fn close(mut self) { 140 | let _ = self 141 | .close 142 | .take() 143 | .expect("Close is always set before self is consumed.") 144 | .send(()) 145 | .map_err(|e| { 146 | warn!("Event Loop is already finished. {:?}", e); 147 | }); 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::*; 154 | 155 | #[test] 156 | fn make_sure_rpc_event_loop_is_send_and_sync() { 157 | fn is_send_and_sync() {} 158 | 159 | is_send_and_sync::(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /server-utils/src/session.rs: -------------------------------------------------------------------------------- 1 | //! Session statistics. 2 | 3 | /// Session id 4 | pub type SessionId = u64; 5 | 6 | /// Keeps track of open sessions 7 | pub trait SessionStats: Send + Sync + 'static { 8 | /// Executed when new session is opened. 9 | fn open_session(&self, id: SessionId); 10 | /// Executed when session is closed. 11 | fn close_session(&self, id: SessionId); 12 | } 13 | -------------------------------------------------------------------------------- /server-utils/src/suspendable_stream.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::pin::Pin; 4 | use std::task::Poll; 5 | use std::time::{Duration, Instant}; 6 | 7 | /// `Incoming` is a stream of incoming sockets 8 | /// Polling the stream may return a temporary io::Error (for instance if we can't open the connection because of "too many open files" limit) 9 | /// we use for_each combinator which: 10 | /// 1. Runs for every Ok(socket) 11 | /// 2. Stops on the FIRST Err() 12 | /// So any temporary io::Error will cause the entire server to terminate. 13 | /// This wrapper type for tokio::Incoming stops accepting new connections 14 | /// for a specified amount of time once an io::Error is encountered 15 | pub struct SuspendableStream { 16 | stream: S, 17 | next_delay: Duration, 18 | initial_delay: Duration, 19 | max_delay: Duration, 20 | suspended_until: Option, 21 | } 22 | 23 | impl SuspendableStream { 24 | /// construct a new Suspendable stream, given tokio::Incoming 25 | /// and the amount of time to pause for. 26 | pub fn new(stream: S) -> Self { 27 | SuspendableStream { 28 | stream, 29 | next_delay: Duration::from_millis(20), 30 | initial_delay: Duration::from_millis(10), 31 | max_delay: Duration::from_secs(5), 32 | suspended_until: None, 33 | } 34 | } 35 | } 36 | 37 | impl futures::Stream for SuspendableStream 38 | where 39 | S: futures::Stream> + Unpin, 40 | { 41 | type Item = I; 42 | 43 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { 44 | loop { 45 | // If we encountered a connection error before then we suspend 46 | // polling from the underlying stream for a bit 47 | if let Some(deadline) = &mut self.suspended_until { 48 | let deadline = tokio::time::Instant::from_std(*deadline); 49 | let sleep = tokio::time::sleep_until(deadline); 50 | futures::pin_mut!(sleep); 51 | match sleep.poll(cx) { 52 | Poll::Pending => return Poll::Pending, 53 | Poll::Ready(()) => { 54 | self.suspended_until = None; 55 | } 56 | } 57 | } 58 | 59 | match Pin::new(&mut self.stream).poll_next(cx) { 60 | Poll::Pending => return Poll::Pending, 61 | Poll::Ready(None) => { 62 | if self.next_delay > self.initial_delay { 63 | self.next_delay = self.initial_delay; 64 | } 65 | return Poll::Ready(None); 66 | } 67 | Poll::Ready(Some(Ok(item))) => { 68 | if self.next_delay > self.initial_delay { 69 | self.next_delay = self.initial_delay; 70 | } 71 | 72 | return Poll::Ready(Some(item)); 73 | } 74 | Poll::Ready(Some(Err(ref err))) => { 75 | if connection_error(err) { 76 | warn!("Connection Error: {:?}", err); 77 | continue; 78 | } 79 | self.next_delay = if self.next_delay < self.max_delay { 80 | self.next_delay * 2 81 | } else { 82 | self.next_delay 83 | }; 84 | debug!("Error accepting connection: {}", err); 85 | debug!("The server will stop accepting connections for {:?}", self.next_delay); 86 | self.suspended_until = Some(Instant::now() + self.next_delay); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | /// assert that the error was a connection error 94 | fn connection_error(e: &io::Error) -> bool { 95 | e.kind() == io::ErrorKind::ConnectionRefused 96 | || e.kind() == io::ErrorKind::ConnectionAborted 97 | || e.kind() == io::ErrorKind::ConnectionReset 98 | } 99 | -------------------------------------------------------------------------------- /stdio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "STDIN/STDOUT server for JSON-RPC" 4 | documentation = "https://docs.rs/jsonrpc-stdio-server/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | license = "MIT" 8 | name = "jsonrpc-stdio-server" 9 | repository = "https://github.com/paritytech/jsonrpc" 10 | version = "18.0.0" 11 | 12 | [dependencies] 13 | futures = "0.3" 14 | jsonrpc-core = { version = "18.0.0", path = "../core" } 15 | log = "0.4" 16 | tokio = { version = "1", features = ["io-std", "io-util"] } 17 | tokio-util = { version = "0.6", features = ["codec"] } 18 | 19 | [dev-dependencies] 20 | tokio = { version = "1", features = ["rt", "macros"] } 21 | lazy_static = "1.0" 22 | env_logger = "0.7" 23 | 24 | [badges] 25 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 26 | -------------------------------------------------------------------------------- /stdio/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-stdio-server 2 | STDIN/STDOUT server for JSON-RPC 2.0. 3 | Takes one request per line and outputs each response on a new line. 4 | 5 | [Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_stdio_server/index.html) 6 | 7 | ## Example 8 | 9 | `Cargo.toml` 10 | 11 | ``` 12 | [dependencies] 13 | jsonrpc-stdio-server = "15.0" 14 | ``` 15 | 16 | `main.rs` 17 | 18 | ```rust 19 | use jsonrpc_stdio_server::ServerBuilder; 20 | use jsonrpc_stdio_server::jsonrpc_core::*; 21 | 22 | fn main() { 23 | let mut io = IoHandler::default(); 24 | io.add_method("say_hello", |_params| { 25 | Ok(Value::String("hello".to_owned())) 26 | }); 27 | 28 | ServerBuilder::new(io).build(); 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /stdio/examples/stdio.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_stdio_server::jsonrpc_core::*; 2 | use jsonrpc_stdio_server::ServerBuilder; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | let mut io = IoHandler::default(); 7 | io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_owned()))); 8 | 9 | let server = ServerBuilder::new(io).build(); 10 | server.await; 11 | } 12 | -------------------------------------------------------------------------------- /stdio/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc server using stdin/stdout 2 | //! 3 | //! ```no_run 4 | //! 5 | //! use jsonrpc_stdio_server::ServerBuilder; 6 | //! use jsonrpc_stdio_server::jsonrpc_core::*; 7 | //! 8 | //! #[tokio::main] 9 | //! async fn main() { 10 | //! let mut io = IoHandler::default(); 11 | //! io.add_sync_method("say_hello", |_params| { 12 | //! Ok(Value::String("hello".to_owned())) 13 | //! }); 14 | //! 15 | //! let server = ServerBuilder::new(io).build(); 16 | //! server.await; 17 | //! } 18 | //! ``` 19 | 20 | #![deny(missing_docs)] 21 | 22 | use std::future::Future; 23 | use std::sync::Arc; 24 | 25 | #[macro_use] 26 | extern crate log; 27 | 28 | pub use jsonrpc_core; 29 | pub use tokio; 30 | 31 | use jsonrpc_core::{MetaIoHandler, Metadata, Middleware}; 32 | use tokio_util::codec::{FramedRead, LinesCodec}; 33 | 34 | /// Stdio server builder 35 | pub struct ServerBuilder = jsonrpc_core::NoopMiddleware> { 36 | handler: Arc>, 37 | } 38 | 39 | impl> ServerBuilder 40 | where 41 | M: Default, 42 | T::Future: Unpin, 43 | T::CallFuture: Unpin, 44 | { 45 | /// Returns a new server instance 46 | pub fn new(handler: impl Into>) -> Self { 47 | ServerBuilder { 48 | handler: Arc::new(handler.into()), 49 | } 50 | } 51 | 52 | /// Returns a server future that needs to be polled in order to make progress. 53 | /// 54 | /// Will block until EOF is read or until an error occurs. 55 | /// The server reads from STDIN line-by-line, one request is taken 56 | /// per line and each response is written to STDOUT on a new line. 57 | pub fn build(&self) -> impl Future + 'static { 58 | let handler = self.handler.clone(); 59 | 60 | async move { 61 | let stdin = tokio::io::stdin(); 62 | let mut stdout = tokio::io::stdout(); 63 | 64 | let mut framed_stdin = FramedRead::new(stdin, LinesCodec::new()); 65 | 66 | use futures::StreamExt; 67 | while let Some(request) = framed_stdin.next().await { 68 | match request { 69 | Ok(line) => { 70 | let res = Self::process(&handler, line).await; 71 | let mut sanitized = res.replace('\n', ""); 72 | sanitized.push('\n'); 73 | use tokio::io::AsyncWriteExt; 74 | if let Err(e) = stdout.write_all(sanitized.as_bytes()).await { 75 | log::warn!("Error writing response: {:?}", e); 76 | } 77 | } 78 | Err(e) => { 79 | log::warn!("Error reading line: {:?}", e); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | /// Process a request asynchronously 87 | fn process(io: &Arc>, input: String) -> impl Future + Send { 88 | use jsonrpc_core::futures::FutureExt; 89 | let f = io.handle_request(&input, Default::default()); 90 | f.map(move |result| match result { 91 | Some(res) => res, 92 | None => { 93 | info!("JSON RPC request produced no response: {:?}", input); 94 | String::from("") 95 | } 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "TCP/IP server for JSON-RPC" 4 | documentation = "https://docs.rs/jsonrpc-tcp-server/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | license = "MIT" 8 | name = "jsonrpc-tcp-server" 9 | repository = "https://github.com/paritytech/jsonrpc" 10 | version = "18.0.0" 11 | 12 | [dependencies] 13 | jsonrpc-core = { version = "18.0.0", path = "../core" } 14 | jsonrpc-server-utils = { version = "18.0.0", path = "../server-utils" } 15 | log = "0.4" 16 | parking_lot = "0.11.0" 17 | tower-service = "0.3" 18 | 19 | [dev-dependencies] 20 | lazy_static = "1.0" 21 | env_logger = "0.7" 22 | 23 | [badges] 24 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 25 | -------------------------------------------------------------------------------- /tcp/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-tcp-server 2 | TCP server for JSON-RPC 2.0. 3 | 4 | [Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_tcp_server/index.html) 5 | 6 | ## Example 7 | 8 | `Cargo.toml` 9 | 10 | ``` 11 | [dependencies] 12 | jsonrpc-tcp-server = "15.0" 13 | ``` 14 | 15 | `main.rs` 16 | 17 | ```rust 18 | use jsonrpc_tcp_server::*; 19 | use jsonrpc_tcp_server::jsonrpc_core::*; 20 | 21 | fn main() { 22 | let mut io = IoHandler::default(); 23 | io.add_method("say_hello", |_params| { 24 | Ok(Value::String("hello".to_owned())) 25 | }); 26 | 27 | let server = ServerBuilder::new(io) 28 | .start(&"0.0.0.0:3030".parse().unwrap()) 29 | .expect("Server must start with no issues"); 30 | 31 | server.wait().unwrap() 32 | } 33 | ``` 34 | 35 | 36 | -------------------------------------------------------------------------------- /tcp/examples/tcp.rs: -------------------------------------------------------------------------------- 1 | use env_logger; 2 | use jsonrpc_tcp_server::jsonrpc_core::*; 3 | use jsonrpc_tcp_server::ServerBuilder; 4 | 5 | fn main() { 6 | env_logger::init(); 7 | let mut io = IoHandler::default(); 8 | io.add_sync_method("say_hello", |_params| { 9 | println!("Processing"); 10 | Ok(Value::String("hello".to_owned())) 11 | }); 12 | 13 | let server = ServerBuilder::new(io) 14 | .start(&"0.0.0.0:3030".parse().unwrap()) 15 | .expect("Server must start with no issues"); 16 | 17 | server.wait() 18 | } 19 | -------------------------------------------------------------------------------- /tcp/src/dispatch.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::SocketAddr; 3 | use std::pin::Pin; 4 | use std::sync::Arc; 5 | use std::task::Poll; 6 | 7 | use crate::futures::{channel::mpsc, Stream}; 8 | 9 | use parking_lot::Mutex; 10 | 11 | pub type SenderChannels = Mutex>>; 12 | 13 | pub struct PeerMessageQueue { 14 | up: S, 15 | receiver: Option>, 16 | _addr: SocketAddr, 17 | } 18 | 19 | impl PeerMessageQueue { 20 | pub fn new(response_stream: S, receiver: mpsc::UnboundedReceiver, addr: SocketAddr) -> Self { 21 | PeerMessageQueue { 22 | up: response_stream, 23 | receiver: Some(receiver), 24 | _addr: addr, 25 | } 26 | } 27 | } 28 | 29 | /// Push Message Error 30 | #[derive(Debug)] 31 | pub enum PushMessageError { 32 | /// Invalid peer 33 | NoSuchPeer, 34 | /// Send error 35 | Send(mpsc::TrySendError), 36 | } 37 | 38 | impl From> for PushMessageError { 39 | fn from(send_err: mpsc::TrySendError) -> Self { 40 | PushMessageError::Send(send_err) 41 | } 42 | } 43 | 44 | /// Peer-messages dispatcher. 45 | #[derive(Clone)] 46 | pub struct Dispatcher { 47 | channels: Arc, 48 | } 49 | 50 | impl Dispatcher { 51 | /// Creates a new dispatcher 52 | pub fn new(channels: Arc) -> Self { 53 | Dispatcher { channels } 54 | } 55 | 56 | /// Pushes message to given peer 57 | pub fn push_message(&self, peer_addr: &SocketAddr, msg: String) -> Result<(), PushMessageError> { 58 | let mut channels = self.channels.lock(); 59 | 60 | match channels.get_mut(peer_addr) { 61 | Some(channel) => { 62 | channel.unbounded_send(msg).map_err(PushMessageError::from)?; 63 | Ok(()) 64 | } 65 | None => Err(PushMessageError::NoSuchPeer), 66 | } 67 | } 68 | 69 | /// Returns `true` if the peer is still connnected 70 | pub fn is_connected(&self, socket_addr: &SocketAddr) -> bool { 71 | self.channels.lock().contains_key(socket_addr) 72 | } 73 | 74 | /// Returns current peer count. 75 | pub fn peer_count(&self) -> usize { 76 | self.channels.lock().len() 77 | } 78 | } 79 | 80 | impl> + Unpin> Stream for PeerMessageQueue { 81 | type Item = std::io::Result; 82 | 83 | // The receiver will never return `Ok(Async::Ready(None))` 84 | // Because the sender is kept in `SenderChannels` and it will never be dropped until `the stream` is resolved. 85 | // 86 | // Thus, that is the reason we terminate if `up_closed && receiver == Async::NotReady`. 87 | // 88 | // However, it is possible to have a race between `poll` and `push_work` if the connection is dropped. 89 | // Therefore, the receiver is then dropped when the connection is dropped and an error is propagated when 90 | // a `send` attempt is made on that channel. 91 | fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { 92 | // check if we have response pending 93 | let this = Pin::into_inner(self); 94 | 95 | let up_closed = match Pin::new(&mut this.up).poll_next(cx) { 96 | Poll::Ready(Some(Ok(item))) => return Poll::Ready(Some(Ok(item))), 97 | Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), 98 | Poll::Ready(None) => true, 99 | Poll::Pending => false, 100 | }; 101 | 102 | let mut rx = match &mut this.receiver { 103 | None => { 104 | debug_assert!(up_closed); 105 | return Poll::Ready(None); 106 | } 107 | Some(rx) => rx, 108 | }; 109 | 110 | match Pin::new(&mut rx).poll_next(cx) { 111 | Poll::Ready(Some(item)) => Poll::Ready(Some(Ok(item))), 112 | Poll::Ready(None) | Poll::Pending if up_closed => { 113 | this.receiver = None; 114 | Poll::Ready(None) 115 | } 116 | Poll::Ready(None) | Poll::Pending => Poll::Pending, 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tcp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! jsonrpc server over tcp/ip 2 | //! 3 | //! ```no_run 4 | //! use jsonrpc_core::*; 5 | //! use jsonrpc_tcp_server::ServerBuilder; 6 | //! 7 | //! fn main() { 8 | //! let mut io = IoHandler::default(); 9 | //! io.add_sync_method("say_hello", |_params| { 10 | //! Ok(Value::String("hello".to_string())) 11 | //! }); 12 | //! let server = ServerBuilder::new(io) 13 | //! .start(&"0.0.0.0:0".parse().unwrap()) 14 | //! .expect("Server must start with no issues."); 15 | //! 16 | //! server.wait(); 17 | //! } 18 | //! ``` 19 | 20 | #![deny(missing_docs)] 21 | 22 | use jsonrpc_server_utils as server_utils; 23 | 24 | pub use jsonrpc_core; 25 | 26 | #[macro_use] 27 | extern crate log; 28 | 29 | #[cfg(test)] 30 | #[macro_use] 31 | extern crate lazy_static; 32 | 33 | mod dispatch; 34 | mod meta; 35 | mod server; 36 | mod service; 37 | 38 | #[cfg(test)] 39 | mod logger; 40 | #[cfg(test)] 41 | mod tests; 42 | 43 | use jsonrpc_core as jsonrpc; 44 | 45 | pub(crate) use crate::jsonrpc::futures; 46 | 47 | pub use self::server_utils::{codecs::Separator, tokio}; 48 | pub use crate::dispatch::{Dispatcher, PushMessageError}; 49 | pub use crate::meta::{MetaExtractor, RequestContext}; 50 | pub use crate::server::{Server, ServerBuilder}; 51 | -------------------------------------------------------------------------------- /tcp/src/logger.rs: -------------------------------------------------------------------------------- 1 | use env_logger::Builder; 2 | use log::LevelFilter; 3 | use std::env; 4 | 5 | lazy_static! { 6 | static ref LOG_DUMMY: bool = { 7 | let mut builder = Builder::new(); 8 | builder.filter(None, LevelFilter::Info); 9 | 10 | if let Ok(log) = env::var("RUST_LOG") { 11 | builder.parse_filters(&log); 12 | } 13 | 14 | if let Ok(_) = builder.try_init() { 15 | println!("logger initialized"); 16 | } 17 | true 18 | }; 19 | } 20 | 21 | /// Intialize log with default settings 22 | pub fn init_log() { 23 | let _ = *LOG_DUMMY; 24 | } 25 | -------------------------------------------------------------------------------- /tcp/src/meta.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use crate::jsonrpc::{futures::channel::mpsc, Metadata}; 4 | 5 | /// Request context 6 | pub struct RequestContext { 7 | /// Peer Address 8 | pub peer_addr: SocketAddr, 9 | /// Peer Sender channel 10 | pub sender: mpsc::UnboundedSender, 11 | } 12 | 13 | /// Metadata extractor (per session) 14 | pub trait MetaExtractor: Send + Sync { 15 | /// Extracts metadata from request context 16 | fn extract(&self, context: &RequestContext) -> M; 17 | } 18 | 19 | impl MetaExtractor for F 20 | where 21 | M: Metadata, 22 | F: Fn(&RequestContext) -> M + Send + Sync, 23 | { 24 | fn extract(&self, context: &RequestContext) -> M { 25 | (*self)(context) 26 | } 27 | } 28 | 29 | /// Noop-extractor 30 | pub struct NoopExtractor; 31 | impl MetaExtractor for NoopExtractor { 32 | fn extract(&self, _context: &RequestContext) -> M { 33 | M::default() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tcp/src/service.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::net::SocketAddr; 3 | use std::pin::Pin; 4 | use std::sync::Arc; 5 | use std::task::{Context, Poll}; 6 | 7 | use crate::futures; 8 | use crate::jsonrpc::{middleware, MetaIoHandler, Metadata, Middleware}; 9 | 10 | pub struct Service = middleware::Noop> { 11 | handler: Arc>, 12 | peer_addr: SocketAddr, 13 | meta: M, 14 | } 15 | 16 | impl> Service { 17 | pub fn new(peer_addr: SocketAddr, handler: Arc>, meta: M) -> Self { 18 | Service { 19 | handler, 20 | peer_addr, 21 | meta, 22 | } 23 | } 24 | } 25 | 26 | impl> tower_service::Service for Service 27 | where 28 | S::Future: Unpin, 29 | S::CallFuture: Unpin, 30 | { 31 | // These types must match the corresponding protocol types: 32 | type Response = Option; 33 | // For non-streaming protocols, service errors are always io::Error 34 | type Error = (); 35 | 36 | // The future for computing the response; box it for simplicity. 37 | type Future = Pin> + Send>>; 38 | 39 | fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { 40 | Poll::Ready(Ok(())) 41 | } 42 | 43 | // Produce a future for computing a response from a request. 44 | fn call(&mut self, req: String) -> Self::Future { 45 | use futures::FutureExt; 46 | trace!(target: "tcp", "Accepted request from peer {}: {}", &self.peer_addr, req); 47 | Box::pin(self.handler.handle_request(&req, self.meta.clone()).map(Ok)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpc-test" 3 | description = "Simple test framework for JSON-RPC." 4 | version = "18.0.0" 5 | authors = ["Tomasz Drwięga "] 6 | license = "MIT" 7 | homepage = "https://github.com/paritytech/jsonrpc" 8 | repository = "https://github.com/paritytech/jsonrpc" 9 | documentation = "https://docs.rs/jsonrpc-test/" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | jsonrpc-core = { version = "18.0.0", path = "../core" } 14 | jsonrpc-core-client = { version = "18.0.0", path = "../core-client" } 15 | jsonrpc-pubsub = { version = "18.0.0", path = "../pubsub" } 16 | log = "0.4" 17 | serde = "1.0" 18 | serde_json = "1.0" 19 | 20 | [features] 21 | arbitrary_precision = ["jsonrpc-core-client/arbitrary_precision", "serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] 22 | 23 | [dev-dependencies] 24 | jsonrpc-derive = { version = "18.0.0", path = "../derive" } 25 | 26 | -------------------------------------------------------------------------------- /test/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An utility package to test jsonrpc-core based projects. 2 | //! 3 | //! ``` 4 | //! use jsonrpc_derive::rpc; 5 | //! use jsonrpc_test as test; 6 | //! 7 | //! use jsonrpc_core::{Result, Error, IoHandler}; 8 | //! 9 | //! #[rpc] 10 | //! pub trait Test { 11 | //! #[rpc(name = "rpc_some_method")] 12 | //! fn some_method(&self, a: u64) -> Result; 13 | //! } 14 | //! 15 | //! 16 | //! struct Dummy; 17 | //! impl Test for Dummy { 18 | //! fn some_method(&self, x: u64) -> Result { 19 | //! Ok(x * 2) 20 | //! } 21 | //! } 22 | //! 23 | //! fn main() { 24 | //! // Initialize new instance of test environment 25 | //! let rpc = test::Rpc::new(Dummy.to_delegate()); 26 | //! 27 | //! // make a request and verify the response as a pretty-printed string 28 | //! assert_eq!(rpc.request("rpc_some_method", &[5]), r#"10"#); 29 | //! 30 | //! // You can also test RPC created without macros: 31 | //! let rpc = { 32 | //! let mut io = IoHandler::new(); 33 | //! io.add_sync_method("rpc_test_method", |_| { 34 | //! Err(Error::internal_error()) 35 | //! }); 36 | //! test::Rpc::from(io) 37 | //! }; 38 | //! 39 | //! assert_eq!(rpc.request("rpc_test_method", &()), r#"{ 40 | //! "code": -32603, 41 | //! "message": "Internal error" 42 | //! }"#); 43 | //! } 44 | //! ``` 45 | 46 | #![deny(missing_docs)] 47 | 48 | extern crate jsonrpc_core as rpc; 49 | 50 | /// Test RPC options. 51 | #[derive(Default, Debug)] 52 | pub struct Options { 53 | /// Disable printing requests and responses. 54 | pub no_print: bool, 55 | } 56 | 57 | #[derive(Default, Debug)] 58 | /// RPC instance. 59 | pub struct Rpc { 60 | /// Underlying `IoHandler`. 61 | pub io: rpc::IoHandler, 62 | /// Options 63 | pub options: Options, 64 | } 65 | 66 | /// Encoding format. 67 | pub enum Encoding { 68 | /// Encodes params using `serde::to_string`. 69 | Compact, 70 | /// Encodes params using `serde::to_string_pretty`. 71 | Pretty, 72 | } 73 | 74 | impl From for Rpc { 75 | fn from(io: rpc::IoHandler) -> Self { 76 | Rpc { 77 | io, 78 | ..Default::default() 79 | } 80 | } 81 | } 82 | 83 | impl Rpc { 84 | /// Create a new RPC instance from a single delegate. 85 | pub fn new(delegate: D) -> Self 86 | where 87 | D: IntoIterator)>, 88 | { 89 | let mut io = rpc::IoHandler::new(); 90 | io.extend_with(delegate); 91 | io.into() 92 | } 93 | 94 | /// Perform a single, synchronous method call and return pretty-printed value 95 | pub fn request(&self, method: &str, params: &T) -> String 96 | where 97 | T: serde::Serialize, 98 | { 99 | self.make_request(method, params, Encoding::Pretty) 100 | } 101 | 102 | /// Perform a single, synchronous method call. 103 | pub fn make_request(&self, method: &str, params: &T, encoding: Encoding) -> String 104 | where 105 | T: serde::Serialize, 106 | { 107 | use self::rpc::types::response; 108 | 109 | let request = format!( 110 | "{{ \"jsonrpc\":\"2.0\", \"id\": 1, \"method\": \"{}\", \"params\": {} }}", 111 | method, 112 | serde_json::to_string_pretty(params).expect("Serialization should be infallible."), 113 | ); 114 | 115 | let response = self 116 | .io 117 | .handle_request_sync(&request) 118 | .expect("We are sending a method call not notification."); 119 | 120 | // extract interesting part from the response 121 | let extracted = match rpc::serde_from_str(&response).expect("We will always get a single output.") { 122 | response::Output::Success(response::Success { result, .. }) => match encoding { 123 | Encoding::Compact => serde_json::to_string(&result), 124 | Encoding::Pretty => serde_json::to_string_pretty(&result), 125 | }, 126 | response::Output::Failure(response::Failure { error, .. }) => match encoding { 127 | Encoding::Compact => serde_json::to_string(&error), 128 | Encoding::Pretty => serde_json::to_string_pretty(&error), 129 | }, 130 | } 131 | .expect("Serialization is infallible; qed"); 132 | 133 | println!("\n{}\n --> {}\n", request, extracted); 134 | 135 | extracted 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::*; 142 | 143 | #[test] 144 | fn should_test_request_is_pretty() { 145 | // given 146 | let rpc = { 147 | let mut io = rpc::IoHandler::new(); 148 | io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); 149 | Rpc::from(io) 150 | }; 151 | 152 | // when 153 | assert_eq!(rpc.request("test_method", &[5u64]), "[\n 5,\n 10\n]"); 154 | } 155 | 156 | #[test] 157 | fn should_test_make_request_compact() { 158 | // given 159 | let rpc = { 160 | let mut io = rpc::IoHandler::new(); 161 | io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); 162 | Rpc::from(io) 163 | }; 164 | 165 | // when 166 | assert_eq!(rpc.make_request("test_method", &[5u64], Encoding::Compact), "[5,10]"); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ws/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Parity Technologies "] 3 | description = "WebSockets server for JSON-RPC" 4 | documentation = "https://docs.rs/jsonrpc-ws-server/" 5 | edition = "2018" 6 | homepage = "https://github.com/paritytech/jsonrpc" 7 | license = "MIT" 8 | name = "jsonrpc-ws-server" 9 | repository = "https://github.com/paritytech/jsonrpc" 10 | version = "18.0.0" 11 | 12 | [dependencies] 13 | futures = "0.3" 14 | jsonrpc-core = { version = "18.0.0", path = "../core" } 15 | jsonrpc-server-utils = { version = "18.0.0", path = "../server-utils" } 16 | log = "0.4" 17 | parking_lot = "0.11.0" 18 | slab = "0.4" 19 | parity-ws = "0.11.1" 20 | 21 | [badges] 22 | travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} 23 | -------------------------------------------------------------------------------- /ws/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-ws-server 2 | WebSockets server for JSON-RPC 2.0. 3 | 4 | [Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_ws_server/index.html) 5 | 6 | ## Example 7 | 8 | `Cargo.toml` 9 | 10 | ``` 11 | [dependencies] 12 | jsonrpc-ws-server = "15.0" 13 | ``` 14 | 15 | `main.rs` 16 | 17 | ```rust 18 | use jsonrpc_ws_server::*; 19 | use jsonrpc_ws_server::jsonrpc_core::*; 20 | 21 | fn main() { 22 | let mut io = IoHandler::new(); 23 | io.add_method("say_hello", |_params| { 24 | Ok(Value::String("hello".into())) 25 | }); 26 | 27 | let server = ServerBuilder::new(io) 28 | .start(&"0.0.0.0:3030".parse().unwrap()) 29 | .expect("Server must start with no issues"); 30 | 31 | server.wait().unwrap() 32 | } 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /ws/examples/ws.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_ws_server::jsonrpc_core::*; 2 | use jsonrpc_ws_server::ServerBuilder; 3 | 4 | fn main() { 5 | let mut io = IoHandler::default(); 6 | io.add_sync_method("say_hello", |_params| { 7 | println!("Processing"); 8 | Ok(Value::String("hello".to_owned())) 9 | }); 10 | 11 | let server = ServerBuilder::new(io) 12 | .start(&"0.0.0.0:3030".parse().unwrap()) 13 | .expect("Server must start with no issues"); 14 | 15 | server.wait().unwrap() 16 | } 17 | -------------------------------------------------------------------------------- /ws/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt, io, result}; 2 | 3 | use crate::ws; 4 | 5 | /// WebSockets Server Error 6 | #[derive(Debug)] 7 | pub enum Error { 8 | /// Io Error 9 | Io(io::Error), 10 | /// WebSockets Error 11 | WsError(ws::Error), 12 | /// Connection Closed 13 | ConnectionClosed, 14 | } 15 | 16 | /// WebSockets Server Result 17 | pub type Result = result::Result; 18 | 19 | impl fmt::Display for Error { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 21 | match self { 22 | Error::ConnectionClosed => write!(f, "Action on closed connection."), 23 | Error::WsError(err) => write!(f, "WebSockets Error: {}", err), 24 | Error::Io(err) => write!(f, "Io Error: {}", err), 25 | } 26 | } 27 | } 28 | 29 | impl error::Error for Error { 30 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 31 | match self { 32 | Error::Io(io) => Some(io), 33 | Error::WsError(ws) => Some(ws), 34 | Error::ConnectionClosed => None, 35 | } 36 | } 37 | } 38 | 39 | impl From for Error { 40 | fn from(err: io::Error) -> Self { 41 | Error::Io(err) 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(err: ws::Error) -> Self { 47 | match err.kind { 48 | ws::ErrorKind::Io(err) => Error::Io(err), 49 | _ => Error::WsError(err), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ws/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `WebSockets` server. 2 | 3 | #![deny(missing_docs)] 4 | 5 | use jsonrpc_server_utils as server_utils; 6 | 7 | pub use jsonrpc_core; 8 | pub use parity_ws as ws; 9 | 10 | #[macro_use] 11 | extern crate log; 12 | 13 | mod error; 14 | mod metadata; 15 | mod server; 16 | mod server_builder; 17 | mod session; 18 | #[cfg(test)] 19 | mod tests; 20 | 21 | use jsonrpc_core as core; 22 | 23 | pub use self::error::{Error, Result}; 24 | pub use self::metadata::{MetaExtractor, NoopExtractor, RequestContext}; 25 | pub use self::server::{Broadcaster, CloseHandle, Server}; 26 | pub use self::server_builder::ServerBuilder; 27 | pub use self::server_utils::cors::Origin; 28 | pub use self::server_utils::hosts::{DomainsValidation, Host}; 29 | pub use self::server_utils::session::{SessionId, SessionStats}; 30 | pub use self::server_utils::tokio; 31 | pub use self::session::{MiddlewareAction, RequestMiddleware}; 32 | -------------------------------------------------------------------------------- /ws/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | use std::sync::{atomic, Arc}; 5 | use std::task::{Context, Poll}; 6 | 7 | use crate::core; 8 | use crate::core::futures::channel::mpsc; 9 | use crate::server_utils::{reactor::TaskExecutor, session}; 10 | use crate::ws; 11 | 12 | use crate::error; 13 | use crate::Origin; 14 | 15 | /// Output of WebSocket connection. Use this to send messages to the other endpoint. 16 | #[derive(Clone)] 17 | pub struct Sender { 18 | out: ws::Sender, 19 | active: Arc, 20 | } 21 | 22 | impl Sender { 23 | /// Creates a new `Sender`. 24 | pub fn new(out: ws::Sender, active: Arc) -> Self { 25 | Sender { out, active } 26 | } 27 | 28 | fn check_active(&self) -> error::Result<()> { 29 | if self.active.load(atomic::Ordering::SeqCst) { 30 | Ok(()) 31 | } else { 32 | Err(error::Error::ConnectionClosed) 33 | } 34 | } 35 | 36 | /// Sends a message over the connection. 37 | /// Will return error if the connection is not active any more. 38 | pub fn send(&self, msg: M) -> error::Result<()> 39 | where 40 | M: Into, 41 | { 42 | self.check_active()?; 43 | self.out.send(msg)?; 44 | Ok(()) 45 | } 46 | 47 | /// Sends a message over the endpoints of all connections. 48 | /// Will return error if the connection is not active any more. 49 | pub fn broadcast(&self, msg: M) -> error::Result<()> 50 | where 51 | M: Into, 52 | { 53 | self.check_active()?; 54 | self.out.broadcast(msg)?; 55 | Ok(()) 56 | } 57 | 58 | /// Sends a close code to the other endpoint. 59 | /// Will return error if the connection is not active any more. 60 | pub fn close(&self, code: ws::CloseCode) -> error::Result<()> { 61 | self.check_active()?; 62 | self.out.close(code)?; 63 | Ok(()) 64 | } 65 | } 66 | 67 | /// Request context 68 | pub struct RequestContext { 69 | /// Session id 70 | pub session_id: session::SessionId, 71 | /// Request Origin 72 | pub origin: Option, 73 | /// Requested protocols 74 | pub protocols: Vec, 75 | /// Direct channel to send messages to a client. 76 | pub out: Sender, 77 | /// Remote to underlying event loop. 78 | pub executor: TaskExecutor, 79 | } 80 | 81 | impl RequestContext { 82 | /// Get this session as a `Sink` spawning a new future 83 | /// in the underlying event loop. 84 | pub fn sender(&self) -> mpsc::UnboundedSender { 85 | let out = self.out.clone(); 86 | let (sender, receiver) = mpsc::unbounded(); 87 | self.executor.spawn(SenderFuture(out, Box::new(receiver))); 88 | sender 89 | } 90 | } 91 | 92 | impl fmt::Debug for RequestContext { 93 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 94 | fmt.debug_struct("RequestContext") 95 | .field("session_id", &self.session_id) 96 | .field("origin", &self.origin) 97 | .field("protocols", &self.protocols) 98 | .finish() 99 | } 100 | } 101 | 102 | /// Metadata extractor from session data. 103 | pub trait MetaExtractor: Send + Sync + 'static { 104 | /// Extract metadata for given session 105 | fn extract(&self, _context: &RequestContext) -> M; 106 | } 107 | 108 | impl MetaExtractor for F 109 | where 110 | M: core::Metadata, 111 | F: Fn(&RequestContext) -> M + Send + Sync + 'static, 112 | { 113 | fn extract(&self, context: &RequestContext) -> M { 114 | (*self)(context) 115 | } 116 | } 117 | 118 | /// Dummy metadata extractor 119 | #[derive(Debug, Clone)] 120 | pub struct NoopExtractor; 121 | impl MetaExtractor for NoopExtractor { 122 | fn extract(&self, _context: &RequestContext) -> M { 123 | M::default() 124 | } 125 | } 126 | 127 | struct SenderFuture(Sender, Box + Send + Unpin>); 128 | 129 | impl Future for SenderFuture { 130 | type Output = (); 131 | 132 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 133 | use futures::Stream; 134 | 135 | let this = Pin::into_inner(self); 136 | loop { 137 | match Pin::new(&mut this.1).poll_next(cx) { 138 | Poll::Pending => return Poll::Pending, 139 | Poll::Ready(None) => return Poll::Ready(()), 140 | Poll::Ready(Some(val)) => { 141 | if let Err(e) = this.0.send(val) { 142 | warn!("Error sending a subscription update: {:?}", e); 143 | return Poll::Ready(()); 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ws/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::sync::{Arc, Mutex}; 3 | use std::thread; 4 | use std::{cmp, fmt}; 5 | 6 | use crate::core; 7 | use crate::server_utils::cors::Origin; 8 | use crate::server_utils::hosts::{self, Host}; 9 | use crate::server_utils::reactor::{Executor, UninitializedExecutor}; 10 | use crate::server_utils::session::SessionStats; 11 | use crate::ws; 12 | 13 | use crate::error::{Error, Result}; 14 | use crate::metadata; 15 | use crate::session; 16 | 17 | /// `WebSockets` server implementation. 18 | pub struct Server { 19 | addr: SocketAddr, 20 | handle: Option>>, 21 | executor: Arc>>, 22 | broadcaster: ws::Sender, 23 | } 24 | 25 | impl fmt::Debug for Server { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | f.debug_struct("Server") 28 | .field("addr", &self.addr) 29 | .field("handle", &self.handle) 30 | .field("executor", &self.executor) 31 | .finish() 32 | } 33 | } 34 | 35 | impl Server { 36 | /// Returns the address this server is listening on 37 | pub fn addr(&self) -> &SocketAddr { 38 | &self.addr 39 | } 40 | 41 | /// Returns a Broadcaster that can be used to send messages on all connections. 42 | pub fn broadcaster(&self) -> Broadcaster { 43 | Broadcaster { 44 | broadcaster: self.broadcaster.clone(), 45 | } 46 | } 47 | 48 | /// Starts a new `WebSocket` server in separate thread. 49 | /// Returns a `Server` handle which closes the server when droped. 50 | pub fn start>( 51 | addr: &SocketAddr, 52 | handler: Arc>, 53 | meta_extractor: Arc>, 54 | allowed_origins: Option>, 55 | allowed_hosts: Option>, 56 | request_middleware: Option>, 57 | stats: Option>, 58 | executor: UninitializedExecutor, 59 | max_connections: usize, 60 | max_payload_bytes: usize, 61 | max_in_buffer_capacity: usize, 62 | max_out_buffer_capacity: usize, 63 | ) -> Result 64 | where 65 | S::Future: Unpin, 66 | S::CallFuture: Unpin, 67 | { 68 | let config = { 69 | let mut config = ws::Settings::default(); 70 | config.max_connections = max_connections; 71 | // don't accept super large requests 72 | config.max_fragment_size = max_payload_bytes; 73 | config.max_total_fragments_size = max_payload_bytes; 74 | config.in_buffer_capacity_hard_limit = max_in_buffer_capacity; 75 | config.out_buffer_capacity_hard_limit = max_out_buffer_capacity; 76 | // don't grow non-final fragments (to prevent DOS) 77 | config.fragments_grow = false; 78 | config.fragments_capacity = cmp::max(1, max_payload_bytes / config.fragment_size); 79 | if config.fragments_capacity > 4096 { 80 | config.fragments_capacity = 4096; 81 | config.fragments_grow = true; 82 | } 83 | // accept only handshakes beginning with GET 84 | config.method_strict = true; 85 | // require masking 86 | config.masking_strict = true; 87 | // Was shutting down server when suspending on linux: 88 | config.shutdown_on_interrupt = false; 89 | config 90 | }; 91 | 92 | // Update allowed_hosts 93 | let allowed_hosts = hosts::update(allowed_hosts, addr); 94 | 95 | // Spawn event loop (if necessary) 96 | let eloop = executor.initialize()?; 97 | let executor = eloop.executor(); 98 | 99 | // Create WebSocket 100 | let ws = ws::Builder::new().with_settings(config).build(session::Factory::new( 101 | handler, 102 | meta_extractor, 103 | allowed_origins, 104 | allowed_hosts, 105 | request_middleware, 106 | stats, 107 | executor, 108 | ))?; 109 | let broadcaster = ws.broadcaster(); 110 | 111 | // Start listening... 112 | let ws = ws.bind(addr)?; 113 | let local_addr = ws.local_addr()?; 114 | debug!("Bound to local address: {}", local_addr); 115 | 116 | // Spawn a thread with event loop 117 | let handle = thread::spawn(move || match ws.run().map_err(Error::from) { 118 | Err(error) => { 119 | error!("Error while running websockets server. Details: {:?}", error); 120 | Err(error) 121 | } 122 | Ok(_server) => Ok(()), 123 | }); 124 | 125 | // Return a handle 126 | Ok(Server { 127 | addr: local_addr, 128 | handle: Some(handle), 129 | executor: Arc::new(Mutex::new(Some(eloop))), 130 | broadcaster, 131 | }) 132 | } 133 | } 134 | 135 | impl Server { 136 | /// Consumes the server and waits for completion 137 | pub fn wait(mut self) -> Result<()> { 138 | self.handle 139 | .take() 140 | .expect("Handle is always Some at start.") 141 | .join() 142 | .expect("Non-panic exit") 143 | } 144 | 145 | /// Closes the server and waits for it to finish 146 | pub fn close(self) { 147 | self.close_handle().close(); 148 | } 149 | 150 | /// Returns a handle to the server that can be used to close it while another thread is 151 | /// blocking in `wait`. 152 | pub fn close_handle(&self) -> CloseHandle { 153 | CloseHandle { 154 | executor: self.executor.clone(), 155 | broadcaster: self.broadcaster.clone(), 156 | } 157 | } 158 | } 159 | 160 | impl Drop for Server { 161 | fn drop(&mut self) { 162 | self.close_handle().close(); 163 | self.handle.take().map(|handle| handle.join()); 164 | } 165 | } 166 | 167 | /// A handle that allows closing of a server even if it owned by a thread blocked in `wait`. 168 | #[derive(Clone)] 169 | pub struct CloseHandle { 170 | executor: Arc>>, 171 | broadcaster: ws::Sender, 172 | } 173 | 174 | impl CloseHandle { 175 | /// Closes the `Server`. 176 | pub fn close(self) { 177 | let _ = self.broadcaster.shutdown(); 178 | if let Some(executor) = self.executor.lock().unwrap().take() { 179 | executor.close() 180 | } 181 | } 182 | } 183 | 184 | /// A Broadcaster that can be used to send messages on all connections. 185 | #[derive(Clone)] 186 | pub struct Broadcaster { 187 | broadcaster: ws::Sender, 188 | } 189 | 190 | impl Broadcaster { 191 | /// Send a message to the endpoints of all connections. 192 | #[inline] 193 | pub fn send(&self, msg: M) -> Result<()> 194 | where 195 | M: Into, 196 | { 197 | match self.broadcaster.send(msg).map_err(Error::from) { 198 | Err(error) => { 199 | error!("Error while running sending. Details: {:?}", error); 200 | Err(error) 201 | } 202 | Ok(_server) => Ok(()), 203 | } 204 | } 205 | } 206 | --------------------------------------------------------------------------------