├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── benchmarks.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── Cargo.toml ├── LICENSE ├── README.md ├── RELEASE-CHECKLIST.md ├── benches ├── Cargo.toml ├── README.md ├── bench.rs └── helpers.rs ├── client ├── http-client │ ├── Cargo.toml │ └── src │ │ ├── client.rs │ │ ├── lib.rs │ │ ├── rpc_service.rs │ │ ├── tests.rs │ │ └── transport.rs ├── transport │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── web.rs │ │ └── ws │ │ ├── mod.rs │ │ └── stream.rs ├── wasm-client │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── ws-client │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── tests.rs ├── core ├── Cargo.toml └── src │ ├── client │ ├── async_client │ │ ├── helpers.rs │ │ ├── manager.rs │ │ ├── mod.rs │ │ ├── rpc_service.rs │ │ └── utils.rs │ ├── error.rs │ └── mod.rs │ ├── error.rs │ ├── http_helpers.rs │ ├── id_providers.rs │ ├── lib.rs │ ├── macros.rs │ ├── middleware │ ├── layer │ │ ├── either.rs │ │ ├── logger.rs │ │ └── mod.rs │ └── mod.rs │ ├── params.rs │ ├── proc_macros_support.rs │ ├── server │ ├── error.rs │ ├── helpers.rs │ ├── method_response.rs │ ├── mod.rs │ ├── rpc_module.rs │ └── subscription.rs │ └── traits.rs ├── examples ├── Cargo.toml └── examples │ ├── client_subscription_drop_oldest_item.rs │ ├── core_client.rs │ ├── cors_server.rs │ ├── host_filter_middleware.rs │ ├── http.rs │ ├── http_middleware.rs │ ├── http_proxy_middleware.rs │ ├── jsonrpsee_as_service.rs │ ├── jsonrpsee_server_close_connection_from_rpc_handler.rs │ ├── jsonrpsee_server_low_level_api.rs │ ├── proc_macro.rs │ ├── proc_macro_bounds.rs │ ├── response_payload_notify_on_response.rs │ ├── rpc_middleware.rs │ ├── rpc_middleware_client.rs │ ├── rpc_middleware_modify_request.rs │ ├── rpc_middleware_rate_limiting.rs │ ├── server_with_connection_details.rs │ ├── tokio_console.rs │ ├── ws.rs │ ├── ws_dual_stack.rs │ ├── ws_pubsub_broadcast.rs │ └── ws_pubsub_with_params.rs ├── jsonrpsee ├── Cargo.toml └── src │ ├── lib.rs │ └── macros.rs ├── proc-macros ├── Cargo.toml ├── src │ ├── attributes.rs │ ├── helpers.rs │ ├── lib.rs │ ├── render_client.rs │ ├── render_server.rs │ ├── rpc_macro.rs │ └── visitor.rs └── tests │ ├── ui.rs │ └── ui │ ├── correct │ ├── alias_doesnt_use_namespace.rs │ ├── basic.rs │ ├── custom_ret_types.rs │ ├── only_client.rs │ ├── only_server.rs │ ├── param_kind.rs │ ├── parse_angle_brackets.rs │ ├── rpc_bounds.rs │ ├── rpc_deny_missing_docs.rs │ └── server_with_raw_methods.rs │ └── incorrect │ ├── method │ ├── method_ignored_arguments.rs │ ├── method_ignored_arguments.stderr │ ├── method_no_name.rs │ ├── method_no_name.stderr │ ├── method_non_result_return_type.rs │ ├── method_non_result_return_type.stderr │ ├── method_unexpected_field.rs │ └── method_unexpected_field.stderr │ ├── rpc │ ├── rpc_assoc_items.rs │ ├── rpc_assoc_items.stderr │ ├── rpc_bounds_without_impl.rs │ ├── rpc_bounds_without_impl.stderr │ ├── rpc_conflicting_alias.rs │ ├── rpc_conflicting_alias.stderr │ ├── rpc_deprecated_method.rs │ ├── rpc_deprecated_method.stderr │ ├── rpc_empty.rs │ ├── rpc_empty.stderr │ ├── rpc_empty_bounds.rs │ ├── rpc_empty_bounds.stderr │ ├── rpc_name_conflict.rs │ ├── rpc_name_conflict.stderr │ ├── rpc_no_impls.rs │ ├── rpc_no_impls.stderr │ ├── rpc_not_qualified.rs │ └── rpc_not_qualified.stderr │ └── sub │ ├── sub_conflicting_alias.rs │ ├── sub_conflicting_alias.stderr │ ├── sub_dup_name_override.rs │ ├── sub_dup_name_override.stderr │ ├── sub_empty_attr.rs │ ├── sub_empty_attr.stderr │ ├── sub_name_override.rs │ ├── sub_name_override.stderr │ ├── sub_no_item.rs │ ├── sub_no_item.stderr │ ├── sub_no_name.rs │ ├── sub_no_name.stderr │ ├── sub_unsupported_field.rs │ └── sub_unsupported_field.stderr ├── rustfmt.toml ├── scripts ├── generate_changelog.sh └── publish.sh ├── server ├── Cargo.toml └── src │ ├── future.rs │ ├── lib.rs │ ├── middleware │ ├── http │ │ ├── authority.rs │ │ ├── host_filter.rs │ │ ├── mod.rs │ │ └── proxy_get_request.rs │ ├── mod.rs │ └── rpc.rs │ ├── server.rs │ ├── tests │ ├── helpers.rs │ ├── http.rs │ ├── mod.rs │ ├── shared.rs │ └── ws.rs │ ├── transport │ ├── http.rs │ ├── mod.rs │ └── ws.rs │ └── utils.rs ├── test-utils ├── Cargo.toml └── src │ ├── helpers.rs │ ├── lib.rs │ └── mocks.rs ├── tests ├── Cargo.toml ├── proc-macro-core │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── tests │ ├── helpers.rs │ ├── integration_tests.rs │ ├── metrics.rs │ ├── proc_macros.rs │ └── rpc_module.rs └── wasm-tests │ ├── Cargo.toml │ └── tests │ └── wasm.rs └── types ├── Cargo.toml └── src ├── error.rs ├── lib.rs ├── params.rs ├── request.rs └── response.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 | [*.{yml,sh}] 13 | indent_style=space 14 | indent_size=2 15 | tab_width=8 16 | end_of_line=lf 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/benchmarks.yml: -------------------------------------------------------------------------------- 1 | name: Benchmarks 2 | 3 | on: 4 | schedule: 5 | - cron: "0 5 * * 6" # Every Saturday at 5:00 UTC 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | bench: 12 | name: Benchmarks 13 | runs-on: parity-benchmark 14 | container: 15 | image: "paritytech/ci-unified:bullseye-1.85.0-2025-01-28-v202504231537" 16 | steps: 17 | - name: Checkout Sources 18 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | 20 | - name: Rust Cache 21 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 22 | 23 | - name: run benchmarks 24 | run: cargo bench -p jsonrpsee-benchmarks -- --output-format bencher | tee output.txt 25 | 26 | - name: Store benchmark result 27 | uses: rhysd/github-action-benchmark@d48d326b4ca9ba73ca0cd0d59f108f9e02a381c7 # v1.20.4 28 | with: 29 | tool: "cargo" 30 | output-file-path: "output.txt" 31 | benchmark-data-dir-path: "bench/dev2" 32 | fail-on-alert: true 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | comment-on-alert: true 35 | auto-push: true 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | */target 3 | **/*.rs.bk 4 | Cargo.lock 5 | .DS_Store 6 | 7 | #Added by cargo 8 | # 9 | #already existing elements are commented out 10 | 11 | #/target 12 | #**/*.rs.bk 13 | -------------------------------------------------------------------------------- /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://docs.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 | # main codeowner @paritytech/subxt-team 20 | * @paritytech/subxt-team 21 | 22 | # CI 23 | /.github/ @paritytech/ci @paritytech/subxt-team 24 | /scripts/ci/ @paritytech/ci @paritytech/subxt-team 25 | /.gitlab-ci.yml @paritytech/ci @paritytech/subxt-team 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "benches", 4 | "client/http-client", 5 | "client/transport", 6 | "client/wasm-client", 7 | "client/ws-client", 8 | "core", 9 | "examples", 10 | "jsonrpsee", 11 | "proc-macros", 12 | "server", 13 | "test-utils", 14 | "tests", 15 | "tests/wasm-tests", 16 | "tests/proc-macro-core", 17 | "types", 18 | ] 19 | resolver = "3" 20 | 21 | [workspace.package] 22 | authors = ["Parity Technologies ", "Pierre Krieger "] 23 | version = "0.25.1" 24 | edition = "2024" 25 | rust-version = "1.85.0" 26 | license = "MIT" 27 | repository = "https://github.com/paritytech/jsonrpsee" 28 | documentation = "https://docs.rs/jsonrpsee" 29 | homepage = "https://www.parity.io/" 30 | keywords = ["jsonrpc", "json", "http", "websocket", "WASM"] 31 | readme = "README.md" 32 | 33 | [workspace.dependencies] 34 | # Internal jsonrpsee crates 35 | jsonrpsee-client-transport = { path = "client/transport", version = "0.25.1" } 36 | jsonrpsee-core = { path = "core", version = "0.25.1" } 37 | jsonrpsee-http-client = { path = "client/http-client", version = "0.25.1" } 38 | jsonrpsee-proc-macros = { path = "proc-macros", version = "0.25.1" } 39 | jsonrpsee-server = { path = "server", version = "0.25.1" } 40 | jsonrpsee-types = { path = "types", version = "0.25.1" } 41 | jsonrpsee-wasm-client = { path = "client/wasm-client", version = "0.25.1" } 42 | jsonrpsee-ws-client = { path = "client/ws-client", version = "0.25.1" } 43 | 44 | # Deps used by the jsonrpsee crates. 45 | async-trait = "0.1" 46 | base64 = { version = "0.22", default-features = false, features = ["alloc"] } 47 | bytes = "1.6" 48 | futures-channel = { version = "0.3.14", default-features = false } 49 | futures-timer = "3" 50 | futures-util = { version = "0.3.14", default-features = false } 51 | gloo-net = { version = "0.6.0", default-features = false } 52 | heck = "0.5.0" 53 | http = "1" 54 | http-body = "1" 55 | http-body-util = "0.1.0" 56 | hyper = "1.5" 57 | hyper-rustls = { version = "0.27", default-features = false } 58 | hyper-util = "0.1" 59 | parking_lot = "0.12" 60 | pin-project = "1.1.3" 61 | proc-macro-crate = "3" 62 | proc-macro2 = "1" 63 | quote = "1" 64 | rand = "0.9" 65 | route-recognizer = "0.3.1" 66 | rustc-hash = "2" 67 | rustls = { version = "0.23", default-features = false } 68 | rustls-pki-types = "1" 69 | rustls-platform-verifier = "0.5" 70 | serde = { version = "1", default-features = false, features = ["derive"] } 71 | serde_json = { version = "1", default-features = false, features = ["alloc", "raw_value"] } 72 | soketto = "0.8.1" 73 | syn = { version = "2", default-features = false } 74 | thiserror = "2" 75 | tokio = "1.42" 76 | tokio-rustls = { version = "0.26", default-features = false } 77 | tokio-stream = "0.1.7" 78 | tokio-util = "0.7" 79 | tower = "0.5" 80 | tower-http = "0.6" 81 | tracing = "0.1.34" 82 | url = "2.4" 83 | wasm-bindgen-futures = "0.4.19" 84 | 85 | # Dev dependencies 86 | anyhow = "1" 87 | console-subscriber = "0.4" 88 | criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } 89 | fast-socks5 = "0.10" 90 | futures = { version = "0.3.14", default-features = false, features = ["std"] } 91 | pprof = { version = "0.15", features = ["flamegraph", "criterion"] } 92 | socket2 = "0.5.1" 93 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 94 | trybuild = "1.0.97" 95 | 96 | [workspace.lints.rust] 97 | rust_2024_compatibility = { level = "warn", priority = -1 } 98 | missing_docs = { level = "warn", priority = -1 } 99 | missing_debug_implementations = { level = "warn", priority = -1 } 100 | missing_copy_implementations = { level = "warn", priority = -1 } 101 | 102 | [workspace.lints.clippy] 103 | manual_async_fn = { level = "allow", priority = -1 } 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 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 | # jsonrpsee 2 | 3 | [![GitLab Status](https://gitlab.parity.io/parity/mirrors/jsonrpsee/badges/master/pipeline.svg)](https://gitlab.parity.io/parity/mirrors/jsonrpsee/-/pipelines) 4 | [![crates.io](https://img.shields.io/crates/v/jsonrpsee)](https://crates.io/crates/jsonrpsee) 5 | [![Docs](https://img.shields.io/docsrs/jsonrpsee/latest)](https://docs.rs/jsonrpsee) 6 | ![MIT](https://img.shields.io/crates/l/jsonrpsee.svg) 7 | [![CI](https://github.com/paritytech/jsonrpsee/actions/workflows/ci.yml/badge.svg)](https://github.com/paritytech/jsonrpsee/actions/workflows/ci.yml) 8 | [![Benchmarks](https://github.com/paritytech/jsonrpsee/actions/workflows/benchmarks_gitlab.yml/badge.svg)](https://github.com/paritytech/jsonrpsee/actions/workflows/benchmarks_gitlab.yml) 9 | [![dependency status](https://deps.rs/crate/jsonrpsee/latest/status.svg)](https://deps.rs/crate/jsonrpsee) 10 | 11 | JSON-RPC library designed for async/await in Rust. 12 | 13 | Designed to be the successor to [ParityTech's JSONRPC crate](https://github.com/paritytech/jsonrpc/). 14 | 15 | ## Features 16 | - Client/server HTTP/HTTP2 support 17 | - Client/server WebSocket support 18 | - Client WASM support via web-sys 19 | - Client transport abstraction to provide custom transports 20 | - Middleware 21 | 22 | ## Documentation 23 | - [API Documentation](https://docs.rs/jsonrpsee) 24 | 25 | ## Examples 26 | 27 | - [HTTP](./examples/examples/http.rs) 28 | - [WebSocket](./examples/examples/ws.rs) 29 | - [WebSocket pubsub](./examples/examples/ws_pubsub_broadcast.rs) 30 | - [API generation with proc macro](./examples/examples/proc_macro.rs) 31 | - [CORS server](./examples/examples/cors_server.rs) 32 | - [Core client](./examples/examples/core_client.rs) 33 | - [HTTP proxy middleware](./examples/examples/http_proxy_middleware.rs) 34 | - [jsonrpsee as service](./examples/examples/jsonrpsee_as_service.rs) 35 | - [low level API](./examples/examples/jsonrpsee_server_low_level_api.rs) 36 | - [Websocket served over dual-stack (v4/v6) sockets](./examples/examples/ws_dual_stack.rs) 37 | 38 | See [this directory](./examples/examples) for more examples 39 | 40 | ## Roadmap 41 | 42 | See [our tracking milestone](https://github.com/paritytech/jsonrpsee/milestone/2) for the upcoming stable v1.0 release. 43 | 44 | ## Users 45 | 46 | If your project uses `jsonrpsee` we would like to know. Please open a pull request and add your project to the list below: 47 | - [parity bridges common](https://github.com/paritytech/parity-bridges-common) 48 | - [remote externalities](https://github.com/paritytech/substrate/tree/master/utils/frame/remote-externalities) 49 | - [polkadot-sdk](https://github.com/paritytech/polkadot-sdk) 50 | - [substrate-api-client](https://github.com/scs/substrate-api-client) 51 | - [subwasm](https://github.com/chevdor/subwasm) 52 | - [subway](https://github.com/AcalaNetwork/subway) 53 | - [subxt](https://github.com/paritytech/subxt) 54 | - [Trin](https://github.com/ethereum/trin) 55 | - [Uptest](https://github.com/uptest-sc/uptest) 56 | - [zkSync Era](https://github.com/matter-labs/zksync-era) 57 | - [Forest](https://github.com/ChainSafe/forest) 58 | 59 | ## Benchmarks 60 | 61 | Daily benchmarks for jsonrpsee can be found: 62 | - Gitlab machine: 63 | -------------------------------------------------------------------------------- /RELEASE-CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | These steps assume that you've checked out the Jsonrpsee repository and are in the root directory of it. 4 | 5 | We also assume that ongoing work done is being merged directly to the `master` branch. 6 | 7 | 1. Ensure that everything you'd like to see released is on the `master` branch. 8 | 9 | 2. Create a release branch off `master`, for example `chore-release-v0.15.0`. Decide how far the version needs to be bumped based 10 | on the changes to date. If unsure what to bump the version to (e.g. is it a major, minor or patch release), check with the 11 | Parity Tools team. 12 | 13 | 3. Bump the crate version (several locations) in `Cargo.toml` of the workspace to whatever was decided in step 2. 14 | 15 | 4. Update `CHANGELOG.md` to reflect the difference between this release and the last. If you're unsure of 16 | what to add, check with the Tools team. See the `CHANGELOG.md` file for details of the format it follows. 17 | 18 | First, if there have been any significant changes, add a description of those changes to the top of the 19 | changelog entry for this release. This will help people to understand the impact of the change and what they need to do 20 | to adopt it. 21 | 22 | Next, you can use the following script to generate the merged PRs between releases: 23 | 24 | ``` 25 | ./scripts/generate_changelog.sh 26 | ``` 27 | 28 | Ensure that the script picked the latest published release tag (e.g. if releasing `v0.15.0`, the script should 29 | provide something like `[+] Latest release tag: v0.14.0` ). Then group the PRs into "Fixed", "Added" and "Changed" sections, 30 | and make any other adjustments that you feel are necessary for clarity. 31 | 32 | 5. Commit any of the above changes to the release branch and open a PR in GitHub with a base of `master`. Name the branch something 33 | like `chore(release): v0.15.0`. 34 | 35 | 6. Once the branch has been reviewed and passes CI, merge it. 36 | 37 | 7. Now, we're ready to publish the release to crates.io. Run `./scripts/publish.sh` to publish all crates in the correct order. 38 | 39 | 8. If the release was successful, tag the commit that we released in the `master` branch with the 40 | version that we just released, for example: 41 | 42 | ``` 43 | git tag -s v0.15.0 # use the version number you've just published to crates.io, not this 44 | git push --tags 45 | ``` 46 | 47 | Once this is pushed, go along to [the releases page on GitHub](https://github.com/paritytech/jsonrpsee/releases) 48 | and draft a new release which points to the tag you just pushed to `master` above. Copy the changelog comments 49 | for the current release into the release description. 50 | 51 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-benchmarks" 3 | description = "Benchmarks for jsonrpsee" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | publish = false 9 | 10 | [dependencies] 11 | pprof = { workspace = true } 12 | criterion = { workspace = true } 13 | futures-util = { workspace = true } 14 | jsonrpsee = { path = "../jsonrpsee", features = ["server"] } 15 | jsonrpsee_v0_20 = { package = "jsonrpsee", version = "=0.20.0", features = ["ws-client", "client-ws-transport-native-tls"] } 16 | # Disable TLS for benches 17 | jsonrpsee_v0_20_http_client = { package = "jsonrpsee-http-client", version = "=0.20.0", default-features = false } 18 | jsonrpc-ws-server = { version = "18.0.0", optional = true } 19 | jsonrpc-http-server = { version = "18.0.0", optional = true } 20 | jsonrpc-pubsub = { version = "18.0.0", optional = true } 21 | serde_json = { workspace = true } 22 | tokio = { workspace = true, features = ["rt-multi-thread"] } 23 | console-subscriber = { workspace = true } 24 | 25 | [[bench]] 26 | name = "bench" 27 | path = "bench.rs" 28 | harness = false 29 | 30 | [features] 31 | # Run benchmarks against servers in https://github.com/paritytech/jsonrpc/ 32 | jsonrpc-crate = ["jsonrpc-ws-server", "jsonrpc-http-server", "jsonrpc-pubsub"] 33 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpsee benchmarks 2 | 3 | This crate contains benchmarks mainly to test the server implementations of some common scenarios such as concurrent connections. 4 | Further, running these will open lots of sockets and file descriptors. 5 | 6 | Note that on MacOS inparticular, you may need to increase some limits to be 7 | able to open a large number of connections. Try commands like: 8 | 9 | ```sh 10 | sudo sysctl -w kern.maxfiles=100000 11 | sudo sysctl -w kern.maxfilesperproc=100000 12 | ulimit -n 100000 13 | sudo sysctl -w kern.ipc.somaxconn=100000 14 | sudo sysctl -w kern.ipc.maxsockbuf=16777216 15 | ``` 16 | 17 | In general, if you run into issues, it may be better to run this on a linux 18 | box; MacOS seems to hit limits quicker in general. 19 | 20 | ## Run all benchmarks 21 | 22 | `$ cargo bench` 23 | 24 | It's also possible to run individual benchmarks by: 25 | 26 | `$ cargo bench --bench bench jsonrpsee_types_v2_array_ref` 27 | 28 | ## Run all benchmarks against [jsonrpc crate servers](https://github.com/paritytech/jsonrpc/) 29 | 30 | `$ cargo bench --features jsonpc-crate` 31 | 32 | ## Run CPU profiling on the benchmarks 33 | 34 | This will generate a flamegraph for the specific benchmark in `./target/criterion//profile/flamegraph.svg`. 35 | 36 | `$ cargo bench --bench bench -- --profile-time=60` 37 | 38 | It's also possible to run profiling on individual benchmarks by: 39 | 40 | `$ cargo bench --bench bench -- --profile-time=60 sync/http_concurrent_conn_calls/1024` 41 | 42 | ## Run tokio console on the benchmarks 43 | 44 | Install and run `tokio-console`. 45 | 46 | `$ cargo install --locked tokio-console && tokio-console` 47 | 48 | Run benchmarks with tokio-console support. 49 | 50 | `$ RUSTFLAGS="--cfg tokio_unstable" cargo bench` 51 | 52 | ## Measurement time of benchmarks 53 | 54 | Some of the benchmarks are quite expensive to run and doesn't run with enough samples with the default values 55 | provided by criterion. Currently the default values are very conversative which can be modified by the following environment variables: 56 | 57 | - "SLOW_MEASUREMENT_TIME" - sets the measurement time for slow benchmarks (default is 60 seconds) 58 | - "MEASUREMENT_TIME" - sets the measurement time for fast benchmarks (default is 10 seconds) 59 | -------------------------------------------------------------------------------- /client/http-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-http-client" 3 | description = "JSON-RPC HTTP client" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | base64 = { workspace = true } 21 | hyper = { workspace = true, features = ["client", "http1", "http2"] } 22 | hyper-rustls = { workspace = true, features = ["http1", "http2", "tls12", "logging", "ring"], optional = true } 23 | hyper-util = { workspace = true, features = ["client", "client-legacy", "tokio", "http1", "http2"] } 24 | http-body = { workspace = true } 25 | jsonrpsee-types = { workspace = true } 26 | jsonrpsee-core = { workspace = true, features = ["client", "http-helpers"] } 27 | rustls = { workspace = true, optional = true, features = ["logging", "std", "tls12", "ring"] } 28 | rustls-platform-verifier = { workspace = true, optional = true } 29 | serde = { workspace = true } 30 | serde_json = { workspace = true } 31 | thiserror = { workspace = true } 32 | tokio = { workspace = true, features = ["time"] } 33 | tower = { workspace = true, features = ["util"] } 34 | url = { workspace = true } 35 | 36 | [dev-dependencies] 37 | tracing-subscriber = { workspace = true } 38 | jsonrpsee-test-utils = { path = "../../test-utils" } 39 | tokio = { workspace = true, features = ["net", "rt-multi-thread", "macros"] } 40 | 41 | [features] 42 | default = ["tls"] 43 | tls = ["hyper-rustls", "rustls", "rustls-platform-verifier"] 44 | 45 | [package.metadata.docs.rs] 46 | all-features = true 47 | rustdoc-args = ["--cfg", "docsrs"] 48 | 49 | [package.metadata.playground] 50 | all-features = true 51 | -------------------------------------------------------------------------------- /client/http-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! # jsonrpsee-http-client 28 | //! 29 | //! `jsonrpsee-http-client` is [JSON RPC](https://www.jsonrpc.org/specification) HTTP client library that's is built for `async/await`. 30 | //! 31 | //! It is tightly-coupled to [`tokio`](https://docs.rs/tokio) because [`hyper`](https://docs.rs/hyper) is used as transport client, 32 | //! which is not compatible with other async runtimes such as 33 | //! [`async-std`](https://docs.rs/async-std/), [`smol`](https://docs.rs/smol) and similar. 34 | 35 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 36 | #![cfg_attr(docsrs, feature(doc_cfg))] 37 | 38 | mod client; 39 | mod rpc_service; 40 | 41 | /// HTTP transport. 42 | pub mod transport; 43 | 44 | #[cfg(test)] 45 | mod tests; 46 | 47 | pub use client::{HttpClient, HttpClientBuilder}; 48 | pub use hyper::http::{HeaderMap, HeaderValue}; 49 | pub use jsonrpsee_types as types; 50 | 51 | /// This is the default implementation of the [`jsonrpsee_core::middleware::RpcServiceT`] trait used in the [`HttpClient`]. 52 | pub use rpc_service::RpcService; 53 | /// Default HTTP body for the client. 54 | pub type HttpBody = jsonrpsee_core::http_helpers::Body; 55 | /// HTTP request with default body. 56 | pub type HttpRequest = jsonrpsee_core::http_helpers::Request; 57 | /// HTTP response with default body. 58 | pub type HttpResponse = jsonrpsee_core::http_helpers::Response; 59 | 60 | /// Custom TLS configuration. 61 | #[cfg(feature = "tls")] 62 | pub type CustomCertStore = rustls::ClientConfig; 63 | 64 | #[cfg(feature = "tls")] 65 | // rustls needs the concrete `ClientConfig` type so we can't Box it here. 66 | #[allow(clippy::large_enum_variant)] 67 | #[derive(Clone, Debug)] 68 | pub(crate) enum CertificateStore { 69 | Native, 70 | Custom(CustomCertStore), 71 | } 72 | -------------------------------------------------------------------------------- /client/http-client/src/rpc_service.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use hyper::body::Bytes; 4 | use jsonrpsee_core::{ 5 | BoxError, JsonRawValue, 6 | client::{Error, MiddlewareBatchResponse, MiddlewareMethodResponse, MiddlewareNotifResponse}, 7 | middleware::{Batch, Notification, Request, RpcServiceT}, 8 | }; 9 | use jsonrpsee_types::Response; 10 | use tower::Service; 11 | 12 | use crate::{ 13 | HttpRequest, HttpResponse, 14 | transport::{Error as TransportError, HttpTransportClient}, 15 | }; 16 | 17 | #[derive(Clone, Debug)] 18 | pub struct RpcService { 19 | service: Arc>, 20 | } 21 | 22 | impl RpcService { 23 | pub fn new(service: HttpTransportClient) -> Self { 24 | Self { service: Arc::new(service) } 25 | } 26 | } 27 | 28 | impl RpcServiceT for RpcService 29 | where 30 | HttpMiddleware: 31 | Service, Error = TransportError> + Clone + Send + Sync + 'static, 32 | HttpMiddleware::Future: Send, 33 | B: http_body::Body + Send + 'static, 34 | B::Data: Send, 35 | B::Error: Into, 36 | { 37 | type BatchResponse = Result; 38 | type MethodResponse = Result; 39 | type NotificationResponse = Result; 40 | 41 | fn call<'a>(&self, request: Request<'a>) -> impl Future + Send + 'a { 42 | let service = self.service.clone(); 43 | 44 | async move { 45 | let raw = serde_json::to_string(&request)?; 46 | let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; 47 | let mut rp: Response> = serde_json::from_slice(&bytes)?; 48 | rp.extensions = request.extensions; 49 | 50 | Ok(MiddlewareMethodResponse::response(rp.into_owned().into())) 51 | } 52 | } 53 | 54 | fn batch<'a>(&self, batch: Batch<'a>) -> impl Future + Send + 'a { 55 | let service = self.service.clone(); 56 | 57 | async move { 58 | let raw = serde_json::to_string(&batch)?; 59 | let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; 60 | let rp: Vec<_> = serde_json::from_slice::>>>(&bytes)? 61 | .into_iter() 62 | .map(|r| r.into_owned().into()) 63 | .collect(); 64 | 65 | Ok(rp) 66 | } 67 | } 68 | 69 | fn notification<'a>( 70 | &self, 71 | notif: Notification<'a>, 72 | ) -> impl Future + Send + 'a { 73 | let service = self.service.clone(); 74 | 75 | async move { 76 | let raw = serde_json::to_string(¬if)?; 77 | service.send(raw).await.map_err(|e| Error::Transport(e.into()))?; 78 | Ok(notif.extensions.into()) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client/transport/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-client-transport" 3 | description = "JSON-RPC client transports" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | jsonrpsee-core = { workspace = true, features = ["client"] } 21 | 22 | # optional 23 | thiserror = { workspace = true, optional = true } 24 | futures-util = { workspace = true, features = ["alloc"], optional = true } 25 | http = { workspace = true, optional = true } 26 | tracing = { workspace = true, optional = true } 27 | tokio-util = { workspace = true, features = ["compat"], optional = true } 28 | tokio = { workspace = true, features = ["net", "time", "macros"], optional = true } 29 | pin-project = { workspace = true, optional = true } 30 | url = { workspace = true, optional = true } 31 | base64 = { workspace = true, optional = true } 32 | 33 | # tls 34 | tokio-rustls = { workspace = true, optional = true, features = ["logging", "tls12", "ring"] } 35 | rustls-pki-types = { workspace = true, optional = true } 36 | rustls-platform-verifier = { workspace = true, optional = true } 37 | rustls = { workspace = true, default-features = false, optional = true } 38 | 39 | # ws 40 | soketto = { workspace = true, optional = true } 41 | 42 | # web-sys 43 | [target.'cfg(target_arch = "wasm32")'.dependencies] 44 | gloo-net = { workspace = true, features = ["json", "websocket"], optional = true } 45 | futures-channel = { workspace = true, optional = true } 46 | 47 | [features] 48 | tls = ["rustls", "tokio-rustls", "rustls-pki-types"] 49 | tls-rustls-platform-verifier = ["tls", "rustls-platform-verifier"] 50 | 51 | ws = [ 52 | "base64", 53 | "futures-util", 54 | "http", 55 | "tokio", 56 | "tokio-util", 57 | "soketto", 58 | "pin-project", 59 | "thiserror", 60 | "tracing", 61 | "url", 62 | ] 63 | web = [ 64 | "gloo-net", 65 | "futures-channel", 66 | "futures-util", 67 | "thiserror", 68 | ] 69 | 70 | [package.metadata.docs.rs] 71 | all-features = true 72 | rustdoc-args = ["--cfg", "docsrs"] 73 | 74 | [package.metadata.playground] 75 | all-features = true 76 | -------------------------------------------------------------------------------- /client/transport/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 28 | #![cfg_attr(docsrs, feature(doc_cfg))] 29 | 30 | //! # jsonrpsee-client-transports 31 | 32 | /// Websocket transport 33 | #[cfg(feature = "ws")] 34 | #[cfg_attr(docsrs, doc(cfg(feature = "ws")))] 35 | pub mod ws; 36 | 37 | /// Websocket transport via web-sys. 38 | #[cfg(all(feature = "web", target_arch = "wasm32"))] 39 | #[cfg_attr(docsrs, doc(cfg(feature = "web")))] 40 | pub mod web; 41 | -------------------------------------------------------------------------------- /client/transport/src/web.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use futures_channel::mpsc; 4 | use futures_util::sink::SinkExt; 5 | use futures_util::stream::{SplitSink, SplitStream, StreamExt}; 6 | use gloo_net::websocket::{Message, WebSocketError, futures::WebSocket}; 7 | use jsonrpsee_core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; 8 | 9 | /// Web-sys transport error that can occur. 10 | #[derive(Debug, thiserror::Error)] 11 | pub enum Error { 12 | /// Internal send error 13 | #[error("Could not send message: {0}")] 14 | SendError(#[from] mpsc::SendError), 15 | /// Sender went away 16 | #[error("Sender went away couldn't receive the message")] 17 | SenderDisconnected, 18 | /// Error that occurred in `JS context`. 19 | #[error("JS Error: {0:?}")] 20 | Js(String), 21 | /// WebSocket error 22 | #[error(transparent)] 23 | WebSocket(WebSocketError), 24 | /// Operation not supported 25 | #[error("Operation not supported")] 26 | NotSupported, 27 | } 28 | 29 | /// Sender. 30 | pub struct Sender(SplitSink); 31 | 32 | impl fmt::Debug for Sender { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | f.debug_struct("Sender").finish() 35 | } 36 | } 37 | 38 | /// Receiver. 39 | pub struct Receiver(SplitStream); 40 | 41 | impl fmt::Debug for Receiver { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | f.debug_struct("Receiver").finish() 44 | } 45 | } 46 | 47 | impl TransportSenderT for Sender { 48 | type Error = Error; 49 | 50 | fn send(&mut self, msg: String) -> impl Future> { 51 | async { 52 | self.0.send(Message::Text(msg)).await.map_err(|e| Error::WebSocket(e))?; 53 | Ok(()) 54 | } 55 | } 56 | } 57 | 58 | impl TransportReceiverT for Receiver { 59 | type Error = Error; 60 | 61 | fn receive(&mut self) -> impl Future> { 62 | async { 63 | match self.0.next().await { 64 | Some(Ok(msg)) => match msg { 65 | Message::Bytes(bytes) => Ok(ReceivedMessage::Bytes(bytes)), 66 | Message::Text(txt) => Ok(ReceivedMessage::Text(txt)), 67 | }, 68 | Some(Err(err)) => Err(Error::WebSocket(err)), 69 | None => Err(Error::SenderDisconnected), 70 | } 71 | } 72 | } 73 | } 74 | 75 | /// Create a transport sender & receiver pair. 76 | pub async fn connect(url: impl AsRef) -> Result<(Sender, Receiver), Error> { 77 | let websocket = WebSocket::open(url.as_ref()).map_err(|e| Error::Js(e.to_string()))?; 78 | let (write, read) = websocket.split(); 79 | 80 | Ok((Sender(write), Receiver(read))) 81 | } 82 | -------------------------------------------------------------------------------- /client/transport/src/ws/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Convenience wrapper for a stream (AsyncRead + AsyncWrite) which can either be plain TCP or TLS. 28 | 29 | use std::io::Error as IoError; 30 | use std::pin::Pin; 31 | use std::task::Context; 32 | use std::task::Poll; 33 | 34 | use pin_project::pin_project; 35 | use tokio::io::{AsyncRead, AsyncWrite}; 36 | use tokio::net::TcpStream; 37 | 38 | /// Stream to represent either a unencrypted or encrypted socket stream. 39 | #[pin_project(project = EitherStreamProj)] 40 | #[derive(Debug)] 41 | #[allow(clippy::large_enum_variant)] 42 | pub enum EitherStream { 43 | /// Unencrypted socket stream. 44 | Plain(#[pin] TcpStream), 45 | /// Encrypted socket stream. 46 | #[cfg(feature = "tls")] 47 | Tls(#[pin] tokio_rustls::client::TlsStream), 48 | } 49 | 50 | impl AsyncRead for EitherStream { 51 | fn poll_read( 52 | self: Pin<&mut Self>, 53 | cx: &mut Context, 54 | buf: &mut tokio::io::ReadBuf<'_>, 55 | ) -> Poll> { 56 | match self.project() { 57 | EitherStreamProj::Plain(stream) => AsyncRead::poll_read(stream, cx, buf), 58 | #[cfg(feature = "tls")] 59 | EitherStreamProj::Tls(stream) => AsyncRead::poll_read(stream, cx, buf), 60 | } 61 | } 62 | } 63 | 64 | impl AsyncWrite for EitherStream { 65 | fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { 66 | match self.project() { 67 | EitherStreamProj::Plain(stream) => AsyncWrite::poll_write(stream, cx, buf), 68 | #[cfg(feature = "tls")] 69 | EitherStreamProj::Tls(stream) => AsyncWrite::poll_write(stream, cx, buf), 70 | } 71 | } 72 | 73 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 74 | match self.project() { 75 | EitherStreamProj::Plain(stream) => AsyncWrite::poll_flush(stream, cx), 76 | #[cfg(feature = "tls")] 77 | EitherStreamProj::Tls(stream) => AsyncWrite::poll_flush(stream, cx), 78 | } 79 | } 80 | 81 | fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | match self.project() { 83 | EitherStreamProj::Plain(stream) => AsyncWrite::poll_shutdown(stream, cx), 84 | #[cfg(feature = "tls")] 85 | EitherStreamProj::Tls(stream) => AsyncWrite::poll_shutdown(stream, cx), 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /client/wasm-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-wasm-client" 3 | description = "JSON-RPC WASM client" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [target.'cfg(target_arch = "wasm32")'.dependencies] 20 | jsonrpsee-types = { workspace = true } 21 | jsonrpsee-client-transport = { workspace = true, features = ["web"] } 22 | jsonrpsee-core = { workspace = true, features = ["async-wasm-client"] } 23 | tower = { workspace = true } 24 | 25 | [package.metadata.docs.rs] 26 | all-features = true 27 | rustdoc-args = ["--cfg", "docsrs"] 28 | targets = ["wasm32-unknown-unknown"] 29 | 30 | [package.metadata.playground] 31 | all-features = true 32 | -------------------------------------------------------------------------------- /client/wasm-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! # jsonrpsee-wasm-client 28 | 29 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 30 | #![cfg_attr(docsrs, feature(doc_cfg))] 31 | #![cfg(target_arch = "wasm32")] 32 | 33 | pub use jsonrpsee_core::client::Client; 34 | pub use jsonrpsee_types as types; 35 | 36 | use std::time::Duration; 37 | 38 | use jsonrpsee_client_transport::web; 39 | use jsonrpsee_core::client::async_client::RpcService; 40 | use jsonrpsee_core::client::{Error, IdKind}; 41 | use jsonrpsee_core::middleware::{RpcServiceBuilder, layer::RpcLoggerLayer}; 42 | 43 | type Logger = tower::layer::util::Stack; 44 | 45 | /// Builder for [`Client`]. 46 | /// 47 | /// # Examples 48 | /// 49 | /// ```no_run 50 | /// 51 | /// use jsonrpsee_wasm_client::WasmClientBuilder; 52 | /// 53 | /// #[tokio::main] 54 | /// async fn main() { 55 | /// // build client 56 | /// let client = WasmClientBuilder::default() 57 | /// .build("wss://localhost:443") 58 | /// .await 59 | /// .unwrap(); 60 | /// 61 | /// // use client.... 62 | /// } 63 | /// 64 | /// ``` 65 | #[derive(Clone, Debug)] 66 | pub struct WasmClientBuilder { 67 | id_kind: IdKind, 68 | max_concurrent_requests: usize, 69 | max_buffer_capacity_per_subscription: usize, 70 | request_timeout: Duration, 71 | service_builder: RpcServiceBuilder, 72 | } 73 | 74 | impl Default for WasmClientBuilder { 75 | fn default() -> Self { 76 | Self { 77 | id_kind: IdKind::Number, 78 | max_concurrent_requests: 256, 79 | max_buffer_capacity_per_subscription: 1024, 80 | request_timeout: Duration::from_secs(60), 81 | service_builder: RpcServiceBuilder::default().rpc_logger(1024), 82 | } 83 | } 84 | } 85 | 86 | impl WasmClientBuilder { 87 | /// Create a new WASM client builder. 88 | pub fn new() -> WasmClientBuilder { 89 | WasmClientBuilder::default() 90 | } 91 | } 92 | 93 | impl WasmClientBuilder { 94 | /// See documentation [`ClientBuilder::request_timeout`] (default is 60 seconds). 95 | pub fn request_timeout(mut self, timeout: Duration) -> Self { 96 | self.request_timeout = timeout; 97 | self 98 | } 99 | 100 | /// See documentation [`ClientBuilder::max_concurrent_requests`] (default is 256). 101 | pub fn max_concurrent_requests(mut self, max: usize) -> Self { 102 | self.max_concurrent_requests = max; 103 | self 104 | } 105 | 106 | /// See documentation [`ClientBuilder::max_buffer_capacity_per_subscription`] (default is 1024). 107 | pub fn max_buffer_capacity_per_subscription(mut self, max: usize) -> Self { 108 | self.max_buffer_capacity_per_subscription = max; 109 | self 110 | } 111 | 112 | /// See documentation for [`ClientBuilder::id_format`] (default is Number). 113 | pub fn id_format(mut self, kind: IdKind) -> Self { 114 | self.id_kind = kind; 115 | self 116 | } 117 | 118 | /// See documentation for [`ClientBuilder::set_rpc_middleware`]. 119 | pub fn set_rpc_middleware(self, middleware: RpcServiceBuilder) -> WasmClientBuilder { 120 | WasmClientBuilder { 121 | id_kind: self.id_kind, 122 | max_concurrent_requests: self.max_concurrent_requests, 123 | max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, 124 | request_timeout: self.request_timeout, 125 | service_builder: middleware, 126 | } 127 | } 128 | 129 | /// Build the client with specified URL to connect to. 130 | pub async fn build(self, url: impl AsRef) -> Result, Error> 131 | where 132 | L: tower::Layer + Clone + Send + Sync + 'static, 133 | { 134 | let Self { 135 | id_kind, 136 | request_timeout, 137 | max_concurrent_requests, 138 | max_buffer_capacity_per_subscription, 139 | service_builder, 140 | } = self; 141 | let (sender, receiver) = web::connect(url).await.map_err(|e| Error::Transport(e.into()))?; 142 | 143 | let client = Client::builder() 144 | .request_timeout(request_timeout) 145 | .id_format(id_kind) 146 | .max_buffer_capacity_per_subscription(max_buffer_capacity_per_subscription) 147 | .max_concurrent_requests(max_concurrent_requests) 148 | .set_rpc_middleware(service_builder) 149 | .build_with_wasm(sender, receiver); 150 | 151 | Ok(client) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /client/ws-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-ws-client" 3 | description = "JSON-RPC websocket client" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | http = { workspace = true } 21 | jsonrpsee-types = { workspace = true } 22 | jsonrpsee-client-transport = { workspace = true, features = ["ws"] } 23 | jsonrpsee-core = { workspace = true, features = ["async-client"] } 24 | url = { workspace = true } 25 | tower = { workspace = true } 26 | 27 | [dev-dependencies] 28 | tracing-subscriber = { workspace = true } 29 | jsonrpsee-test-utils = { path = "../../test-utils" } 30 | tokio = { workspace = true, features = ["macros"] } 31 | serde_json = { workspace = true } 32 | serde = { workspace = true } 33 | rustls = { workspace = true, features = ["logging", "std", "tls12", "ring"] } 34 | 35 | [features] 36 | tls = ["jsonrpsee-client-transport/tls"] 37 | tls-rustls-platform-verifier = ["jsonrpsee-client-transport/tls-rustls-platform-verifier", "tls"] 38 | default = ["tls-rustls-platform-verifier"] 39 | 40 | [package.metadata.docs.rs] 41 | all-features = true 42 | rustdoc-args = ["--cfg", "docsrs"] 43 | 44 | [package.metadata.playground] 45 | all-features = true 46 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-core" 3 | description = "Utilities for jsonrpsee" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | async-trait = { workspace = true } 21 | jsonrpsee-types = { workspace = true } 22 | thiserror = { workspace = true } 23 | serde = { workspace = true } 24 | serde_json = { workspace = true, features = ["std", "raw_value"] } 25 | tracing = { workspace = true } 26 | 27 | # optional deps 28 | futures-util = { workspace = true, optional = true, features = ["alloc"] } 29 | http = { workspace = true, optional = true } 30 | bytes = { workspace = true, optional = true } 31 | http-body = { workspace = true, optional = true } 32 | http-body-util = { workspace = true, optional = true } 33 | rustc-hash = { workspace = true, optional = true } 34 | rand = { workspace = true, optional = true } 35 | parking_lot = { workspace = true, optional = true } 36 | tokio = { workspace = true, optional = true } 37 | tower = { workspace = true, optional = true } 38 | futures-timer = { workspace = true, optional = true } 39 | tokio-stream = { workspace = true, optional = true } 40 | pin-project = { workspace = true, optional = true } 41 | 42 | [target.'cfg(target_arch = "wasm32")'.dependencies] 43 | wasm-bindgen-futures = { workspace = true, optional = true } 44 | 45 | [features] 46 | default = [] 47 | http-helpers = ["bytes", "futures-util", "http-body", "http-body-util", "http"] 48 | server = ["futures-util", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time", "tower", "http", "pin-project"] 49 | client = ["futures-util/sink", "tokio/sync", "tower", "pin-project", "http"] 50 | async-client = [ 51 | "client", 52 | "futures-util", 53 | "rustc-hash", 54 | "tokio/macros", 55 | "tokio/rt", 56 | "tokio/time", 57 | "futures-timer", 58 | "tokio-stream", 59 | "pin-project", 60 | ] 61 | async-wasm-client = [ 62 | "client", 63 | "futures-util", 64 | "wasm-bindgen-futures", 65 | "rustc-hash/std", 66 | "futures-timer/wasm-bindgen", 67 | "tokio/macros", 68 | "tokio/time", 69 | "pin-project", 70 | ] 71 | 72 | [dev-dependencies] 73 | serde_json = { workspace = true } 74 | tokio = { workspace = true, features = ["macros", "rt"] } 75 | jsonrpsee = { path = "../jsonrpsee", features = ["server", "macros"] } 76 | http-body-util = { workspace = true } 77 | 78 | [package.metadata.docs.rs] 79 | all-features = true 80 | rustdoc-args = ["--cfg", "docsrs"] 81 | 82 | [package.metadata.playground] 83 | all-features = true 84 | -------------------------------------------------------------------------------- /core/src/client/async_client/rpc_service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | client::{ 3 | BatchMessage, Error, FrontToBack, MiddlewareBatchResponse, MiddlewareMethodResponse, MiddlewareNotifResponse, 4 | RequestMessage, SubscriptionMessage, SubscriptionResponse, 5 | }, 6 | middleware::{Batch, IsBatch, IsSubscription, Notification, Request, RpcServiceT}, 7 | }; 8 | 9 | use jsonrpsee_types::{Response, ResponsePayload}; 10 | use tokio::sync::{mpsc, oneshot}; 11 | 12 | impl From> for Error { 13 | fn from(_: mpsc::error::SendError) -> Self { 14 | Error::ServiceDisconnect 15 | } 16 | } 17 | 18 | impl From for Error { 19 | fn from(_: oneshot::error::RecvError) -> Self { 20 | Error::ServiceDisconnect 21 | } 22 | } 23 | 24 | /// RpcService implementation for the async client. 25 | #[derive(Debug, Clone)] 26 | pub struct RpcService(mpsc::Sender); 27 | 28 | impl RpcService { 29 | // This is a private interface but we need to expose it for the async client 30 | // to be able to create the service. 31 | #[allow(private_interfaces)] 32 | pub(crate) fn new(tx: mpsc::Sender) -> Self { 33 | Self(tx) 34 | } 35 | } 36 | 37 | impl RpcServiceT for RpcService { 38 | type MethodResponse = Result; 39 | type BatchResponse = Result; 40 | type NotificationResponse = Result; 41 | 42 | fn call<'a>(&self, request: Request<'a>) -> impl Future + Send + 'a { 43 | let tx = self.0.clone(); 44 | 45 | async move { 46 | let raw = serde_json::to_string(&request)?; 47 | 48 | match request.extensions.get::() { 49 | Some(sub) => { 50 | let (send_back_tx, send_back_rx) = tokio::sync::oneshot::channel(); 51 | 52 | tx.clone() 53 | .send(FrontToBack::Subscribe(SubscriptionMessage { 54 | raw, 55 | subscribe_id: sub.sub_req_id(), 56 | unsubscribe_id: sub.unsub_req_id(), 57 | unsubscribe_method: sub.unsubscribe_method().to_owned(), 58 | send_back: send_back_tx, 59 | })) 60 | .await?; 61 | 62 | let (subscribe_rx, sub_id) = send_back_rx.await??; 63 | 64 | let rp = serde_json::value::to_raw_value(&sub_id)?; 65 | 66 | Ok(MiddlewareMethodResponse::subscription_response( 67 | Response::new(ResponsePayload::success(rp), request.id.clone().into_owned()).into(), 68 | SubscriptionResponse { sub_id, stream: subscribe_rx }, 69 | )) 70 | } 71 | None => { 72 | let (send_back_tx, send_back_rx) = oneshot::channel(); 73 | 74 | tx.send(FrontToBack::Request(RequestMessage { 75 | raw, 76 | send_back: Some(send_back_tx), 77 | id: request.id.clone().into_owned(), 78 | })) 79 | .await?; 80 | let mut rp = send_back_rx.await??; 81 | 82 | rp.0.extensions = request.extensions; 83 | 84 | Ok(MiddlewareMethodResponse::response(rp)) 85 | } 86 | } 87 | } 88 | } 89 | 90 | fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future + Send + 'a { 91 | let tx = self.0.clone(); 92 | 93 | async move { 94 | let (send_back_tx, send_back_rx) = oneshot::channel(); 95 | 96 | let raw = serde_json::to_string(&batch)?; 97 | let id_range = batch 98 | .extensions() 99 | .get::() 100 | .map(|b| b.id_range.clone()) 101 | .expect("Batch ID range must be set in extensions"); 102 | 103 | tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; 104 | let json = send_back_rx.await??; 105 | 106 | Ok(json) 107 | } 108 | } 109 | 110 | fn notification<'a>(&self, n: Notification<'a>) -> impl Future + Send + 'a { 111 | let tx = self.0.clone(); 112 | 113 | async move { 114 | let raw = serde_json::to_string(&n)?; 115 | tx.send(FrontToBack::Notification(raw)).await?; 116 | Ok(MiddlewareNotifResponse::from(n.extensions)) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /core/src/client/async_client/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::pin::Pin; 28 | use std::task::{Context, Poll, Waker}; 29 | use std::time::Duration; 30 | 31 | use futures_util::stream::FuturesUnordered; 32 | use futures_util::{Stream, StreamExt, Future}; 33 | use pin_project::pin_project; 34 | 35 | #[pin_project] 36 | pub(crate) struct IntervalStream(#[pin] Option); 37 | 38 | impl IntervalStream { 39 | /// Creates a stream which never returns any elements. 40 | pub(crate) fn pending() -> Self { 41 | Self(None) 42 | } 43 | 44 | /// Creates a stream which produces elements with interval of `period`. 45 | #[cfg(feature = "async-client")] 46 | pub(crate) fn new(s: S) -> Self { 47 | Self(Some(s)) 48 | } 49 | } 50 | 51 | impl Stream for IntervalStream { 52 | type Item = (); 53 | 54 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 55 | if let Some(mut stream) = self.project().0.as_pin_mut() { 56 | match stream.poll_next_unpin(cx) { 57 | Poll::Pending => Poll::Pending, 58 | Poll::Ready(Some(_)) => Poll::Ready(Some(())), 59 | Poll::Ready(None) => Poll::Ready(None), 60 | } 61 | } else { 62 | // NOTE: this will not be woken up again and it's by design 63 | // to be a pending stream that never returns. 64 | Poll::Pending 65 | } 66 | } 67 | } 68 | 69 | #[allow(unused)] 70 | pub(crate) enum InactivityCheck { 71 | Disabled, 72 | Enabled { inactive_dur: Duration, last_active: std::time::Instant, count: usize, max_count: usize } 73 | } 74 | 75 | impl InactivityCheck { 76 | #[cfg(feature = "async-client")] 77 | pub(crate) fn new(_inactive_dur: Duration, _max_count: usize) -> Self { 78 | Self::Enabled { inactive_dur: _inactive_dur, last_active: std::time::Instant::now(), count: 0, max_count: _max_count } 79 | } 80 | 81 | pub(crate) fn is_inactive(&mut self) -> bool { 82 | match self { 83 | Self::Disabled => false, 84 | Self::Enabled { inactive_dur, last_active, count, max_count, .. } => { 85 | if last_active.elapsed() >= *inactive_dur { 86 | *count += 1; 87 | } 88 | 89 | count >= max_count 90 | } 91 | } 92 | } 93 | 94 | pub(crate) fn mark_as_active(&mut self) { 95 | if let Self::Enabled { last_active, .. } = self { 96 | *last_active = std::time::Instant::now(); 97 | } 98 | } 99 | } 100 | 101 | 102 | 103 | /// A wrapper around `FuturesUnordered` that doesn't return `None` when it's empty. 104 | pub(crate) struct MaybePendingFutures { 105 | futs: FuturesUnordered, 106 | waker: Option, 107 | } 108 | 109 | impl MaybePendingFutures { 110 | pub(crate) fn new() -> Self { 111 | Self { futs: FuturesUnordered::new(), waker: None } 112 | } 113 | 114 | pub(crate) fn push(&mut self, fut: Fut) { 115 | self.futs.push(fut); 116 | 117 | if let Some(w) = self.waker.take() { 118 | w.wake(); 119 | } 120 | } 121 | } 122 | 123 | impl Stream for MaybePendingFutures { 124 | type Item = Fut::Output; 125 | 126 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 127 | if self.futs.is_empty() { 128 | self.waker = Some(cx.waker().clone()); 129 | return Poll::Pending; 130 | } 131 | 132 | self.futs.poll_next_unpin(cx) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /core/src/client/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Error type for client(s). 28 | 29 | use crate::{BoxError, RegisterMethodError, params::EmptyBatchRequest}; 30 | use jsonrpsee_types::{ErrorObjectOwned, InvalidRequestId}; 31 | use std::sync::Arc; 32 | 33 | /// Error type. 34 | #[derive(Debug, thiserror::Error)] 35 | pub enum Error { 36 | /// JSON-RPC error which can occur when a JSON-RPC call fails. 37 | #[error("{0}")] 38 | Call(#[from] ErrorObjectOwned), 39 | /// Networking error or error on the low-level protocol layer. 40 | #[error(transparent)] 41 | Transport(BoxError), 42 | /// The background task has been terminated. 43 | #[error("The background task closed {0}; restart required")] 44 | RestartNeeded(Arc), 45 | /// Failed to parse the data. 46 | #[error("Parse error: {0}")] 47 | ParseError(#[from] serde_json::Error), 48 | /// Invalid subscription ID. 49 | #[error("Invalid subscription ID")] 50 | InvalidSubscriptionId, 51 | /// Invalid request ID. 52 | #[error(transparent)] 53 | InvalidRequestId(#[from] InvalidRequestId), 54 | /// Request timeout 55 | #[error("Request timeout")] 56 | RequestTimeout, 57 | /// Custom error. 58 | #[error("Custom error: {0}")] 59 | Custom(String), 60 | /// Not implemented for HTTP clients. 61 | #[error("Not implemented")] 62 | HttpNotImplemented, 63 | /// Empty batch request. 64 | #[error(transparent)] 65 | EmptyBatchRequest(#[from] EmptyBatchRequest), 66 | /// The error returned when registering a method or subscription failed. 67 | #[error(transparent)] 68 | RegisterMethod(#[from] RegisterMethodError), 69 | /// An internal state when the underlying RpcService 70 | /// got disconnected and the error must be fetched 71 | /// from the backend. 72 | // 73 | // NOTE: This is a workaround where an error occurred in 74 | // underlying RpcService implementation in the async client 75 | // but we don't want to expose different error types for 76 | // ergonomics when writing middleware. 77 | #[error("RPC service disconnected")] 78 | #[doc(hidden)] 79 | ServiceDisconnect, 80 | } 81 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use serde::Serialize; 28 | use serde_json::value::RawValue; 29 | 30 | #[derive(Debug, Clone)] 31 | pub(crate) enum InnerSubscriptionErr { 32 | String(String), 33 | Json(Box), 34 | } 35 | 36 | /// Error returned when a subscription fails where the error is returned 37 | /// as special error notification with the following format: 38 | /// 39 | /// ```json 40 | /// {"jsonrpc":"2.0", "method":"subscription_error", "params": {"subscription": "sub_id", "error": }} 41 | /// ``` 42 | /// 43 | /// It's recommended to use [`SubscriptionError::from_json`] to create a new instance of this error 44 | /// if the underlying error is a JSON value. That will ensure that the error is serialized correctly. 45 | /// 46 | /// SubscriptionError::from will serialize the error as a string, which is not 47 | /// recommended and should only by used in the value of a `String` type. 48 | /// It's mainly provided for convenience and to allow for easy conversion any type that implements StdError. 49 | #[derive(Debug, Clone)] 50 | pub struct SubscriptionError(pub(crate) InnerSubscriptionErr); 51 | 52 | impl Serialize for SubscriptionError { 53 | fn serialize(&self, serializer: S) -> Result 54 | where 55 | S: serde::Serializer, 56 | { 57 | match &self.0 { 58 | InnerSubscriptionErr::String(s) => serializer.serialize_str(s), 59 | InnerSubscriptionErr::Json(json) => json.serialize(serializer), 60 | } 61 | } 62 | } 63 | 64 | impl From for SubscriptionError { 65 | fn from(val: T) -> Self { 66 | Self(InnerSubscriptionErr::String(val.to_string())) 67 | } 68 | } 69 | 70 | impl SubscriptionError { 71 | /// Create a new `SubscriptionError` from a JSON value. 72 | pub fn from_json(json: Box) -> Self { 73 | Self(InnerSubscriptionErr::Json(json)) 74 | } 75 | } 76 | 77 | /// The error returned when registering a method or subscription failed. 78 | #[derive(Debug, Clone, thiserror::Error)] 79 | pub enum RegisterMethodError { 80 | /// Method was already registered. 81 | #[error("Method: {0} was already registered")] 82 | AlreadyRegistered(String), 83 | /// Subscribe and unsubscribe method names are the same. 84 | #[error("Cannot use the same method name for subscribe and unsubscribe, used: {0}")] 85 | SubscriptionNameConflict(String), 86 | /// Method with that name has not yet been registered. 87 | #[error("Method: {0} has not yet been registered")] 88 | MethodNotFound(String), 89 | } 90 | -------------------------------------------------------------------------------- /core/src/id_providers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Subscription ID providers. 28 | 29 | use rand::{distr::Alphanumeric, Rng}; 30 | 31 | use crate::traits::IdProvider; 32 | use jsonrpsee_types::SubscriptionId; 33 | 34 | /// Generates random integers as subscription ID. 35 | #[derive(Debug, Copy, Clone)] 36 | pub struct RandomIntegerIdProvider; 37 | 38 | impl IdProvider for RandomIntegerIdProvider { 39 | fn next_id(&self) -> SubscriptionId<'static> { 40 | const JS_NUM_MASK: u64 = !0 >> 11; 41 | (rand::random::() & JS_NUM_MASK).into() 42 | } 43 | } 44 | 45 | /// Generates random strings of length `len` as subscription ID. 46 | #[derive(Debug, Copy, Clone)] 47 | pub struct RandomStringIdProvider { 48 | len: usize, 49 | } 50 | 51 | impl RandomStringIdProvider { 52 | /// Create a new random string provider. 53 | pub fn new(len: usize) -> Self { 54 | Self { len } 55 | } 56 | } 57 | 58 | impl IdProvider for RandomStringIdProvider { 59 | fn next_id(&self) -> SubscriptionId<'static> { 60 | let mut rng = rand::rng(); 61 | (&mut rng).sample_iter(Alphanumeric).take(self.len).map(char::from).collect::().into() 62 | } 63 | } 64 | 65 | /// No-op implementation to be used for servers that don't support subscriptions. 66 | #[derive(Debug, Copy, Clone)] 67 | pub struct NoopIdProvider; 68 | 69 | impl IdProvider for NoopIdProvider { 70 | fn next_id(&self) -> SubscriptionId<'static> { 71 | 0.into() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Shared utilities for `jsonrpsee`. 28 | 29 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 30 | #![cfg_attr(docsrs, feature(doc_cfg))] 31 | 32 | // Macros useful internally within this crate, but not to be exposed outside of it. 33 | #[macro_use] 34 | mod macros; 35 | 36 | /// Error type. 37 | pub mod error; 38 | 39 | /// Traits 40 | pub mod traits; 41 | 42 | /// RPC Parameters. 43 | pub mod params; 44 | 45 | cfg_http_helpers! { 46 | pub mod http_helpers; 47 | } 48 | 49 | cfg_server! { 50 | pub mod id_providers; 51 | pub mod server; 52 | } 53 | 54 | cfg_client! { 55 | pub mod client; 56 | pub use client::Error as ClientError; 57 | } 58 | 59 | cfg_client_or_server! { 60 | pub mod middleware; 61 | } 62 | 63 | pub use async_trait::async_trait; 64 | pub use error::{RegisterMethodError, SubscriptionError}; 65 | 66 | /// JSON-RPC result. 67 | pub type RpcResult = std::result::Result; 68 | 69 | /// Empty server `RpcParams` type to use while registering modules. 70 | pub type EmptyServerParams = Vec<()>; 71 | 72 | #[doc(hidden)] 73 | mod proc_macros_support; 74 | 75 | /// Re-exports for proc-macro library to not require any additional 76 | /// dependencies to be explicitly added on the client side. 77 | #[doc(hidden)] 78 | pub mod __reexports { 79 | pub use async_trait::async_trait; 80 | pub use serde; 81 | pub use serde_json; 82 | 83 | // Needed for the params parsing in the proc macro API. 84 | cfg_client_or_server! { 85 | pub use tokio; 86 | } 87 | 88 | pub use super::proc_macros_support::*; 89 | } 90 | 91 | pub use serde::{Serialize, de::DeserializeOwned}; 92 | pub use serde_json::{ 93 | Value as JsonValue, to_value as to_json_value, value::RawValue as JsonRawValue, 94 | value::to_raw_value as to_json_raw_value, 95 | }; 96 | pub use std::borrow::Cow; 97 | 98 | /// Ten megabytes. 99 | pub const TEN_MB_SIZE_BYTES: u32 = 10 * 1024 * 1024; 100 | 101 | /// The return type if the subscription wants to return `Result`. 102 | pub type SubscriptionResult = Result<(), SubscriptionError>; 103 | 104 | /// Type erased error. 105 | pub type BoxError = Box; 106 | -------------------------------------------------------------------------------- /core/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! cfg_feature { 2 | ($feature:literal, $($item:item)*) => { 3 | $( 4 | #[cfg(feature = $feature)] 5 | #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] 6 | $item 7 | )* 8 | } 9 | } 10 | 11 | macro_rules! cfg_client { 12 | ($($item:item)*) => { 13 | cfg_feature!("client", $($item)*); 14 | }; 15 | } 16 | 17 | macro_rules! cfg_server { 18 | ($($item:item)*) => { 19 | cfg_feature!("server", $($item)*); 20 | }; 21 | } 22 | 23 | macro_rules! cfg_client_or_server { 24 | ($($item:item)*) => { 25 | $( 26 | #[cfg(any(feature = "client", feature = "server"))] 27 | $item 28 | )* 29 | } 30 | } 31 | 32 | macro_rules! cfg_http_helpers { 33 | ($($item:item)*) => { 34 | cfg_feature!("http-helpers", $($item)*); 35 | }; 36 | } 37 | 38 | macro_rules! cfg_async_client { 39 | ($($item:item)*) => { 40 | $( 41 | #[cfg(any(feature = "async-wasm-client", feature = "async-client"))] 42 | #[cfg_attr(docsrs, doc(cfg(feature = "async-client")))] 43 | #[cfg_attr(docsrs, doc(cfg(feature = "async-wasm-client")))] 44 | $item 45 | )* 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/middleware/layer/either.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! [`tower::util::Either`] but 28 | //! adjusted to satisfy the trait bound [`RpcServiceT]. 29 | //! 30 | //! NOTE: This is introduced because it doesn't 31 | //! work to implement tower::Layer for 32 | //! external types such as future::Either. 33 | 34 | use crate::middleware::{Batch, Notification, RpcServiceT}; 35 | use jsonrpsee_types::Request; 36 | 37 | /// [`tower::util::Either`] but 38 | /// adjusted to satisfy the trait bound [`RpcServiceT]. 39 | #[derive(Clone, Debug)] 40 | pub enum Either { 41 | /// One type of backing [`RpcServiceT`]. 42 | Left(A), 43 | /// The other type of backing [`RpcServiceT`]. 44 | Right(B), 45 | } 46 | 47 | impl tower::Layer for Either 48 | where 49 | A: tower::Layer, 50 | B: tower::Layer, 51 | { 52 | type Service = Either; 53 | 54 | fn layer(&self, inner: S) -> Self::Service { 55 | match self { 56 | Either::Left(layer) => Either::Left(layer.layer(inner)), 57 | Either::Right(layer) => Either::Right(layer.layer(inner)), 58 | } 59 | } 60 | } 61 | 62 | impl RpcServiceT for Either 63 | where 64 | A: RpcServiceT + Send, 65 | B: RpcServiceT< 66 | MethodResponse = A::MethodResponse, 67 | NotificationResponse = A::NotificationResponse, 68 | BatchResponse = A::BatchResponse, 69 | > + Send, 70 | { 71 | type BatchResponse = A::BatchResponse; 72 | type MethodResponse = A::MethodResponse; 73 | type NotificationResponse = A::NotificationResponse; 74 | 75 | fn call<'a>(&self, request: Request<'a>) -> impl Future + Send + 'a { 76 | match self { 77 | Either::Left(service) => futures_util::future::Either::Left(service.call(request)), 78 | Either::Right(service) => futures_util::future::Either::Right(service.call(request)), 79 | } 80 | } 81 | 82 | fn batch<'a>(&self, batch: Batch<'a>) -> impl Future + Send + 'a { 83 | match self { 84 | Either::Left(service) => futures_util::future::Either::Left(service.batch(batch)), 85 | Either::Right(service) => futures_util::future::Either::Right(service.batch(batch)), 86 | } 87 | } 88 | 89 | fn notification<'a>(&self, n: Notification<'a>) -> impl Future + Send + 'a { 90 | match self { 91 | Either::Left(service) => futures_util::future::Either::Left(service.notification(n)), 92 | Either::Right(service) => futures_util::future::Either::Right(service.notification(n)), 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /core/src/middleware/layer/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Specific middleware layer implementation provided by jsonrpsee. 28 | 29 | mod either; 30 | mod logger; 31 | 32 | pub use either::*; 33 | pub use logger::*; 34 | -------------------------------------------------------------------------------- /core/src/proc_macros_support.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee_types::ErrorObjectOwned; 2 | 3 | // We're marking functions on the error paths as #[cold] to both reduce chance of inlining and to 4 | // make the generated assembly slightly better. 5 | 6 | #[cold] 7 | pub fn log_fail_parse(arg_pat: &str, ty: &str, err: &ErrorObjectOwned, optional: bool) { 8 | let optional = if optional { "optional " } else { "" }; 9 | tracing::debug!("Error parsing {optional}\"{arg_pat}\" as \"{ty}\": {err}"); 10 | } 11 | 12 | #[cold] 13 | pub fn log_fail_parse_as_object(err: &ErrorObjectOwned) { 14 | tracing::debug!("Failed to parse JSON-RPC params as object: {err}"); 15 | } 16 | 17 | #[cold] 18 | pub fn panic_fail_serialize(param: &str, err: serde_json::Error) -> ! { 19 | panic!("Parameter `{param}` cannot be serialized: {err}"); 20 | } 21 | 22 | #[cfg(debug_assertions)] 23 | #[cold] 24 | pub fn panic_fail_register() -> ! { 25 | panic!("RPC macro method names should never conflict. This is a bug, please report it."); 26 | } 27 | -------------------------------------------------------------------------------- /core/src/server/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use crate::server::SubscriptionMessage; 28 | use serde_json::value::RawValue; 29 | use tokio::sync::mpsc; 30 | 31 | /// Error that may occur during [`crate::server::MethodSink::try_send`] or [`crate::server::SubscriptionSink::try_send`]. 32 | #[derive(Debug, thiserror::Error)] 33 | pub enum TrySendError { 34 | /// The connection channel is closed. 35 | #[error("The connection channel is closed")] 36 | Closed(SubscriptionMessage), 37 | /// The connection channel is full. 38 | #[error("The connection channel is full")] 39 | Full(SubscriptionMessage), 40 | } 41 | 42 | /// Error that may occur during [`crate::server::MethodSink::send`] or [`crate::server::SubscriptionSink::send`]. 43 | #[derive(Debug, thiserror::Error)] 44 | #[error("The connection channel is closed")] 45 | pub struct DisconnectError(pub SubscriptionMessage); 46 | 47 | /// Error that may occur during [`crate::server::MethodSink::send_timeout`] or [`crate::server::SubscriptionSink::send_timeout`]. 48 | #[derive(Debug, thiserror::Error)] 49 | pub enum SendTimeoutError { 50 | /// The data could not be sent because the timeout elapsed 51 | /// which most likely is that the channel is full. 52 | #[error("The connection channel timed out waiting on send operation")] 53 | Timeout(SubscriptionMessage), 54 | /// The connection channel is closed. 55 | #[error("The connection channel is closed")] 56 | Closed(SubscriptionMessage), 57 | } 58 | 59 | /// The error returned while accepting a subscription. 60 | #[derive(Debug, Copy, Clone, thiserror::Error)] 61 | #[error("The remote peer closed the connection")] 62 | pub struct PendingSubscriptionAcceptError; 63 | 64 | impl From>> for DisconnectError { 65 | fn from(e: mpsc::error::SendError>) -> Self { 66 | DisconnectError(SubscriptionMessage::from_complete_message(e.0)) 67 | } 68 | } 69 | 70 | impl From>> for TrySendError { 71 | fn from(e: mpsc::error::TrySendError>) -> Self { 72 | match e { 73 | mpsc::error::TrySendError::Closed(m) => Self::Closed(SubscriptionMessage::from_complete_message(m)), 74 | mpsc::error::TrySendError::Full(m) => Self::Full(SubscriptionMessage::from_complete_message(m)), 75 | } 76 | } 77 | } 78 | 79 | impl From>> for SendTimeoutError { 80 | fn from(e: mpsc::error::SendTimeoutError>) -> Self { 81 | match e { 82 | mpsc::error::SendTimeoutError::Closed(m) => Self::Closed(SubscriptionMessage::from_complete_message(m)), 83 | mpsc::error::SendTimeoutError::Timeout(m) => Self::Timeout(SubscriptionMessage::from_complete_message(m)), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/server/helpers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::time::Duration; 28 | 29 | use jsonrpsee_types::{ErrorCode, ErrorObject, Id, InvalidRequest, Response, ResponsePayload}; 30 | use serde_json::value::RawValue; 31 | use tokio::sync::mpsc; 32 | 33 | use super::{DisconnectError, SendTimeoutError, TrySendError}; 34 | 35 | /// Sink that is used to send back the result to the server for a specific method. 36 | #[derive(Clone, Debug)] 37 | pub struct MethodSink { 38 | /// Channel sender. 39 | tx: mpsc::Sender>, 40 | /// Max response size in bytes for a executed call. 41 | max_response_size: u32, 42 | } 43 | 44 | impl MethodSink { 45 | /// Create a new `MethodSink` with unlimited response size. 46 | pub fn new(tx: mpsc::Sender>) -> Self { 47 | MethodSink { tx, max_response_size: u32::MAX } 48 | } 49 | 50 | /// Create a new `MethodSink` with a limited response size. 51 | pub fn new_with_limit(tx: mpsc::Sender>, max_response_size: u32) -> Self { 52 | MethodSink { tx, max_response_size } 53 | } 54 | 55 | /// Returns whether this channel is closed without needing a context. 56 | pub fn is_closed(&self) -> bool { 57 | self.tx.is_closed() 58 | } 59 | 60 | /// Same as [`tokio::sync::mpsc::Sender::closed`]. 61 | /// 62 | /// # Cancel safety 63 | /// This method is cancel safe. Once the channel is closed, 64 | /// it stays closed forever and all future calls to closed will return immediately. 65 | pub async fn closed(&self) { 66 | self.tx.closed().await 67 | } 68 | 69 | /// Get the max response size. 70 | pub const fn max_response_size(&self) -> u32 { 71 | self.max_response_size 72 | } 73 | 74 | /// Attempts to send out the message immediately and fails if the underlying 75 | /// connection has been closed or if the message buffer is full. 76 | /// 77 | /// Returns the message if the send fails such that either can be thrown away or re-sent later. 78 | pub fn try_send(&mut self, msg: Box) -> Result<(), TrySendError> { 79 | self.tx.try_send(msg).map_err(Into::into) 80 | } 81 | 82 | /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. 83 | pub async fn send(&self, msg: Box) -> Result<(), DisconnectError> { 84 | self.tx.send(msg).await.map_err(Into::into) 85 | } 86 | 87 | /// Send a JSON-RPC error to the client 88 | pub async fn send_error<'a>(&self, id: Id<'a>, err: ErrorObject<'a>) -> Result<(), DisconnectError> { 89 | let payload = ResponsePayload::<()>::error_borrowed(err); 90 | let json = serde_json::value::to_raw_value(&Response::new(payload, id)).expect("valid JSON; qed"); 91 | 92 | self.send(json).await 93 | } 94 | 95 | /// Similar to `MethodSink::send` but only waits for a limited time. 96 | pub async fn send_timeout(&self, msg: Box, timeout: Duration) -> Result<(), SendTimeoutError> { 97 | self.tx.send_timeout(msg, timeout).await.map_err(Into::into) 98 | } 99 | 100 | /// Get the capacity of the channel. 101 | pub fn capacity(&self) -> usize { 102 | self.tx.capacity() 103 | } 104 | 105 | /// Get the max capacity of the channel. 106 | pub fn max_capacity(&self) -> usize { 107 | self.tx.max_capacity() 108 | } 109 | 110 | /// Waits for there to be space on the return channel. 111 | pub async fn has_capacity(&self) -> Result<(), DisconnectError> { 112 | match self.tx.reserve().await { 113 | // The permit is thrown away here because it's just 114 | // a way to ensure that the return buffer has space. 115 | Ok(_) => Ok(()), 116 | Err(_) => Err(DisconnectError(RawValue::NULL.to_owned().into())), 117 | } 118 | } 119 | } 120 | 121 | /// Figure out if this is a sufficiently complete request that we can extract an [`Id`] out of, or just plain 122 | /// unparseable garbage. 123 | pub fn prepare_error(data: &[u8]) -> (Id<'_>, ErrorCode) { 124 | match serde_json::from_slice::(data) { 125 | Ok(InvalidRequest { id }) => (id, ErrorCode::InvalidRequest), 126 | Err(_) => (Id::Null, ErrorCode::ParseError), 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /core/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Shared modules for the JSON-RPC servers. 28 | 29 | /// Error types. 30 | mod error; 31 | /// Helpers. 32 | pub mod helpers; 33 | /// Method response. 34 | mod method_response; 35 | /// JSON-RPC "modules" group sets of methods that belong together and handles method/subscription registration. 36 | mod rpc_module; 37 | /// Subscription related types. 38 | mod subscription; 39 | 40 | pub use error::*; 41 | pub use helpers::*; 42 | pub use http::Extensions; 43 | pub use method_response::*; 44 | pub use rpc_module::*; 45 | pub use subscription::*; 46 | 47 | use jsonrpsee_types::ErrorObjectOwned; 48 | 49 | const LOG_TARGET: &str = "jsonrpsee-server"; 50 | 51 | /// Something that can be converted into a JSON-RPC method call response. 52 | /// 53 | /// If the value couldn't be serialized/encoded, jsonrpsee will sent out an error 54 | /// to the client `response could not be serialized`. 55 | pub trait IntoResponse { 56 | /// Output. 57 | type Output: serde::Serialize + Clone; 58 | 59 | /// Something that can be converted into a JSON-RPC method call response. 60 | fn into_response(self) -> ResponsePayload<'static, Self::Output>; 61 | } 62 | 63 | impl> IntoResponse for Result 64 | where 65 | T: serde::Serialize + Clone, 66 | { 67 | type Output = T; 68 | 69 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 70 | match self { 71 | Ok(val) => ResponsePayload::success(val), 72 | Err(e) => ResponsePayload::error(e), 73 | } 74 | } 75 | } 76 | 77 | impl IntoResponse for Option 78 | where 79 | T: serde::Serialize + Clone, 80 | { 81 | type Output = Option; 82 | 83 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 84 | ResponsePayload::success(self) 85 | } 86 | } 87 | 88 | impl IntoResponse for Vec 89 | where 90 | T: serde::Serialize + Clone, 91 | { 92 | type Output = Vec; 93 | 94 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 95 | ResponsePayload::success(self) 96 | } 97 | } 98 | 99 | impl IntoResponse for [T; N] 100 | where 101 | [T; N]: serde::Serialize + Clone, 102 | { 103 | type Output = [T; N]; 104 | 105 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 106 | ResponsePayload::success(self) 107 | } 108 | } 109 | 110 | impl IntoResponse for jsonrpsee_types::ResponsePayload<'static, T> 111 | where 112 | T: serde::Serialize + Clone, 113 | { 114 | type Output = T; 115 | 116 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 117 | self.into() 118 | } 119 | } 120 | 121 | impl IntoResponse for ResponsePayload<'static, T> 122 | where 123 | T: serde::Serialize + Clone, 124 | { 125 | type Output = T; 126 | 127 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 128 | self 129 | } 130 | } 131 | 132 | impl IntoResponse for ErrorObjectOwned { 133 | type Output = (); 134 | 135 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 136 | ResponsePayload::error(self) 137 | } 138 | } 139 | 140 | macro_rules! impl_into_response { 141 | ($($n:ty),*) => { 142 | $( 143 | impl IntoResponse for $n { 144 | type Output = $n; 145 | 146 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 147 | ResponsePayload::success(self) 148 | } 149 | } 150 | )+ 151 | } 152 | } 153 | 154 | impl_into_response!( 155 | u8, 156 | u16, 157 | u32, 158 | u64, 159 | u128, 160 | usize, 161 | i8, 162 | i16, 163 | i32, 164 | i64, 165 | i128, 166 | isize, 167 | String, 168 | &'static str, 169 | bool, 170 | serde_json::Value, 171 | () 172 | ); 173 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-examples" 3 | description = "Examples for jsonrpsee" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | publish = false 9 | 10 | [dev-dependencies] 11 | anyhow = { workspace = true } 12 | http-body-util = { workspace = true } 13 | http-body = { workspace = true } 14 | futures = { workspace = true } 15 | jsonrpsee = { path = "../jsonrpsee", features = ["server", "http-client", "ws-client", "macros", "client-ws-transport-tls"] } 16 | tracing = { workspace = true } 17 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 18 | tokio = { workspace = true, features = ["rt-multi-thread", "time"] } 19 | tokio-stream = { workspace = true, features = ["sync"] } 20 | serde_json = { workspace = true } 21 | tower-http = { workspace = true, features = ["cors", "compression-full", "sensitive-headers", "trace", "timeout"] } 22 | tower = { workspace = true, features = ["timeout"] } 23 | hyper = { workspace = true } 24 | hyper-util = { workspace = true, features = ["client", "client-legacy"]} 25 | console-subscriber = { workspace = true } -------------------------------------------------------------------------------- /examples/examples/client_subscription_drop_oldest_item.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | use std::time::Duration; 29 | 30 | use futures::{Stream, StreamExt}; 31 | use jsonrpsee::core::DeserializeOwned; 32 | use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; 33 | use jsonrpsee::rpc_params; 34 | use jsonrpsee::server::{RpcModule, Server}; 35 | use jsonrpsee::ws_client::WsClientBuilder; 36 | use tokio_stream::wrappers::BroadcastStream; 37 | use tokio_stream::wrappers::errors::BroadcastStreamRecvError; 38 | 39 | #[tokio::main] 40 | async fn main() -> anyhow::Result<()> { 41 | tracing_subscriber::FmtSubscriber::builder() 42 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 43 | .try_init() 44 | .expect("setting default subscriber failed"); 45 | 46 | let addr = run_server().await?; 47 | let url = format!("ws://{}", addr); 48 | 49 | let client = WsClientBuilder::default().build(&url).await?; 50 | 51 | let sub: Subscription = client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?; 52 | 53 | // drop oldest messages from subscription: 54 | let mut sub = drop_oldest_when_lagging(sub, 10); 55 | 56 | // Simulate that polling takes a long time. 57 | tokio::time::sleep(Duration::from_secs(1)).await; 58 | 59 | // The subscription starts from zero but you can 60 | // notice that many items have been replaced 61 | // because the subscription wasn't polled. 62 | for _ in 0..10 { 63 | match sub.next().await.unwrap() { 64 | Ok(n) => { 65 | tracing::info!("recv={n}"); 66 | } 67 | Err(e) => { 68 | tracing::info!("{e}"); 69 | } 70 | }; 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | fn drop_oldest_when_lagging( 77 | mut sub: Subscription, 78 | buffer_size: usize, 79 | ) -> impl Stream> { 80 | let (tx, rx) = tokio::sync::broadcast::channel(buffer_size); 81 | 82 | tokio::spawn(async move { 83 | // Poll the subscription which ignores errors. 84 | while let Some(n) = sub.next().await { 85 | let msg = match n { 86 | Ok(msg) => msg, 87 | Err(e) => { 88 | tracing::error!("Failed to decode the subscription message: {e}"); 89 | continue; 90 | } 91 | }; 92 | 93 | if tx.send(msg).is_err() { 94 | return; 95 | } 96 | } 97 | }); 98 | 99 | BroadcastStream::new(rx) 100 | } 101 | 102 | async fn run_server() -> anyhow::Result { 103 | let server = Server::builder().build("127.0.0.1:0").await?; 104 | let mut module = RpcModule::new(()); 105 | module 106 | .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", |_, pending, _, _| async move { 107 | let sub = pending.accept().await.unwrap(); 108 | 109 | for i in 0..usize::MAX { 110 | let json = serde_json::value::to_raw_value(&i).unwrap(); 111 | sub.send(json).await.unwrap(); 112 | tokio::time::sleep(Duration::from_millis(10)).await; 113 | } 114 | 115 | Ok(()) 116 | }) 117 | .unwrap(); 118 | let addr = server.local_addr()?; 119 | 120 | let handle = server.start(module); 121 | 122 | // In this example we don't care about doing shutdown so let's it run forever. 123 | // You may use the `ServerHandle` to shut it down or manage it yourself. 124 | tokio::spawn(handle.stopped()); 125 | 126 | Ok(addr) 127 | } 128 | -------------------------------------------------------------------------------- /examples/examples/core_client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::client_transport::ws::{Url, WsTransportClientBuilder}; 30 | use jsonrpsee::core::client::{ClientBuilder, ClientT}; 31 | use jsonrpsee::rpc_params; 32 | use jsonrpsee::server::{RpcModule, Server}; 33 | 34 | #[tokio::main] 35 | async fn main() -> anyhow::Result<()> { 36 | tracing_subscriber::FmtSubscriber::builder() 37 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 38 | .try_init() 39 | .expect("setting default subscriber failed"); 40 | 41 | let addr = run_server().await?; 42 | let uri = Url::parse(&format!("ws://{}", addr))?; 43 | 44 | let (tx, rx) = WsTransportClientBuilder::default().build(uri).await?; 45 | let client = ClientBuilder::default().build_with_tokio(tx, rx); 46 | let response: String = client.request("say_hello", rpc_params![]).await?; 47 | tracing::info!("response: {:?}", response); 48 | 49 | Ok(()) 50 | } 51 | 52 | async fn run_server() -> anyhow::Result { 53 | let server = Server::builder().build("127.0.0.1:0").await?; 54 | let mut module = RpcModule::new(()); 55 | module.register_method("say_hello", |_, _, _| "lo")?; 56 | let addr = server.local_addr()?; 57 | 58 | let handle = server.start(module); 59 | 60 | // In this example we don't care about doing shutdown so let's it run forever. 61 | // You may use the `ServerHandle` to shut it down or manage it yourself. 62 | tokio::spawn(handle.stopped()); 63 | 64 | Ok(addr) 65 | } 66 | -------------------------------------------------------------------------------- /examples/examples/cors_server.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! This example adds upstream CORS layers to the RPC service, 28 | //! with access control allowing requests from all hosts. 29 | 30 | use hyper::Method; 31 | use jsonrpsee::server::{RpcModule, Server}; 32 | use std::net::SocketAddr; 33 | use tower_http::cors::{Any, CorsLayer}; 34 | 35 | #[tokio::main] 36 | async fn main() -> anyhow::Result<()> { 37 | tracing_subscriber::FmtSubscriber::builder() 38 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 39 | .try_init() 40 | .expect("setting default subscriber failed"); 41 | 42 | // Start up a JSON-RPC server that allows cross origin requests. 43 | let server_addr = run_server().await?; 44 | 45 | // Print instructions for testing CORS from a browser. 46 | println!("Run the following snippet in the developer console in any Website."); 47 | println!( 48 | r#" 49 | fetch("http://{}", {{ 50 | method: 'POST', 51 | mode: 'cors', 52 | headers: {{ 'Content-Type': 'application/json' }}, 53 | body: JSON.stringify({{ 54 | jsonrpc: '2.0', 55 | method: 'say_hello', 56 | id: 1 57 | }}) 58 | }}).then(res => {{ 59 | console.log("Response:", res); 60 | return res.text() 61 | }}).then(body => {{ 62 | console.log("Response Body:", body) 63 | }}); 64 | "#, 65 | server_addr 66 | ); 67 | 68 | futures::future::pending().await 69 | } 70 | 71 | async fn run_server() -> anyhow::Result { 72 | // Add a CORS middleware for handling HTTP requests. 73 | // This middleware does affect the response, including appropriate 74 | // headers to satisfy CORS. Because any origins are allowed, the 75 | // "Access-Control-Allow-Origin: *" header is appended to the response. 76 | let cors = CorsLayer::new() 77 | // Allow `POST` when accessing the resource 78 | .allow_methods([Method::POST]) 79 | // Allow requests from any origin 80 | .allow_origin(Any) 81 | .allow_headers([hyper::header::CONTENT_TYPE]); 82 | let middleware = tower::ServiceBuilder::new().layer(cors); 83 | 84 | // The RPC exposes the access control for filtering and the middleware for 85 | // modifying requests / responses. These features are independent of one another 86 | // and can also be used separately. 87 | // In this example, we use both features. 88 | let server = Server::builder().set_http_middleware(middleware).build("127.0.0.1:0".parse::()?).await?; 89 | 90 | let mut module = RpcModule::new(()); 91 | module.register_method("say_hello", |_, _, _| { 92 | println!("say_hello method called!"); 93 | "Hello there!!" 94 | })?; 95 | 96 | let addr = server.local_addr()?; 97 | let handle = server.start(module); 98 | 99 | // In this example we don't care about doing shutdown so let's it run forever. 100 | // You may use the `ServerHandle` to shut it down or manage it yourself. 101 | tokio::spawn(handle.stopped()); 102 | 103 | Ok(addr) 104 | } 105 | -------------------------------------------------------------------------------- /examples/examples/host_filter_middleware.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! This example shows how to configure `host filtering` by tower middleware on the jsonrpsee server. 28 | //! 29 | //! The server whitelist's only `example.com` and any call from localhost will be 30 | //! rejected both by HTTP and WebSocket transports. 31 | 32 | use std::net::SocketAddr; 33 | 34 | use jsonrpsee::core::client::ClientT; 35 | use jsonrpsee::http_client::HttpClient; 36 | use jsonrpsee::rpc_params; 37 | use jsonrpsee::server::middleware::http::HostFilterLayer; 38 | use jsonrpsee::server::{RpcModule, Server}; 39 | 40 | #[tokio::main] 41 | async fn main() -> anyhow::Result<()> { 42 | tracing_subscriber::FmtSubscriber::builder() 43 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 44 | .try_init() 45 | .expect("setting default subscriber failed"); 46 | 47 | let addr = run_server().await?; 48 | let url = format!("http://{}", addr); 49 | 50 | // Use RPC client to get the response of `say_hello` method. 51 | let client = HttpClient::builder().build(&url)?; 52 | // This call will be denied because only `example.com` URIs/hosts are allowed by the host filter. 53 | let response = client.request::("say_hello", rpc_params![]).await.unwrap_err(); 54 | println!("[main]: response: {}", response); 55 | 56 | Ok(()) 57 | } 58 | 59 | async fn run_server() -> anyhow::Result { 60 | // Custom tower service to handle the RPC requests 61 | let service_builder = tower::ServiceBuilder::new() 62 | // For this example we only want to permit requests from `example.com` 63 | // all other request are denied. 64 | // 65 | // `HostFilerLayer::new` only fails on invalid URIs.. 66 | .layer(HostFilterLayer::new(["example.com"]).unwrap()); 67 | 68 | let server = 69 | Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::()?).await?; 70 | 71 | let addr = server.local_addr()?; 72 | 73 | let mut module = RpcModule::new(()); 74 | module.register_method("say_hello", |_, _, _| "lo").unwrap(); 75 | 76 | let handle = server.start(module); 77 | 78 | // In this example we don't care about doing shutdown so let's it run forever. 79 | // You may use the `ServerHandle` to shut it down or manage it yourself. 80 | tokio::spawn(handle.stopped()); 81 | 82 | Ok(addr) 83 | } 84 | -------------------------------------------------------------------------------- /examples/examples/http.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::core::client::ClientT; 30 | use jsonrpsee::http_client::HttpClient; 31 | use jsonrpsee::rpc_params; 32 | use jsonrpsee::server::{RpcModule, Server}; 33 | use tracing_subscriber::util::SubscriberInitExt; 34 | 35 | #[tokio::main] 36 | async fn main() -> anyhow::Result<()> { 37 | let filter = tracing_subscriber::EnvFilter::try_from_default_env()? 38 | .add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?); 39 | tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?; 40 | 41 | let server_addr = run_server().await?; 42 | let url = format!("http://{}", server_addr); 43 | 44 | let client = HttpClient::builder().build(url)?; 45 | let params = rpc_params![1_u64, 2, 3]; 46 | let response: Result = client.request("say_hello", params).await; 47 | tracing::info!("r: {:?}", response); 48 | 49 | Ok(()) 50 | } 51 | 52 | async fn run_server() -> anyhow::Result { 53 | let server = Server::builder().build("127.0.0.1:0".parse::()?).await?; 54 | let mut module = RpcModule::new(()); 55 | module.register_method("say_hello", |_, _, _| "lo")?; 56 | 57 | let addr = server.local_addr()?; 58 | let handle = server.start(module); 59 | 60 | // In this example we don't care about doing shutdown so let's it run forever. 61 | // You may use the `ServerHandle` to shut it down or manage it yourself. 62 | tokio::spawn(handle.stopped()); 63 | 64 | Ok(addr) 65 | } 66 | -------------------------------------------------------------------------------- /examples/examples/http_middleware.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! jsonrpsee supports two kinds of middlewares `http_middleware` and `rpc_middleware`. 28 | //! 29 | //! This example demonstrates how to use the `http_middleware` which applies for each 30 | //! HTTP request. 31 | //! 32 | //! A typical use-case for this it to apply a specific CORS policy which applies both 33 | //! for HTTP and WebSocket. 34 | //! 35 | 36 | use hyper::Method; 37 | use hyper::body::Bytes; 38 | use hyper::http::HeaderValue; 39 | use jsonrpsee::rpc_params; 40 | use std::iter::once; 41 | use std::net::SocketAddr; 42 | use std::time::Duration; 43 | use tower_http::LatencyUnit; 44 | use tower_http::compression::CompressionLayer; 45 | use tower_http::cors::CorsLayer; 46 | use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer; 47 | use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; 48 | 49 | use jsonrpsee::core::client::ClientT; 50 | use jsonrpsee::http_client::HttpClient; 51 | use jsonrpsee::server::{RpcModule, Server}; 52 | use jsonrpsee::ws_client::WsClientBuilder; 53 | 54 | #[tokio::main] 55 | async fn main() -> anyhow::Result<()> { 56 | tracing_subscriber::FmtSubscriber::builder() 57 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 58 | .try_init() 59 | .expect("setting default subscriber failed"); 60 | 61 | let addr = run_server().await?; 62 | 63 | // WebSocket. 64 | { 65 | let client = WsClientBuilder::default().build(format!("ws://{}", addr)).await?; 66 | let response: String = client.request("say_hello", rpc_params![]).await?; 67 | println!("[main]: ws response: {:?}", response); 68 | let _response: Result = client.request("unknown_method", rpc_params![]).await; 69 | let _ = client.request::("say_hello", rpc_params![]).await?; 70 | } 71 | 72 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 73 | 74 | // HTTP. 75 | { 76 | let client = HttpClient::builder().build(format!("http://{}", addr))?; 77 | let response: String = client.request("say_hello", rpc_params![]).await?; 78 | println!("[main]: http response: {:?}", response); 79 | let _response: Result = client.request("unknown_method", rpc_params![]).await; 80 | let _ = client.request::("say_hello", rpc_params![]).await?; 81 | } 82 | 83 | Ok(()) 84 | } 85 | 86 | async fn run_server() -> anyhow::Result { 87 | let cors = CorsLayer::new() 88 | // Allow `POST` when accessing the resource 89 | .allow_methods([Method::POST]) 90 | // Allow requests from any origin 91 | .allow_origin(HeaderValue::from_str("http://example.com").unwrap()) 92 | .allow_headers([hyper::header::CONTENT_TYPE]); 93 | 94 | // Custom tower service to handle the RPC requests 95 | let service_builder = tower::ServiceBuilder::new() 96 | // Add high level tracing/logging to all requests 97 | .layer( 98 | TraceLayer::new_for_http() 99 | .on_request( 100 | |request: &hyper::Request<_>, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), 101 | ) 102 | .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { 103 | tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") 104 | }) 105 | .make_span_with(DefaultMakeSpan::new().include_headers(true)) 106 | .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)), 107 | ) 108 | // Mark the `Authorization` request header as sensitive so it doesn't show in logs 109 | .layer(SetSensitiveRequestHeadersLayer::new(once(hyper::header::AUTHORIZATION))) 110 | .layer(cors) 111 | .layer(CompressionLayer::new()) 112 | .timeout(Duration::from_secs(2)); 113 | 114 | let server = 115 | Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::()?).await?; 116 | 117 | let addr = server.local_addr()?; 118 | 119 | let mut module = RpcModule::new(()); 120 | module.register_method("say_hello", |_, _, _| "lo").unwrap(); 121 | 122 | let handle = server.start(module); 123 | 124 | // In this example we don't care about doing shutdown so let's it run forever. 125 | // You may use the `ServerHandle` to shut it down or manage it yourself. 126 | tokio::spawn(handle.stopped()); 127 | 128 | Ok(addr) 129 | } 130 | -------------------------------------------------------------------------------- /examples/examples/http_proxy_middleware.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! This example utilizes the `ProxyRequest` layer for redirecting 28 | //! `GET /path` requests to internal RPC methods. 29 | //! 30 | //! The RPC server registers a method named `system_health` which 31 | //! returns `serde_json::Value`. Redirect any `GET /health` 32 | //! requests to the internal method, and return only the method's 33 | //! response in the body (ie, without any jsonRPC 2.0 overhead). 34 | //! 35 | //! # Note 36 | //! 37 | //! This functionality is useful for services which would 38 | //! like to query a certain `URI` path for statistics. 39 | 40 | use hyper_util::client::legacy::Client; 41 | use hyper_util::rt::TokioExecutor; 42 | use std::net::SocketAddr; 43 | use std::time::Duration; 44 | 45 | use jsonrpsee::core::client::ClientT; 46 | use jsonrpsee::http_client::HttpClient; 47 | use jsonrpsee::rpc_params; 48 | use jsonrpsee::server::middleware::http::ProxyGetRequestLayer; 49 | use jsonrpsee::server::{RpcModule, Server}; 50 | 51 | type EmptyBody = http_body_util::Empty; 52 | 53 | #[tokio::main] 54 | async fn main() -> anyhow::Result<()> { 55 | tracing_subscriber::FmtSubscriber::builder() 56 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 57 | .try_init() 58 | .expect("setting default subscriber failed"); 59 | 60 | let addr = run_server().await?; 61 | let url = format!("http://{}", addr); 62 | 63 | // Use RPC client to get the response of `say_hello` method. 64 | let client = HttpClient::builder().build(&url)?; 65 | let response: String = client.request("say_hello", rpc_params![]).await?; 66 | println!("[main]: response: {:?}", response); 67 | 68 | // Use hyper client to manually submit a `GET /health` request. 69 | let http_client = Client::builder(TokioExecutor::new()).build_http(); 70 | let uri = format!("http://{}/health", addr); 71 | 72 | let req = hyper::Request::builder().method("GET").uri(&uri).body(EmptyBody::new())?; 73 | println!("[main]: Submit proxy request: {:?}", req); 74 | let res = http_client.request(req).await?; 75 | println!("[main]: Received proxy response: {:?}", res); 76 | 77 | // Interpret the response as String. 78 | let collected = http_body_util::BodyExt::collect(res.into_body()).await?; 79 | let out = String::from_utf8(collected.to_bytes().to_vec()).unwrap(); 80 | println!("[main]: Interpret proxy response: {:?}", out); 81 | assert_eq!(out.as_str(), "{\"health\":true}"); 82 | 83 | Ok(()) 84 | } 85 | 86 | async fn run_server() -> anyhow::Result { 87 | // Custom tower service to handle the RPC requests 88 | let service_builder = tower::ServiceBuilder::new() 89 | // Proxy `GET /health` requests to internal `system_health` method. 90 | .layer(ProxyGetRequestLayer::new([("/health", "system_health")])?) 91 | .timeout(Duration::from_secs(2)); 92 | 93 | let server = 94 | Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::()?).await?; 95 | 96 | let addr = server.local_addr()?; 97 | 98 | let mut module = RpcModule::new(()); 99 | module.register_method("say_hello", |_, _, _| "lo").unwrap(); 100 | module.register_method("system_health", |_, _, _| serde_json::json!({ "health": true })).unwrap(); 101 | 102 | let handle = server.start(module); 103 | 104 | // In this example we don't care about doing shutdown so let's it run forever. 105 | // You may use the `ServerHandle` to shut it down or manage it yourself. 106 | tokio::spawn(handle.stopped()); 107 | 108 | Ok(addr) 109 | } 110 | -------------------------------------------------------------------------------- /examples/examples/proc_macro.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::core::{SubscriptionResult, async_trait, client::Subscription}; 30 | use jsonrpsee::proc_macros::rpc; 31 | use jsonrpsee::server::{PendingSubscriptionSink, Server}; 32 | use jsonrpsee::types::ErrorObjectOwned; 33 | use jsonrpsee::ws_client::WsClientBuilder; 34 | 35 | type ExampleHash = [u8; 32]; 36 | type ExampleStorageKey = Vec; 37 | 38 | #[rpc(server, client, namespace = "state")] 39 | pub trait Rpc 40 | where 41 | Hash: std::fmt::Debug, 42 | { 43 | /// Async method call example. 44 | #[method(name = "getKeys")] 45 | async fn storage_keys( 46 | &self, 47 | storage_key: StorageKey, 48 | hash: Option, 49 | ) -> Result, ErrorObjectOwned>; 50 | 51 | /// Subscription that takes a `StorageKey` as input and produces a `Vec`. 52 | #[subscription(name = "subscribeStorage" => "override", item = Vec)] 53 | async fn subscribe_storage(&self, keys: Option>) -> SubscriptionResult; 54 | 55 | #[subscription(name = "subscribeSync" => "sync", item = Vec)] 56 | fn s(&self, keys: Option>); 57 | } 58 | 59 | pub struct RpcServerImpl; 60 | 61 | #[async_trait] 62 | impl RpcServer for RpcServerImpl { 63 | async fn storage_keys( 64 | &self, 65 | storage_key: ExampleStorageKey, 66 | _hash: Option, 67 | ) -> Result, ErrorObjectOwned> { 68 | Ok(vec![storage_key]) 69 | } 70 | 71 | async fn subscribe_storage( 72 | &self, 73 | pending: PendingSubscriptionSink, 74 | _keys: Option>, 75 | ) -> SubscriptionResult { 76 | let sink = pending.accept().await?; 77 | let json = serde_json::value::to_raw_value(&vec![[0; 32]])?; 78 | sink.send(json).await?; 79 | 80 | Ok(()) 81 | } 82 | 83 | fn s(&self, pending: PendingSubscriptionSink, _keys: Option>) { 84 | tokio::spawn(async move { 85 | let sink = pending.accept().await.unwrap(); 86 | let json = serde_json::value::to_raw_value(&vec![[0; 32]]).unwrap(); 87 | sink.send(json).await.unwrap(); 88 | }); 89 | } 90 | } 91 | 92 | #[tokio::main] 93 | async fn main() -> anyhow::Result<()> { 94 | tracing_subscriber::FmtSubscriber::builder() 95 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 96 | .try_init() 97 | .expect("setting default subscriber failed"); 98 | 99 | let server_addr = run_server().await?; 100 | let url = format!("ws://{}", server_addr); 101 | 102 | let client = WsClientBuilder::default().build(&url).await?; 103 | assert_eq!(client.storage_keys(vec![1, 2, 3, 4], None::).await.unwrap(), vec![vec![1, 2, 3, 4]]); 104 | 105 | let mut sub: Subscription> = 106 | RpcClient::::subscribe_storage(&client, None).await.unwrap(); 107 | assert_eq!(Some(vec![[0; 32]]), sub.next().await.transpose().unwrap()); 108 | 109 | Ok(()) 110 | } 111 | 112 | async fn run_server() -> anyhow::Result { 113 | let server = Server::builder().build("127.0.0.1:0").await?; 114 | 115 | let addr = server.local_addr()?; 116 | let handle = server.start(RpcServerImpl.into_rpc()); 117 | 118 | // In this example we don't care about doing shutdown so let's it run forever. 119 | // You may use the `ServerHandle` to shut it down or manage it yourself. 120 | tokio::spawn(handle.stopped()); 121 | 122 | Ok(addr) 123 | } 124 | -------------------------------------------------------------------------------- /examples/examples/proc_macro_bounds.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::core::async_trait; 30 | use jsonrpsee::proc_macros::rpc; 31 | use jsonrpsee::server::Server; 32 | use jsonrpsee::types::ErrorObjectOwned; 33 | use jsonrpsee::ws_client::WsClientBuilder; 34 | type ExampleHash = [u8; 32]; 35 | 36 | pub trait Config { 37 | type Hash: Send + Sync + 'static; 38 | } 39 | 40 | impl Config for ExampleHash { 41 | type Hash = Self; 42 | } 43 | 44 | /// The RPC macro requires `DeserializeOwned` for output types for the client implementation, while the 45 | /// server implementation requires output types to be bounded by `Serialize`. 46 | /// 47 | /// In this example, we don't want the `Conf` to be bounded by default to 48 | /// `Conf : Send + Sync + 'static + jsonrpsee::core::DeserializeOwned` for client implementation and 49 | /// `Conf : Send + Sync + 'static + jsonrpsee::core::Serialize` for server implementation. 50 | /// 51 | /// Explicitly, specify client and server bounds to handle the `Serialize` and `DeserializeOwned` cases 52 | /// just for the `Conf::hash` part. 53 | #[rpc(server, client, namespace = "foo", client_bounds(T::Hash: jsonrpsee::core::DeserializeOwned), server_bounds(T::Hash: jsonrpsee::core::Serialize + Clone))] 54 | pub trait Rpc { 55 | #[method(name = "bar")] 56 | fn method(&self) -> Result; 57 | } 58 | 59 | pub struct RpcServerImpl; 60 | 61 | #[async_trait] 62 | impl RpcServer for RpcServerImpl { 63 | fn method(&self) -> Result<::Hash, ErrorObjectOwned> { 64 | Ok([0u8; 32]) 65 | } 66 | } 67 | 68 | #[tokio::main] 69 | async fn main() -> anyhow::Result<()> { 70 | tracing_subscriber::FmtSubscriber::builder() 71 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 72 | .try_init() 73 | .expect("setting default subscriber failed"); 74 | 75 | let server_addr = run_server().await?; 76 | let url = format!("ws://{}", server_addr); 77 | 78 | let client = WsClientBuilder::default().build(&url).await?; 79 | assert_eq!(RpcClient::::method(&client).await.unwrap(), [0u8; 32]); 80 | 81 | Ok(()) 82 | } 83 | 84 | async fn run_server() -> anyhow::Result { 85 | let server = Server::builder().build("127.0.0.1:0").await?; 86 | 87 | let addr = server.local_addr()?; 88 | let handle = server.start(RpcServerImpl.into_rpc()); 89 | 90 | // In this example we don't care about doing shutdown so let's it run forever. 91 | // You may use the `ServerHandle` to shut it down or manage it yourself. 92 | tokio::spawn(handle.stopped()); 93 | 94 | Ok(addr) 95 | } 96 | -------------------------------------------------------------------------------- /examples/examples/response_payload_notify_on_response.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::core::client::ClientT; 30 | use jsonrpsee::proc_macros::rpc; 31 | use jsonrpsee::server::Server; 32 | use jsonrpsee::ws_client::WsClientBuilder; 33 | use jsonrpsee::{ResponsePayload, rpc_params}; 34 | 35 | #[rpc(client, server, namespace = "state")] 36 | pub trait Rpc { 37 | /// Async method call example. 38 | #[method(name = "getKeys")] 39 | fn storage_keys(&self) -> ResponsePayload<'static, String>; 40 | } 41 | 42 | pub struct RpcServerImpl; 43 | 44 | impl RpcServer for RpcServerImpl { 45 | fn storage_keys(&self) -> ResponsePayload<'static, String> { 46 | let (rp, rp_future) = ResponsePayload::success("ehheeheh".to_string()).notify_on_completion(); 47 | 48 | tokio::spawn(async move { 49 | rp_future.await.unwrap(); 50 | println!("Method response to `state_getKeys` finished"); 51 | }); 52 | 53 | rp 54 | } 55 | } 56 | 57 | #[tokio::main] 58 | async fn main() -> anyhow::Result<()> { 59 | tracing_subscriber::FmtSubscriber::builder() 60 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 61 | .try_init() 62 | .expect("setting default subscriber failed"); 63 | 64 | let server_addr = run_server().await?; 65 | let url = format!("ws://{}", server_addr); 66 | 67 | let client = WsClientBuilder::default().build(&url).await?; 68 | assert_eq!("ehheeheh".to_string(), client.request::("state_getKeys", rpc_params![]).await.unwrap()); 69 | 70 | Ok(()) 71 | } 72 | 73 | async fn run_server() -> anyhow::Result { 74 | let server = Server::builder().build("127.0.0.1:0").await?; 75 | 76 | let addr = server.local_addr()?; 77 | let handle = server.start(RpcServerImpl.into_rpc()); 78 | 79 | // In this example we don't care about doing shutdown so let's it run forever. 80 | // You may use the `ServerHandle` to shut it down or manage it yourself. 81 | tokio::spawn(handle.stopped()); 82 | 83 | Ok(addr) 84 | } 85 | -------------------------------------------------------------------------------- /examples/examples/rpc_middleware_modify_request.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use jsonrpsee::core::client::ClientT; 28 | use jsonrpsee::core::middleware::{Batch, BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; 29 | use jsonrpsee::server::Server; 30 | use jsonrpsee::types::Request; 31 | use jsonrpsee::ws_client::WsClientBuilder; 32 | use jsonrpsee::{RpcModule, rpc_params}; 33 | use std::borrow::Cow as StdCow; 34 | use std::net::SocketAddr; 35 | 36 | fn modify_method_call(req: &mut Request<'_>) { 37 | // Example how to modify the params in the call. 38 | if req.method == "say_hello" { 39 | // It's a bit awkward to create new params in the request 40 | // but this shows how to do it. 41 | let raw_value = serde_json::value::to_raw_value("myparams").unwrap(); 42 | req.params = Some(StdCow::Owned(raw_value)); 43 | } 44 | // Re-direct all calls that isn't `say_hello` to `say_goodbye` 45 | else if req.method != "say_hello" { 46 | req.method = "say_goodbye".into(); 47 | } 48 | } 49 | 50 | fn modify_notif(n: &mut Notification<'_>) { 51 | // Example how to modify the params in the notification. 52 | if n.method == "say_hello" { 53 | // It's a bit awkward to create new params in the request 54 | // but this shows how to do it. 55 | let raw_value = serde_json::value::to_raw_value("myparams").unwrap(); 56 | n.params = Some(StdCow::Owned(raw_value)); 57 | } 58 | // Re-direct all notifs that isn't `say_hello` to `say_goodbye` 59 | else if n.method != "say_hello" { 60 | n.method = "say_goodbye".into(); 61 | } 62 | } 63 | 64 | #[derive(Clone)] 65 | pub struct ModifyRequestIf(S); 66 | 67 | impl RpcServiceT for ModifyRequestIf 68 | where 69 | S: RpcServiceT + Send + Sync + Clone + 'static, 70 | { 71 | type MethodResponse = S::MethodResponse; 72 | type NotificationResponse = S::NotificationResponse; 73 | type BatchResponse = S::BatchResponse; 74 | 75 | fn call<'a>(&self, mut req: Request<'a>) -> impl Future + Send + 'a { 76 | modify_method_call(&mut req); 77 | self.0.call(req) 78 | } 79 | 80 | fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future + Send + 'a { 81 | for call in batch.iter_mut() { 82 | match call { 83 | Ok(BatchEntry::Call(call)) => { 84 | modify_method_call(call); 85 | } 86 | Ok(BatchEntry::Notification(n)) => { 87 | modify_notif(n); 88 | } 89 | // Invalid request, we don't care about it. 90 | Err(_err) => {} 91 | } 92 | } 93 | 94 | self.0.batch(batch) 95 | } 96 | 97 | fn notification<'a>( 98 | &self, 99 | mut n: Notification<'a>, 100 | ) -> impl Future + Send + 'a { 101 | modify_notif(&mut n); 102 | self.0.notification(n) 103 | } 104 | } 105 | 106 | #[tokio::main] 107 | async fn main() -> anyhow::Result<()> { 108 | tracing_subscriber::FmtSubscriber::builder() 109 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 110 | .try_init() 111 | .expect("setting default subscriber failed"); 112 | 113 | let addr = run_server().await?; 114 | let url = format!("ws://{}", addr); 115 | 116 | let client = WsClientBuilder::default().build(&url).await?; 117 | let _response: String = client.request("say_hello", rpc_params![]).await?; 118 | let _response: Result = client.request("unknown_method", rpc_params![]).await; 119 | let _: String = client.request("say_hello", rpc_params![]).await?; 120 | 121 | Ok(()) 122 | } 123 | 124 | async fn run_server() -> anyhow::Result { 125 | let rpc_middleware = RpcServiceBuilder::new().layer_fn(ModifyRequestIf); 126 | let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; 127 | let mut module = RpcModule::new(()); 128 | module.register_method("say_hello", |_, _, _| "lo")?; 129 | module.register_method("say_goodbye", |_, _, _| "goodbye")?; 130 | let addr = server.local_addr()?; 131 | 132 | let handle = server.start(module); 133 | 134 | // In this example we don't care about doing shutdown so let's it run forever. 135 | // You may use the `ServerHandle` to shut it down or manage it yourself. 136 | tokio::spawn(handle.stopped()); 137 | 138 | Ok(addr) 139 | } 140 | -------------------------------------------------------------------------------- /examples/examples/tokio_console.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Example how to use `tokio-console` to debug async tasks `jsonrpsee`. 28 | //! For further information see https://docs.rs/console-subscriber. 29 | //! 30 | //! To run it: 31 | //! `$ cargo install --locked tokio-console` 32 | //! `$ RUSTFLAGS="--cfg tokio_unstable" cargo run --example tokio_console` 33 | //! `$ tokio-console` 34 | //! 35 | //! It will start a server on http://127.0.0.1:6669 for `tokio-console` to connect to. 36 | 37 | use std::net::SocketAddr; 38 | 39 | use jsonrpsee::RpcModule; 40 | use jsonrpsee::server::Server; 41 | 42 | #[tokio::main] 43 | async fn main() -> anyhow::Result<()> { 44 | console_subscriber::init(); 45 | 46 | let _ = run_server().await?; 47 | 48 | futures::future::pending().await 49 | } 50 | 51 | async fn run_server() -> anyhow::Result { 52 | let server = Server::builder().build("127.0.0.1:9944").await?; 53 | let mut module = RpcModule::new(()); 54 | module.register_method("say_hello", |_, _, _| "lo")?; 55 | module.register_method("memory_call", |_, _, _| "A".repeat(1024 * 1024))?; 56 | module.register_async_method("sleep", |_, _, _| async { 57 | tokio::time::sleep(std::time::Duration::from_millis(100)).await; 58 | "lo" 59 | })?; 60 | 61 | let addr = server.local_addr()?; 62 | let handle = server.start(module); 63 | 64 | // In this example we don't care about doing a stopping the server so let it run forever. 65 | // You may use the `ServerHandle` to shut it down or manage it yourself. 66 | tokio::spawn(handle.stopped()); 67 | 68 | Ok(addr) 69 | } 70 | -------------------------------------------------------------------------------- /examples/examples/ws.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use std::net::SocketAddr; 28 | 29 | use jsonrpsee::core::client::ClientT; 30 | use jsonrpsee::core::middleware::RpcServiceBuilder; 31 | use jsonrpsee::server::Server; 32 | use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; 33 | use jsonrpsee::{RpcModule, rpc_params}; 34 | use tracing_subscriber::util::SubscriberInitExt; 35 | 36 | #[tokio::main] 37 | async fn main() -> anyhow::Result<()> { 38 | let filter = tracing_subscriber::EnvFilter::try_from_default_env()? 39 | .add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?); 40 | 41 | tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?; 42 | 43 | let addr = run_server().await?; 44 | let url = format!("ws://{}", addr); 45 | 46 | let client: WsClient = WsClientBuilder::new().build(&url).await?; 47 | let response: String = client.request("say_hello", rpc_params![]).await?; 48 | tracing::info!("response: {:?}", response); 49 | 50 | Ok(()) 51 | } 52 | 53 | async fn run_server() -> anyhow::Result { 54 | let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024); 55 | let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; 56 | let mut module = RpcModule::new(()); 57 | module.register_method("say_hello", |_, _, _| "lo")?; 58 | let addr = server.local_addr()?; 59 | 60 | let handle = server.start(module); 61 | 62 | // In this example we don't care about doing shutdown so let's it run forever. 63 | // You may use the `ServerHandle` to shut it down or manage it yourself. 64 | tokio::spawn(handle.stopped()); 65 | 66 | Ok(addr) 67 | } 68 | -------------------------------------------------------------------------------- /examples/examples/ws_dual_stack.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | use jsonrpsee::core::client::ClientT; 28 | use jsonrpsee::server::{ServerHandle, serve_with_graceful_shutdown, stop_channel}; 29 | use jsonrpsee::ws_client::WsClientBuilder; 30 | use jsonrpsee::{RpcModule, rpc_params}; 31 | use std::net::SocketAddr; 32 | use tokio::net::TcpListener; 33 | use tracing_subscriber::util::SubscriberInitExt; 34 | 35 | #[tokio::main] 36 | async fn main() -> anyhow::Result<()> { 37 | let filter = tracing_subscriber::EnvFilter::try_from_default_env()? 38 | .add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?) 39 | .add_directive("jsonrpsee-client=trace".parse()?); 40 | 41 | tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?; 42 | 43 | let (_server_hdl, addrs) = run_server().await?; 44 | let url_v4 = format!("ws://{}", addrs.v4); 45 | let url_v6 = format!("ws://{}", addrs.v6); 46 | 47 | let client_v4 = WsClientBuilder::default().build(&url_v4).await?; 48 | let client_v6 = WsClientBuilder::default().build(&url_v6).await?; 49 | 50 | let response_v4: String = client_v4.request("say_hello", rpc_params![]).await?; 51 | let response_v6: String = client_v6.request("say_hello", rpc_params![]).await?; 52 | 53 | tracing::info!("response V4: {:?}", response_v4); 54 | tracing::info!("response V6: {:?}", response_v6); 55 | 56 | Ok(()) 57 | } 58 | 59 | async fn run_server() -> anyhow::Result<(ServerHandle, Addrs)> { 60 | let port = 9944; 61 | // V4 address 62 | let v4_addr = SocketAddr::from(([127, 0, 0, 1], port)); 63 | // V6 address 64 | let v6_addr = SocketAddr::new("::1".parse().unwrap(), port); 65 | 66 | let mut module = RpcModule::new(()); 67 | module.register_method("say_hello", |_, _, _| "lo")?; 68 | 69 | // Bind to both IPv4 and IPv6 addresses. 70 | let listener_v4 = TcpListener::bind(&v4_addr).await?; 71 | let listener_v6 = TcpListener::bind(&v6_addr).await?; 72 | 73 | // Each RPC call/connection get its own `stop_handle` 74 | // to able to determine whether the server has been stopped or not. 75 | // 76 | // To keep the server running the `server_handle` 77 | // must be kept and it can also be used to stop the server. 78 | let (stop_hdl, server_hdl) = stop_channel(); 79 | 80 | // Create and finalize a server configuration from a TowerServiceBuilder 81 | // given an RpcModule and the stop handle. 82 | let svc = jsonrpsee::server::Server::builder().to_service_builder().build(module, stop_hdl.clone()); 83 | 84 | tokio::spawn(async move { 85 | loop { 86 | // The `tokio::select!` macro is used to wait for either of the 87 | // listeners to accept a new connection or for the server to be 88 | // stopped. 89 | let stream = tokio::select! { 90 | res = listener_v4.accept() => { 91 | match res { 92 | Ok((stream, _remote_addr)) => stream, 93 | Err(e) => { 94 | tracing::error!("failed to accept v4 connection: {:?}", e); 95 | continue; 96 | } 97 | } 98 | } 99 | res = listener_v6.accept() => { 100 | match res { 101 | Ok((stream, _remote_addr)) => stream, 102 | Err(e) => { 103 | tracing::error!("failed to accept v6 connection: {:?}", e); 104 | continue; 105 | } 106 | } 107 | } 108 | _ = stop_hdl.clone().shutdown() => break, 109 | }; 110 | 111 | // Spawn a new task to serve each respective (Hyper) connection. 112 | tokio::spawn(serve_with_graceful_shutdown(stream, svc.clone(), stop_hdl.clone().shutdown())); 113 | } 114 | }); 115 | 116 | Ok((server_hdl, Addrs { v4: v4_addr, v6: v6_addr })) 117 | } 118 | 119 | struct Addrs { 120 | v4: SocketAddr, 121 | v6: SocketAddr, 122 | } 123 | -------------------------------------------------------------------------------- /jsonrpsee/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee" 3 | description = "JSON-RPC client/server framework" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | # No support for namespaced features yet so workspace dependencies are prefixed with `jsonrpsee-`. 21 | # See https://github.com/rust-lang/cargo/issues/5565 for more details. 22 | jsonrpsee-http-client = { workspace = true, optional = true } 23 | jsonrpsee-ws-client = { workspace = true, optional = true } 24 | jsonrpsee-wasm-client = { workspace = true, optional = true } 25 | jsonrpsee-client-transport = { workspace = true, optional = true } 26 | jsonrpsee-server = { workspace = true, optional = true } 27 | jsonrpsee-proc-macros = { workspace = true, optional = true } 28 | jsonrpsee-core = { workspace = true, optional = true } 29 | jsonrpsee-types = { workspace = true, optional = true } 30 | tracing = { workspace = true, optional = true } 31 | tokio = { workspace = true, optional = true } 32 | 33 | [features] 34 | client-ws-transport-tls = ["jsonrpsee-client-transport/ws", "jsonrpsee-client-transport/tls-rustls-platform-verifier"] 35 | client-ws-transport-no-tls = ["jsonrpsee-client-transport/ws"] 36 | client-web-transport = ["jsonrpsee-client-transport/web"] 37 | async-client = ["jsonrpsee-core/async-client"] 38 | async-wasm-client = ["jsonrpsee-core/async-wasm-client"] 39 | http-client = ["jsonrpsee-http-client", "jsonrpsee-types", "jsonrpsee-core/client"] 40 | wasm-client = ["jsonrpsee-wasm-client", "jsonrpsee-types", "jsonrpsee-core/client"] 41 | ws-client = ["jsonrpsee-ws-client", "jsonrpsee-types", "jsonrpsee-core/client"] 42 | macros = ["jsonrpsee-proc-macros", "jsonrpsee-types", "tracing"] 43 | 44 | client = ["http-client", "ws-client", "wasm-client", "client-ws-transport-tls", "client-web-transport", "async-client", "async-wasm-client", "client-core"] 45 | client-core = ["jsonrpsee-core/client"] 46 | server = ["jsonrpsee-server", "server-core", "jsonrpsee-types", "tokio"] 47 | server-core = ["jsonrpsee-core/server"] 48 | full = ["client", "server", "macros"] 49 | 50 | [package.metadata.docs.rs] 51 | all-features = true 52 | rustdoc-args = ["--cfg", "docsrs"] 53 | 54 | [package.metadata.playground] 55 | all-features = true 56 | -------------------------------------------------------------------------------- /jsonrpsee/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! jsonrpsee wrapper crate. 28 | //! 29 | //!
30 | //! 31 | //! # Optional features 32 | //! 33 | //! The `jsonrpsee` crate composes JSON-RPC functionality behind optional feature 34 | //! flags to provide for client and server communication over specific protocols. 35 | //! There are no default features, all functionality must be opted in to accordingly. 36 | //! The following features are available. 37 | //! 38 | //! - **`http-client`** - JSON-RPC client functionality over HTTP protocol. 39 | //! - **`wasm-client`** - JSON-RPC client functionality over web-sys. 40 | //! - **`ws-client`** - JSON-RPC client functionality over WebSocket protocol. 41 | //! - **`macros`** - JSON-RPC API generation convenience by derive macros. 42 | //! - **`client-core`** - Enables minimal client features to generate the RPC API without transports. 43 | //! - **`client`** - Enables all client features including transports. 44 | //! - **`server-core`** - Enables minimal server features to generate the RPC API without transports. 45 | //! - **`server`** - Enables all server features including transports. 46 | //! - **`full`** - Enables all features. 47 | //! - **`async-client`** - Enables the async client without any transport. 48 | //! - **`client-ws-transport`** - Enables `ws` transport with TLS. 49 | //! - **`client-ws-transport-no-tls`** - Enables `ws` transport without TLS. 50 | //! - **`client-web-transport`** - Enables `websys` transport. 51 | 52 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 53 | #![cfg_attr(docsrs, feature(doc_cfg))] 54 | 55 | // Macros useful below, but not to be exposed outside of the crate. 56 | #[macro_use] 57 | mod macros; 58 | 59 | cfg_http_client! { 60 | pub use jsonrpsee_http_client as http_client; 61 | } 62 | 63 | cfg_ws_client! { 64 | pub use jsonrpsee_ws_client as ws_client; 65 | } 66 | 67 | cfg_wasm_client! { 68 | pub use jsonrpsee_wasm_client as wasm_client; 69 | } 70 | 71 | cfg_async_client! { 72 | pub use jsonrpsee_core::client::async_client; 73 | } 74 | 75 | cfg_client_transport! { 76 | pub use jsonrpsee_client_transport as client_transport; 77 | } 78 | 79 | cfg_server! { 80 | pub use jsonrpsee_server as server; 81 | pub use tokio; 82 | } 83 | 84 | cfg_server_core! { 85 | pub use jsonrpsee_core::server::*; 86 | } 87 | 88 | cfg_proc_macros! { 89 | pub use jsonrpsee_proc_macros as proc_macros; 90 | pub use tracing; 91 | } 92 | 93 | cfg_types! { 94 | pub use jsonrpsee_types as types; 95 | } 96 | 97 | cfg_client_or_server! { 98 | pub use jsonrpsee_core as core; 99 | } 100 | 101 | cfg_client! { 102 | pub use jsonrpsee_core::rpc_params; 103 | } 104 | -------------------------------------------------------------------------------- /jsonrpsee/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! cfg_feature { 2 | ($feature:literal, $($item:item)*) => { 3 | $( 4 | #[cfg(feature = $feature)] 5 | #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] 6 | $item 7 | )* 8 | } 9 | } 10 | 11 | macro_rules! cfg_client { 12 | ($($item:item)*) => { 13 | $( 14 | #[cfg(any( 15 | feature = "jsonrpsee-http-client", feature = "jsonrpsee-wasm-client", feature = "jsonrpsee-ws-client", 16 | feature = "client", feature = "async-client", feature = "client-core", feature = "async-wasm-client" 17 | ))] 18 | $item 19 | )* 20 | } 21 | } 22 | 23 | macro_rules! cfg_http_client { 24 | ($($item:item)*) => { 25 | cfg_feature!("jsonrpsee-http-client", $($item)*); 26 | }; 27 | } 28 | 29 | macro_rules! cfg_ws_client { 30 | ($($item:item)*) => { 31 | cfg_feature!("jsonrpsee-ws-client", $($item)*); 32 | }; 33 | } 34 | 35 | macro_rules! cfg_wasm_client { 36 | ($($item:item)*) => { 37 | cfg_feature!("jsonrpsee-wasm-client", $($item)*); 38 | }; 39 | } 40 | 41 | macro_rules! cfg_async_client { 42 | ($($item:item)*) => { 43 | cfg_feature!("async-client", $($item)*); 44 | }; 45 | } 46 | 47 | macro_rules! cfg_client_transport { 48 | ($($item:item)*) => { 49 | cfg_feature!("jsonrpsee-client-transport", $($item)*); 50 | }; 51 | } 52 | 53 | macro_rules! cfg_server { 54 | ($($item:item)*) => { 55 | cfg_feature!("server", $($item)*); 56 | } 57 | } 58 | 59 | macro_rules! cfg_server_core { 60 | ($($item:item)*) => { 61 | cfg_feature!("server-core", $($item)*); 62 | } 63 | } 64 | 65 | macro_rules! cfg_proc_macros { 66 | ($($item:item)*) => { 67 | cfg_feature!("jsonrpsee-proc-macros", $($item)*); 68 | }; 69 | } 70 | 71 | macro_rules! cfg_types { 72 | ($($item:item)*) => { 73 | cfg_feature!("jsonrpsee-types", $($item)*); 74 | }; 75 | } 76 | 77 | macro_rules! cfg_client_or_server { 78 | ($($item:item)*) => { 79 | $( 80 | #[cfg(any( 81 | feature = "jsonrpsee-http-client", feature = "jsonrpsee-wasm-client", feature = "jsonrpsee-ws-client", 82 | feature = "client", feature = "async-client", feature = "async-wasm-client", 83 | feature = "client-core", feature = "server", feature = "server-core" 84 | ))] 85 | $item 86 | )* 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /proc-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-proc-macros" 3 | description = "Procedueral macros for jsonrpsee" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [lib] 20 | proc-macro = true 21 | 22 | [dependencies] 23 | proc-macro2 = { workspace = true } 24 | quote = { workspace = true } 25 | syn = { workspace = true, features = ["extra-traits", "full", "visit", "parsing", "printing", "clone-impls", "proc-macro"] } 26 | proc-macro-crate = { workspace = true } 27 | heck = { workspace = true } 28 | 29 | [dev-dependencies] 30 | jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } 31 | hyper = { workspace = true } 32 | hyper-util = { workspace = true, features = ["client", "client-legacy"]} 33 | futures-channel = { workspace = true } 34 | futures-util = { workspace = true } 35 | serde_json = { workspace = true } 36 | serde = { workspace = true } 37 | trybuild = { workspace = true } 38 | tokio = { workspace = true, features = ["rt", "macros"] } 39 | tower = { workspace = true } 40 | -------------------------------------------------------------------------------- /proc-macros/tests/ui.rs: -------------------------------------------------------------------------------- 1 | //! UI test set uses [`trybuild`](https://docs.rs/trybuild/1.0.42/trybuild/) to 2 | //! check whether expected valid examples of code compile correctly, and for incorrect ones 3 | //! errors are helpful and valid (e.g. have correct spans). 4 | //! 5 | //! Use with `TRYBUILD=overwrite` after updating codebase (see `trybuild` docs for more details on that) 6 | //! to automatically regenerate `stderr` files, but don't forget to check that new files make sense. 7 | 8 | #[test] 9 | fn ui_pass() { 10 | let t = trybuild::TestCases::new(); 11 | t.pass("tests/ui/correct/*.rs"); 12 | } 13 | 14 | #[test] 15 | fn ui_fail() { 16 | let t = trybuild::TestCases::new(); 17 | t.compile_fail("tests/ui/incorrect/**/*.rs"); 18 | } 19 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::{core::RpcResult, proc_macros::rpc}; 2 | 3 | #[rpc(client, server, namespace = "myapi")] 4 | pub trait Rpc { 5 | /// Aliases don't use the namespace. 6 | /// Thus, this will generate `myapi_getTemp` and `getTemp`. 7 | #[method(name = "getTemp", aliases = ["getTemp"])] 8 | async fn async_method(&self, param_a: u8, param_b: String) -> RpcResult; 9 | 10 | #[subscription(name = "subscribeGetFood", item = String, aliases = ["getFood"], unsubscribe_aliases = ["unsubscribegetFood"])] 11 | async fn sub(&self); 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/custom_ret_types.rs: -------------------------------------------------------------------------------- 1 | //! Example of using custom response type. 2 | 3 | use std::net::SocketAddr; 4 | 5 | use jsonrpsee::core::{async_trait, ClientError, Serialize}; 6 | use jsonrpsee::proc_macros::rpc; 7 | use jsonrpsee::server::{IntoResponse, ResponsePayload, ServerBuilder}; 8 | use jsonrpsee::ws_client::*; 9 | 10 | // Serialize impl is not used as the responses are sent out as error. 11 | #[derive(Serialize, Clone)] 12 | pub enum CustomError { 13 | One, 14 | Two { custom_data: u32 }, 15 | } 16 | 17 | impl IntoResponse for CustomError { 18 | type Output = Self; 19 | 20 | fn into_response(self) -> ResponsePayload<'static, Self::Output> { 21 | let code = match &self { 22 | CustomError::One => 101, 23 | CustomError::Two { .. } => 102, 24 | }; 25 | let data = match &self { 26 | CustomError::One => None, 27 | CustomError::Two { custom_data } => Some(serde_json::json!({ "customData": custom_data })), 28 | }; 29 | 30 | let data = data.map(|val| serde_json::value::to_raw_value(&val).unwrap()); 31 | 32 | let error_object = jsonrpsee::types::ErrorObjectOwned::owned(code, "custom_error", data); 33 | ResponsePayload::error(error_object) 34 | } 35 | } 36 | 37 | #[rpc(server, namespace = "foo")] 38 | pub trait Rpc { 39 | #[method(name = "async_method1")] 40 | async fn async_method1(&self) -> CustomError; 41 | 42 | #[method(name = "async_method2")] 43 | async fn async_method2(&self, x: u32) -> CustomError; 44 | 45 | #[method(name = "sync_method1")] 46 | fn method1(&self) -> CustomError; 47 | 48 | #[method(name = "sync_method2")] 49 | fn method2(&self, x: u32) -> CustomError; 50 | 51 | #[method(name = "blocking_method1", blocking)] 52 | fn blocking_method1(&self) -> CustomError; 53 | 54 | #[method(name = "blocking_method2", blocking)] 55 | fn blocking_method2(&self, x: u32) -> CustomError; 56 | } 57 | 58 | pub struct RpcServerImpl; 59 | 60 | #[async_trait] 61 | impl RpcServer for RpcServerImpl { 62 | async fn async_method1(&self) -> CustomError { 63 | CustomError::One 64 | } 65 | 66 | async fn async_method2(&self, x: u32) -> CustomError { 67 | CustomError::Two { custom_data: x } 68 | } 69 | 70 | fn method1(&self) -> CustomError { 71 | CustomError::One 72 | } 73 | 74 | fn method2(&self, x: u32) -> CustomError { 75 | CustomError::Two { custom_data: x } 76 | } 77 | 78 | fn blocking_method1(&self) -> CustomError { 79 | CustomError::One 80 | } 81 | 82 | fn blocking_method2(&self, x: u32) -> CustomError { 83 | CustomError::Two { custom_data: x } 84 | } 85 | } 86 | 87 | // TODO: https://github.com/paritytech/jsonrpsee/issues/1067 88 | // 89 | // The client accepts only return types that are `Result`. 90 | #[rpc(client, namespace = "foo")] 91 | pub trait RpcClient { 92 | #[method(name = "async_method1")] 93 | async fn async_method1(&self) -> RpcResult; 94 | 95 | #[method(name = "async_method2")] 96 | async fn async_method2(&self, x: u32) -> Result; 97 | 98 | #[method(name = "sync_method1")] 99 | async fn sync_method1(&self) -> RpcResult; 100 | 101 | #[method(name = "sync_method2")] 102 | async fn sync_method2(&self, x: u32) -> Result; 103 | 104 | #[method(name = "blocking_method1")] 105 | async fn blocking_method1(&self) -> RpcResult; 106 | 107 | #[method(name = "blocking_method2")] 108 | async fn blocking_method2(&self, x: u32) -> Result; 109 | } 110 | 111 | pub async fn server() -> SocketAddr { 112 | let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); 113 | let addr = server.local_addr().unwrap(); 114 | let server_handle = server.start(RpcServerImpl.into_rpc()); 115 | 116 | tokio::spawn(server_handle.stopped()); 117 | 118 | addr 119 | } 120 | 121 | #[tokio::main] 122 | async fn main() { 123 | let server_addr = server().await; 124 | let server_url = format!("ws://{}", server_addr); 125 | let client = WsClientBuilder::default().build(&server_url).await.unwrap(); 126 | 127 | let error = client.async_method1().await.unwrap_err(); 128 | assert_method1(error); 129 | 130 | let error = client.async_method2(123).await.unwrap_err(); 131 | assert_method2(error); 132 | 133 | let error = client.sync_method1().await.unwrap_err(); 134 | assert_method1(error); 135 | 136 | let error = client.sync_method2(123).await.unwrap_err(); 137 | assert_method2(error); 138 | 139 | let error = client.blocking_method1().await.unwrap_err(); 140 | assert_method1(error); 141 | 142 | let error = client.blocking_method2(123).await.unwrap_err(); 143 | assert_method2(error); 144 | } 145 | 146 | fn assert_method1(error: ClientError) { 147 | let get_error_object = |err| match err { 148 | ClientError::Call(object) => object, 149 | _ => panic!("wrong error kind: {:?}", err), 150 | }; 151 | 152 | let error_object = get_error_object(error); 153 | assert_eq!(error_object.code(), 101); 154 | assert_eq!(error_object.message(), "custom_error"); 155 | assert!(error_object.data().is_none()); 156 | } 157 | 158 | fn assert_method2(error: ClientError) { 159 | let get_error_object = |err| match err { 160 | ClientError::Call(object) => object, 161 | _ => panic!("wrong error kind: {:?}", err), 162 | }; 163 | 164 | let error_object = get_error_object(error); 165 | assert_eq!(error_object.code(), 102); 166 | assert_eq!(error_object.message(), "custom_error"); 167 | assert_eq!(error_object.data().unwrap().get(), r#"{"customData":123}"#); 168 | } 169 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/only_client.rs: -------------------------------------------------------------------------------- 1 | //! Example of using proc macro to generate working client. 2 | 3 | use jsonrpsee::proc_macros::rpc; 4 | 5 | #[rpc(client)] 6 | pub trait Rpc { 7 | #[method(name = "foo")] 8 | async fn async_method(&self, param_a: u8, param_b: String) -> Result; 9 | 10 | #[method(name = "bar")] 11 | fn sync_method(&self) -> Result; 12 | 13 | #[subscription(name = "subscribe", item = String)] 14 | async fn sub(&self) -> Result<(), Error>; 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/only_server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use jsonrpsee::core::{RpcResult, SubscriptionResult, async_trait, to_json_raw_value}; 4 | use jsonrpsee::proc_macros::rpc; 5 | use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; 6 | 7 | #[rpc(server)] 8 | pub trait Rpc { 9 | #[method(name = "foo")] 10 | async fn async_method(&self, param_a: u8, param_b: String) -> RpcResult; 11 | 12 | #[method(name = "bar")] 13 | fn sync_method(&self) -> RpcResult; 14 | 15 | #[subscription(name = "subscribe", item = String)] 16 | async fn sub(&self) -> SubscriptionResult; 17 | } 18 | 19 | pub struct RpcServerImpl; 20 | 21 | #[async_trait] 22 | impl RpcServer for RpcServerImpl { 23 | async fn async_method(&self, _param_a: u8, _param_b: String) -> RpcResult { 24 | Ok(42u16) 25 | } 26 | 27 | fn sync_method(&self) -> RpcResult { 28 | Ok(10u16) 29 | } 30 | 31 | async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { 32 | let sink = pending.accept().await?; 33 | 34 | let msg1 = to_json_raw_value(&"Response_A").unwrap(); 35 | let msg2 = to_json_raw_value(&"Response_B").unwrap(); 36 | sink.send(msg1).await?; 37 | sink.send(msg2).await?; 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | pub async fn server() -> SocketAddr { 44 | let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); 45 | let addr = server.local_addr().unwrap(); 46 | let server_handle = server.start(RpcServerImpl.into_rpc()); 47 | 48 | tokio::spawn(server_handle.stopped()); 49 | addr 50 | } 51 | 52 | #[tokio::main] 53 | async fn main() { 54 | let _server_addr = server().await; 55 | } 56 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/param_kind.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use jsonrpsee::core::{async_trait, RpcResult}; 4 | use jsonrpsee::proc_macros::rpc; 5 | use jsonrpsee::server::ServerBuilder; 6 | use jsonrpsee::ws_client::*; 7 | 8 | #[rpc(client, server, namespace = "foo")] 9 | pub trait Rpc { 10 | #[method(name = "method_with_array_param", param_kind = array)] 11 | async fn method_with_array_param(&self, param_a: u8, param_b: String) -> RpcResult; 12 | 13 | #[method(name="method_with_map_param", param_kind= map)] 14 | async fn method_with_map_param(&self, param_a: u8, param_b: String) -> RpcResult; 15 | 16 | #[method(name = "method_with_default_param")] 17 | async fn method_with_default_param(&self, param_a: u8, param_b: String) -> RpcResult; 18 | } 19 | 20 | pub struct RpcServerImpl; 21 | 22 | #[async_trait] 23 | impl RpcServer for RpcServerImpl { 24 | async fn method_with_array_param(&self, param_a: u8, param_b: String) -> RpcResult { 25 | assert_eq!(param_a, 0); 26 | assert_eq!(¶m_b, "a"); 27 | Ok(42u16) 28 | } 29 | 30 | async fn method_with_map_param(&self, param_a: u8, param_b: String) -> RpcResult { 31 | assert_eq!(param_a, 0); 32 | assert_eq!(¶m_b, "a"); 33 | Ok(42u16) 34 | } 35 | 36 | async fn method_with_default_param(&self, param_a: u8, param_b: String) -> RpcResult { 37 | assert_eq!(param_a, 0); 38 | assert_eq!(¶m_b, "a"); 39 | Ok(42u16) 40 | } 41 | } 42 | 43 | pub async fn server() -> SocketAddr { 44 | let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); 45 | let addr = server.local_addr().unwrap(); 46 | let server_handle = server.start(RpcServerImpl.into_rpc()); 47 | 48 | tokio::spawn(server_handle.stopped()); 49 | 50 | addr 51 | } 52 | 53 | #[tokio::main] 54 | async fn main() { 55 | let server_addr = server().await; 56 | let server_url = format!("ws://{}", server_addr); 57 | let client = WsClientBuilder::default().build(&server_url).await.unwrap(); 58 | 59 | assert_eq!(client.method_with_array_param(0, "a".into()).await.unwrap(), 42); 60 | assert_eq!(client.method_with_map_param(0, "a".into()).await.unwrap(), 42); 61 | assert_eq!(client.method_with_default_param(0, "a".into()).await.unwrap(), 42); 62 | } 63 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/parse_angle_brackets.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | fn main() { 4 | #[rpc(server)] 5 | pub trait Rpc { 6 | #[subscription( 7 | name = "submitAndWatchExtrinsic", 8 | unsubscribe = "author_unwatchExtrinsic", 9 | aliases = ["author_extrinsicUpdate"], 10 | unsubscribe_aliases = ["author_unwatchExtrinsic2"], 11 | // Arguments are being parsed the nearest comma, 12 | // angle braces need to be accounted for manually. 13 | item = TransactionStatus, 14 | )] 15 | async fn dummy_subscription(&self); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs: -------------------------------------------------------------------------------- 1 | //! Test to check that the proc macros actually generates documentation. 2 | 3 | #![deny(missing_docs)] 4 | 5 | use jsonrpsee::core::RpcResult; 6 | use jsonrpsee::proc_macros::rpc; 7 | 8 | #[rpc(client, server)] 9 | pub trait ApiWithDocumentation { 10 | /// Async method. 11 | #[method(name = "foo")] 12 | async fn async_method(&self) -> RpcResult; 13 | 14 | /// Subscription docs. 15 | #[subscription(name = "sub", unsubscribe = "unsub", item = String)] 16 | async fn sub(&self); 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/correct/server_with_raw_methods.rs: -------------------------------------------------------------------------------- 1 | //! Example of using proc macro to generate working client. 2 | 3 | use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObjectOwned}; 4 | 5 | #[rpc(server)] 6 | pub trait Rpc { 7 | #[method(name = "foo")] 8 | async fn async_method(&self, param_a: u8, param_b: String) -> RpcResult; 9 | 10 | #[method(name = "bar")] 11 | fn sync_method(&self) -> Result; 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_ignored_arguments.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | #[rpc(server)] 4 | pub trait IgnoredArgument { 5 | #[method(name = "a")] 6 | async fn a(&self, _: Vec); 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_ignored_arguments.stderr: -------------------------------------------------------------------------------- 1 | error: Method argument names must be valid Rust identifiers; got `_` instead 2 | --> $DIR/method_ignored_arguments.rs:6:20 3 | | 4 | 6 | async fn a(&self, _: Vec); 5 | | ^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_no_name.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Missing mandatory `name` field. 4 | #[rpc(client, server)] 5 | pub trait NoMethodName { 6 | #[method()] 7 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_no_name.stderr: -------------------------------------------------------------------------------- 1 | error: Missing argument `name` 2 | --> $DIR/method_no_name.rs:6:4 3 | | 4 | 6 | #[method()] 5 | | ^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_non_result_return_type.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | #[rpc(client)] 4 | pub trait NonResultReturnType { 5 | #[method(name = "a")] 6 | async fn a(&self) -> u16; 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_non_result_return_type.stderr: -------------------------------------------------------------------------------- 1 | error: Expecting something like 'Result' here, but got no generic args (eg no ''). 2 | --> tests/ui/incorrect/method/method_non_result_return_type.rs:6:23 3 | | 4 | 6 | async fn a(&self) -> u16; 5 | | ^^^ 6 | 7 | error[E0271]: expected `impl Future> + Send` to be a future that resolves to `()`, but it resolves to `Result<_, ClientError>` 8 | --> tests/ui/incorrect/method/method_non_result_return_type.rs:3:1 9 | | 10 | 3 | #[rpc(client)] 11 | | ^^^^^^^^^^^^^^ expected `()`, found `Result<_, ClientError>` 12 | | 13 | = note: expected unit type `()` 14 | found enum `Result<_, ClientError>` 15 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_unexpected_field.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Unsupported attribute field. 4 | #[rpc(client, server)] 5 | pub trait UnexpectedField { 6 | #[method(name = "foo", magic = false)] 7 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/method/method_unexpected_field.stderr: -------------------------------------------------------------------------------- 1 | error: Unknown argument `magic`, expected one of: `aliases`, `blocking`, `name`, `param_kind`, `with_extensions` 2 | --> tests/ui/incorrect/method/method_unexpected_field.rs:6:25 3 | | 4 | 6 | #[method(name = "foo", magic = false)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_assoc_items.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Associated items are forbidden. 4 | #[rpc(client, server)] 5 | pub trait AssociatedConst { 6 | const WOO: usize; 7 | 8 | #[method(name = "foo")] 9 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 10 | } 11 | 12 | #[rpc(client, server)] 13 | pub trait AssociatedType { 14 | type Woo; 15 | 16 | #[method(name = "foo")] 17 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_assoc_items.stderr: -------------------------------------------------------------------------------- 1 | error: Only methods allowed in RPC traits 2 | --> $DIR/rpc_assoc_items.rs:6:2 3 | | 4 | 6 | const WOO: usize; 5 | | ^^^^^^^^^^^^^^^^^ 6 | 7 | error: Only methods allowed in RPC traits 8 | --> $DIR/rpc_assoc_items.rs:14:2 9 | | 10 | 14 | type Woo; 11 | | ^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | pub trait Config { 4 | type Hash: Send + Sync + 'static; 5 | } 6 | 7 | #[rpc(server, client_bounds(), server_bounds(Conf::Hash: jsonrpsee::core::Serialize))] 8 | pub trait ClientBoundsForbidden { 9 | #[method(name = "bar")] 10 | fn method(&self) -> Result; 11 | } 12 | 13 | #[rpc(client, server_bounds(), client_bounds(Conf::Hash: jsonrpsee::core::DeserializeOwned))] 14 | pub trait ServerBoundsForbidden { 15 | #[method(name = "bar")] 16 | fn method(&self) -> Result; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_bounds_without_impl.stderr: -------------------------------------------------------------------------------- 1 | error: Attribute 'client' must be specified with 'client_bounds' 2 | --> tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs:8:11 3 | | 4 | 8 | pub trait ClientBoundsForbidden { 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | error: Attribute 'server' must be specified with 'server_bounds' 8 | --> tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs:14:11 9 | | 10 | 14 | pub trait ServerBoundsForbidden { 11 | | ^^^^^^^^^^^^^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_conflicting_alias.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | use jsonrpsee::core::RpcResult; 3 | #[rpc(client, server)] 4 | pub trait DuplicatedAlias { 5 | #[method(name = "foo", aliases = ["foo_dup", "foo_dup"])] 6 | async fn async_method(&self) -> RpcResult; 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_conflicting_alias.stderr: -------------------------------------------------------------------------------- 1 | error: "foo_dup" is already defined 2 | --> $DIR/rpc_conflicting_alias.rs:6:11 3 | | 4 | 6 | async fn async_method(&self) -> RpcResult; 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_deprecated_method.rs: -------------------------------------------------------------------------------- 1 | //! Test that calling a deprecated method will generate warnings at compile-time. 2 | 3 | // Treat warnings as errors to fail the build. 4 | #![deny(warnings)] 5 | 6 | use std::net::SocketAddr; 7 | 8 | use jsonrpsee::core::{async_trait, RpcResult}; 9 | use jsonrpsee::proc_macros::rpc; 10 | use jsonrpsee::server::ServerBuilder; 11 | use jsonrpsee::ws_client::*; 12 | 13 | #[rpc(client, server)] 14 | pub trait Deprecated { 15 | // Deprecated method that is called by the client. 16 | #[deprecated(since = "0.5.0", note = "please use `new_method` instead")] 17 | #[method(name = "foo")] 18 | async fn async_method(&self) -> RpcResult; 19 | 20 | // Deprecated methods that are not called should not generate warnings. 21 | #[deprecated(since = "0.5.0", note = "please use `new_method` instead")] 22 | #[method(name = "foo_unused")] 23 | async fn async_method_unused(&self) -> RpcResult; 24 | 25 | // If the method is not marked as deprecated, should not generate warnings. 26 | #[method(name = "bar")] 27 | fn sync_method(&self) -> RpcResult; 28 | } 29 | 30 | pub struct DeprecatedServerImpl; 31 | 32 | #[async_trait] 33 | impl DeprecatedServer for DeprecatedServerImpl { 34 | async fn async_method(&self) -> RpcResult { 35 | Ok(16u8) 36 | } 37 | 38 | async fn async_method_unused(&self) -> RpcResult { 39 | Ok(32u8) 40 | } 41 | 42 | fn sync_method(&self) -> RpcResult { 43 | Ok(64u8) 44 | } 45 | } 46 | 47 | pub async fn server() -> SocketAddr { 48 | let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); 49 | let addr = server.local_addr().unwrap(); 50 | 51 | server.start(DeprecatedServerImpl.into_rpc()); 52 | 53 | addr 54 | } 55 | 56 | #[tokio::main] 57 | async fn main() { 58 | let server_addr = server().await; 59 | let server_url = format!("ws://{}", server_addr); 60 | let client = WsClientBuilder::default().build(&server_url).await.unwrap(); 61 | 62 | // Calling this method should generate an warning. 63 | assert_eq!(client.async_method().await.unwrap(), 16); 64 | // Note: `async_method_unused` is not called, and should not generate warnings. 65 | assert_eq!(client.sync_method().await.unwrap(), 64); 66 | } 67 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_deprecated_method.stderr: -------------------------------------------------------------------------------- 1 | error: use of deprecated method `DeprecatedClient::async_method`: please use `new_method` instead 2 | --> $DIR/rpc_deprecated_method.rs:63:20 3 | | 4 | 63 | assert_eq!(client.async_method().await.unwrap(), 16); 5 | | ^^^^^^^^^^^^ 6 | | 7 | note: the lint level is defined here 8 | --> $DIR/rpc_deprecated_method.rs:4:9 9 | | 10 | 4 | #![deny(warnings)] 11 | | ^^^^^^^^ 12 | = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` 13 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_empty.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Empty RPC is forbidden. 4 | #[rpc(client, server)] 5 | pub trait Empty {} 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_empty.stderr: -------------------------------------------------------------------------------- 1 | error: RPC cannot be empty 2 | --> $DIR/rpc_empty.rs:5:1 3 | | 4 | 5 | pub trait Empty {} 5 | | ^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::{core::RpcResult, proc_macros::rpc}; 2 | 3 | pub trait Config { 4 | type Hash: Send + Sync + 'static; 5 | } 6 | 7 | /// Client bound must be `Conf::Hash: jsonrpsee::core::DeserializeOwned` 8 | /// Server bound must be `Conf::Hash: jsonrpsee::core::Serialize + Clone` 9 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] 10 | pub trait EmptyBounds { 11 | #[method(name = "bar")] 12 | fn method(&self) -> RpcResult; 13 | } 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `::Hash: DeserializeOwned` is not satisfied 2 | --> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:9:1 3 | | 4 | 9 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'de> Deserialize<'de>` is not implemented for `::Hash` 6 | | 7 | = note: required for `::Hash` to implement `DeserializeOwned` 8 | note: required by a bound in `request` 9 | --> $WORKSPACE/core/src/client/mod.rs 10 | | 11 | | fn request(&self, method: &str, params: Params) -> impl Future> + Send 12 | | ------- required by a bound in this associated function 13 | | where 14 | | R: DeserializeOwned, 15 | | ^^^^^^^^^^^^^^^^ required by this bound in `ClientT::request` 16 | = note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info) 17 | 18 | error[E0277]: the trait bound `::Hash: Serialize` is not satisfied 19 | --> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:9:1 20 | | 21 | 9 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] 22 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `::Hash` 23 | | 24 | = note: for local types consider adding `#[derive(serde::Serialize)]` to your `::Hash` type 25 | = note: for types from other crates check whether the crate offers a `serde` feature flag 26 | = note: required for `Result<::Hash, ErrorObject<'_>>` to implement `IntoResponse` 27 | = note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | 29 | error[E0277]: the trait bound `::Hash: Clone` is not satisfied 30 | --> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:9:1 31 | | 32 | 9 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `::Hash` 34 | | 35 | = note: required for `Result<::Hash, ErrorObject<'_>>` to implement `IntoResponse` 36 | = note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info) 37 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_name_conflict.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::{proc_macros::rpc, core::RpcResult}; 2 | 3 | // Names must be unique. 4 | #[rpc(client, server)] 5 | pub trait MethodNameConflict { 6 | #[method(name = "foo")] 7 | async fn foo(&self) -> RpcResult; 8 | 9 | #[method(name = "foo")] 10 | async fn bar(&self) -> RpcResult; 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_name_conflict.stderr: -------------------------------------------------------------------------------- 1 | error: "foo" is already defined 2 | --> $DIR/rpc_name_conflict.rs:10:11 3 | | 4 | 10 | async fn bar(&self) -> RpcResult; 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_no_impls.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Either client or server field must be provided. 4 | #[rpc()] 5 | pub trait NoImpls { 6 | #[method(name = "foo")] 7 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_no_impls.stderr: -------------------------------------------------------------------------------- 1 | error: Either 'server' or 'client' attribute must be applied 2 | --> $DIR/rpc_no_impls.rs:5:11 3 | | 4 | 5 | pub trait NoImpls { 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_not_qualified.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Method without type marker, `#[method(…)]` or `#[subscription(…)]`. 4 | #[rpc(client, server)] 5 | pub trait NotQualified { 6 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/rpc/rpc_not_qualified.stderr: -------------------------------------------------------------------------------- 1 | error: Methods must have either 'method' or 'subscription' attribute 2 | --> $DIR/rpc_not_qualified.rs:6:2 3 | | 4 | 6 | async fn async_method(&self) -> jsonrpsee::core::RpcResult; 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | #[rpc(client, server)] 4 | pub trait DuplicatedSubAlias { 5 | #[subscription(name = "subscribeAlias", item = String, aliases = ["hello_is_goodbye"], unsubscribe_aliases = ["hello_is_goodbye"])] 6 | async fn async_method(&self); 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr: -------------------------------------------------------------------------------- 1 | error: "hello_is_goodbye" is already defined 2 | --> $DIR/sub_conflicting_alias.rs:6:11 3 | | 4 | 6 | async fn async_method(&self); 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Subscription method must not use the same override name. 4 | #[rpc(client, server)] 5 | pub trait DupOverride { 6 | #[subscription(name = "subscribeOne" => "override", item = u8)] 7 | async fn one(&self); 8 | #[subscription(name = "subscribeTwo" => "override", item = u8)] 9 | async fn two(&self); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr: -------------------------------------------------------------------------------- 1 | error: "override" is already defined 2 | --> $DIR/sub_dup_name_override.rs:9:11 3 | | 4 | 9 | async fn two(&self); 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Missing all the mandatory fields. 4 | #[rpc(client, server)] 5 | pub trait SubEmptyAttr { 6 | #[subscription()] 7 | async fn sub(&self); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_empty_attr.stderr: -------------------------------------------------------------------------------- 1 | error: Missing argument `name` 2 | --> $DIR/sub_empty_attr.rs:6:4 3 | | 4 | 6 | #[subscription()] 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_name_override.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Subscription method name conflict with notif override. 4 | #[rpc(client, server)] 5 | pub trait DupName { 6 | #[subscription(name = "one" => "one", unsubscribe = "unsubscribeOne", item = u8)] 7 | async fn one(&self); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr: -------------------------------------------------------------------------------- 1 | error: "one" is already defined 2 | --> $DIR/sub_name_override.rs:7:11 3 | | 4 | 7 | async fn one(&self); 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_no_item.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Missing mandatory `item` field. 4 | #[rpc(client, server)] 5 | pub trait NoSubItem { 6 | #[subscription(name = "sub")] 7 | async fn sub(&self); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_no_item.stderr: -------------------------------------------------------------------------------- 1 | error: Missing argument `item` 2 | --> $DIR/sub_no_item.rs:6:4 3 | | 4 | 6 | #[subscription(name = "sub")] 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_no_name.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Missing mandatory `name` field. 4 | #[rpc(client, server)] 5 | pub trait NoSubName { 6 | #[subscription(item = String)] 7 | async fn async_method(&self); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_no_name.stderr: -------------------------------------------------------------------------------- 1 | error: Missing argument `name` 2 | --> $DIR/sub_no_name.rs:6:4 3 | | 4 | 6 | #[subscription(item = String)] 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs: -------------------------------------------------------------------------------- 1 | use jsonrpsee::proc_macros::rpc; 2 | 3 | // Unsupported attribute field. 4 | #[rpc(client, server)] 5 | pub trait UnsupportedField { 6 | #[subscription(name = "sub", unsubscribe = "unsub", item = u8, magic = true)] 7 | async fn sub(&self); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.stderr: -------------------------------------------------------------------------------- 1 | error: Unknown argument `magic`, expected one of: `aliases`, `item`, `name`, `param_kind`, `unsubscribe`, `unsubscribe_aliases`, `with_extensions` 2 | --> tests/ui/incorrect/sub/sub_unsupported_field.rs:6:65 3 | | 4 | 6 | #[subscription(name = "sub", unsubscribe = "unsub", item = u8, magic = true)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | max_width = 120 3 | use_small_heuristics = "Max" 4 | edition = "2024" 5 | -------------------------------------------------------------------------------- /scripts/generate_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script obtains the changelog to be introduced in the new release. 4 | 5 | set -eu 6 | 7 | REMOTE_LINK="https://github.com/paritytech/jsonrpsee/pull/" 8 | 9 | function usage() { 10 | cat <&2 21 | exit 1 22 | } 23 | 24 | function log_info() { 25 | echo -e "[+]" "$@" 26 | } 27 | 28 | while getopts "h?" opt; do 29 | case "$opt" in 30 | h|\?) 31 | usage 32 | exit 0 33 | ;; 34 | esac 35 | done 36 | 37 | GIT_BIN=$(which git) || log_error 'git is not installed. Please follow https://github.com/git-guides/install-git for instructions' 38 | 39 | # Generate the changelog between the provided tag and origin/master. 40 | function generate_changelog() { 41 | local tag="$1" 42 | 43 | prs=$($GIT_BIN --no-pager log --pretty=format:"%s" "$tag"..origin/master) || log_error 'Failed to obtain commit list' 44 | 45 | log_info "Changelog\n" 46 | while IFS= read -r line; do 47 | # Obtain the pr number from each line. The regex should match, as provided by the previous grep. 48 | if [[ $line =~ "(#"([0-9]+)")"$ ]]; then 49 | pr_number="${BASH_REMATCH[1]}" 50 | else 51 | continue 52 | fi 53 | 54 | # Generate a valid PR link. 55 | pr_link="$REMOTE_LINK$pr_number" 56 | # Generate the link as markdown. 57 | pr_md_link=" ([#$pr_number]($pr_link))" 58 | # Print every word from the commit title, except the last word. 59 | # The last word is the PR id that is already included by the pr-link. 60 | # The changelog line is `- commit-title pr-link`. 61 | echo "$line" | awk -v link="$pr_md_link" '{ printf "- "; for(i=1;i<=NF-1;i++) { printf $i" "} print link}' 62 | done <<< "$prs" 63 | } 64 | 65 | # Get latest release tag. 66 | tag=$($GIT_BIN describe --match "v[0-9]*" --abbrev=0 origin/master) || log_error 'Failed to obtain the latest release tag' 67 | log_info "Latest release tag: $tag" 68 | 69 | generate_changelog "$tag" 70 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script is copied from `https://github.com/paritytech/jsonrpc` with some minor tweaks. 4 | # Add `--dry-run` and/or `--allow-dirty` to your command line to test things before publication. 5 | 6 | set -eu 7 | 8 | ORDER=(types proc-macros core client/http-client client/transport client/ws-client client/wasm-client server jsonrpsee) 9 | 10 | function read_toml () { 11 | NAME="" 12 | VERSION="" 13 | NAME=$(grep "^name" ./Cargo.toml | sed -e 's/.*"\(.*\)"/\1/') 14 | VERSION=$(grep "^version" ./Cargo.toml | sed -e 's/.*"\(.*\)"/\1/') 15 | } 16 | function remote_version () { 17 | REMOTE_VERSION="" 18 | REMOTE_VERSION=$(cargo search "$NAME" | grep "^$NAME =" | sed -e 's/.*"\(.*\)".*/\1/') 19 | } 20 | 21 | # First display the plan 22 | for CRATE_DIR in ${ORDER[@]}; do 23 | cd $CRATE_DIR > /dev/null 24 | read_toml 25 | echo "$NAME@$VERSION" 26 | cd - > /dev/null 27 | done 28 | 29 | read -p ">>>> Really publish?. Press [enter] to continue. " 30 | 31 | set -x 32 | 33 | cargo clean 34 | 35 | set +x 36 | 37 | # Then actually perform publishing. 38 | for CRATE_DIR in ${ORDER[@]}; do 39 | cd $CRATE_DIR > /dev/null 40 | read_toml 41 | remote_version 42 | # Seems the latest version matches, skip by default. 43 | if [ "$REMOTE_VERSION" = "$VERSION" ] || [[ "$REMOTE_VERSION" > "$VERSION" ]]; then 44 | RET="" 45 | echo "Seems like $NAME@$REMOTE_VERSION is already published. Continuing in 5s. " 46 | read -t 5 -p ">>>> Type [r][enter] to retry, or [enter] to continue... " RET || true 47 | if [ "$RET" != "r" ]; then 48 | echo "Skipping $NAME@$VERSION" 49 | cd - > /dev/null 50 | continue 51 | fi 52 | fi 53 | 54 | # Attempt to publish (allow retries) 55 | while : ; do 56 | # give the user an opportunity to abort or skip before publishing 57 | echo "🚀 Publishing $NAME@$VERSION..." 58 | sleep 3 59 | 60 | set +e && set -x 61 | cargo publish $@ 62 | RES=$? 63 | set +x && set -e 64 | # Check if it succeeded 65 | if [ "$RES" != "0" ]; then 66 | CHOICE="" 67 | echo "##### Publishing $NAME failed" 68 | read -p ">>>>> Type [s][enter] to skip, or [enter] to retry.. " CHOICE 69 | if [ "$CHOICE" = "s" ]; then 70 | break 71 | fi 72 | else 73 | break 74 | fi 75 | done 76 | 77 | cd - > /dev/null 78 | done 79 | 80 | echo "Tagging jsonrpsee@$VERSION" 81 | set -x 82 | git tag -a -s v$VERSION -m "Version $VERSION" 83 | sleep 3 84 | git push --tags 85 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-server" 3 | description = "JSON-RPC server that supports HTTP and WebSocket transports" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | futures-util = { workspace = true, features = ["io", "async-await-macro"] } 21 | http = { workspace = true } 22 | http-body = { workspace = true } 23 | http-body-util = { workspace = true } 24 | hyper = { workspace = true, features = ["server", "http1", "http2"] } 25 | hyper-util = { workspace = true, features = ["tokio", "service", "tokio", "server-auto"] } 26 | jsonrpsee-core = { workspace = true, features = ["server", "http-helpers"] } 27 | jsonrpsee-types = { workspace = true } 28 | pin-project = "1.1.3" 29 | route-recognizer = "0.3.1" 30 | serde = "1" 31 | serde_json = { version = "1", features = ["raw_value"] } 32 | soketto = { version = "0.8.1", features = ["http"] } 33 | thiserror = "2" 34 | tokio = { version = "1.23.1", features = ["net", "rt-multi-thread", "macros", "time"] } 35 | tokio-util = { version = "0.7", features = ["compat"] } 36 | tokio-stream = { version = "0.1.7", features = ["sync"] } 37 | tower = { workspace = true, features = ["util"] } 38 | tracing = { workspace = true } 39 | 40 | [dev-dependencies] 41 | jsonrpsee-test-utils = { path = "../test-utils" } 42 | socket2 = { workspace = true } 43 | tower = { workspace = true, features = ["timeout"] } 44 | tracing-subscriber = { workspace = true } 45 | -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! # jsonrpsee-server 28 | //! 29 | //! `jsonrpsee-server` is a [JSON RPC](https://www.jsonrpc.org/specification) server that supports both HTTP and WebSocket transport. 30 | 31 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 32 | #![cfg_attr(docsrs, feature(doc_cfg))] 33 | 34 | mod future; 35 | mod server; 36 | mod transport; 37 | mod utils; 38 | 39 | pub mod middleware; 40 | 41 | #[cfg(test)] 42 | mod tests; 43 | 44 | pub use future::{AlreadyStoppedError, ConnectionGuard, ConnectionPermit, ServerHandle, StopHandle, stop_channel}; 45 | pub use jsonrpsee_core::error::RegisterMethodError; 46 | pub use jsonrpsee_core::server::*; 47 | pub use jsonrpsee_core::{id_providers::*, traits::IdProvider}; 48 | pub use jsonrpsee_types as types; 49 | pub use server::{ 50 | BatchRequestConfig, Builder as ServerBuilder, ConnectionState, PingConfig, Server, ServerConfig, 51 | ServerConfigBuilder, TowerService, TowerServiceBuilder, 52 | }; 53 | pub use tracing; 54 | 55 | pub use jsonrpsee_core::http_helpers::{Body as HttpBody, Request as HttpRequest, Response as HttpResponse}; 56 | pub use transport::http; 57 | pub use transport::ws; 58 | pub use utils::{serve, serve_with_graceful_shutdown}; 59 | 60 | pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; 61 | -------------------------------------------------------------------------------- /server/src/middleware/http/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Various middleware implementations for HTTP specific purposes. 28 | 29 | /// Utility and types related to the authority of an URI. 30 | mod authority; 31 | /// HTTP Host filtering middleware. 32 | mod host_filter; 33 | /// Proxy `GET /path` to internal RPC methods. 34 | mod proxy_get_request; 35 | 36 | pub use {authority::*, host_filter::*, proxy_get_request::*}; 37 | -------------------------------------------------------------------------------- /server/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! jsonrpsee-server middleware 28 | 29 | /// HTTP related middleware. 30 | pub mod http; 31 | pub mod rpc; 32 | -------------------------------------------------------------------------------- /server/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod helpers; 2 | mod http; 3 | mod shared; 4 | mod ws; 5 | -------------------------------------------------------------------------------- /server/src/tests/shared.rs: -------------------------------------------------------------------------------- 1 | use crate::ServerConfig; 2 | use crate::tests::helpers::{init_logger, server_with_handles}; 3 | use hyper::StatusCode; 4 | use jsonrpsee_test_utils::TimeoutFutureExt; 5 | use jsonrpsee_test_utils::helpers::{http_request, ok_response, to_http_uri}; 6 | use jsonrpsee_test_utils::mocks::{Id, WebSocketTestClient, WebSocketTestError}; 7 | use std::time::Duration; 8 | 9 | #[tokio::test] 10 | async fn stop_works() { 11 | init_logger(); 12 | let (_addr, server_handle) = server_with_handles().with_default_timeout().await.unwrap(); 13 | 14 | let handle = server_handle.clone(); 15 | handle.stop().unwrap(); 16 | handle.stopped().await; 17 | 18 | // After that we should be able to wait for task handle to finish. 19 | // First `unwrap` is timeout, second is `JoinHandle`'s one. 20 | 21 | // After server was stopped, attempt to stop it again should result in an error. 22 | assert!(server_handle.stop().is_err()); 23 | } 24 | 25 | #[tokio::test] 26 | async fn run_forever() { 27 | const TIMEOUT: Duration = Duration::from_millis(200); 28 | 29 | init_logger(); 30 | let (_addr, server_handle) = server_with_handles().with_default_timeout().await.unwrap(); 31 | 32 | assert!(matches!(server_handle.stopped().with_timeout(TIMEOUT).await, Err(_timeout_err))); 33 | 34 | let (_addr, server_handle) = server_with_handles().with_default_timeout().await.unwrap(); 35 | 36 | server_handle.stop().unwrap(); 37 | 38 | // Send the shutdown request from one handle and await the server on the second one. 39 | server_handle.stopped().with_timeout(TIMEOUT).await.unwrap(); 40 | } 41 | 42 | #[tokio::test] 43 | async fn http_only_works() { 44 | use crate::{RpcModule, ServerBuilder}; 45 | 46 | let config = ServerConfig::builder().http_only().build(); 47 | let server = ServerBuilder::with_config(config).build("127.0.0.1:0").with_default_timeout().await.unwrap().unwrap(); 48 | let mut module = RpcModule::new(()); 49 | module 50 | .register_method("say_hello", |_, _, _| { 51 | tracing::debug!("server respond to hello"); 52 | "hello" 53 | }) 54 | .unwrap(); 55 | 56 | let addr = server.local_addr().unwrap(); 57 | let _server_handle = server.start(module); 58 | 59 | let req = r#"{"jsonrpc":"2.0","method":"say_hello","id":1}"#; 60 | let response = http_request(req.into(), to_http_uri(addr)).with_default_timeout().await.unwrap().unwrap(); 61 | assert_eq!(response.status, StatusCode::OK); 62 | assert_eq!(response.body, ok_response("hello".to_string().into(), Id::Num(1))); 63 | 64 | let err = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap_err(); 65 | assert!(matches!(err, WebSocketTestError::RejectedWithStatusCode(code) if code == 403)); 66 | } 67 | 68 | #[tokio::test] 69 | async fn ws_only_works() { 70 | use crate::{RpcModule, ServerBuilder}; 71 | 72 | let config = ServerConfig::builder().ws_only().build(); 73 | let server = ServerBuilder::with_config(config).build("127.0.0.1:0").with_default_timeout().await.unwrap().unwrap(); 74 | let mut module = RpcModule::new(()); 75 | module 76 | .register_method("say_hello", |_, _, _| { 77 | tracing::debug!("server respond to hello"); 78 | "hello" 79 | }) 80 | .unwrap(); 81 | 82 | let addr = server.local_addr().unwrap(); 83 | let _server_handle = server.start(module); 84 | 85 | let req = r#"{"jsonrpc":"2.0","method":"say_hello","id":1}"#; 86 | let response = http_request(req.into(), to_http_uri(addr)).with_default_timeout().await.unwrap().unwrap(); 87 | assert_eq!(response.status, StatusCode::FORBIDDEN); 88 | 89 | let mut client = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap(); 90 | let response = client.send_request_text(req.to_string()).await.unwrap(); 91 | assert_eq!(response, ok_response("hello".to_string().into(), Id::Num(1))); 92 | } 93 | -------------------------------------------------------------------------------- /server/src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | /// HTTP related server functionality. 2 | pub mod http; 3 | /// WebSocket related server functionality. 4 | pub mod ws; 5 | -------------------------------------------------------------------------------- /test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-test-utils" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish = false 8 | 9 | [dependencies] 10 | futures-channel = { workspace = true } 11 | futures-util = { workspace = true, features = ["default"] } 12 | hyper = { workspace = true } 13 | hyper-util = { workspace = true, features = ["server-auto", "tokio", "client-legacy"] } 14 | http-body-util = { workspace = true } 15 | tracing = { workspace = true } 16 | serde = { workspace = true, features = ["derive"] } 17 | serde_json = { workspace = true } 18 | soketto = { workspace = true, features = ["http"] } 19 | tokio = { workspace = true, features = ["net", "rt-multi-thread", "macros", "time"] } 20 | tokio-util = { workspace = true, features = ["compat"] } 21 | -------------------------------------------------------------------------------- /test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! Shared test helpers for JSONRPC v2. 28 | 29 | #![recursion_limit = "256"] 30 | 31 | use std::future::Future; 32 | use std::time::Duration; 33 | 34 | use tokio::time::{Timeout, timeout}; 35 | 36 | pub mod helpers; 37 | pub mod mocks; 38 | 39 | /// Helper extension trait which allows to limit execution time for the futures. 40 | /// It is helpful in tests to ensure that no future will ever get stuck forever. 41 | pub trait TimeoutFutureExt: Future + Sized { 42 | /// Returns a reasonable value that can be used as a future timeout with a certain 43 | /// degree of confidence that timeout won't be triggered by the test specifics. 44 | fn default_timeout() -> Duration { 45 | // If some future wasn't done in 60 seconds, it's either a poorly written test 46 | // or (most likely) a bug related to some future never actually being completed. 47 | const TIMEOUT_SECONDS: u64 = 60; 48 | Duration::from_secs(TIMEOUT_SECONDS) 49 | } 50 | 51 | /// Adds a fixed timeout to the future. 52 | fn with_default_timeout(self) -> Timeout { 53 | self.with_timeout(Self::default_timeout()) 54 | } 55 | 56 | /// Adds a custom timeout to the future. 57 | fn with_timeout(self, timeout_value: Duration) -> Timeout { 58 | timeout(timeout_value, self) 59 | } 60 | } 61 | 62 | impl TimeoutFutureExt for U where U: Future + Sized {} 63 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-integration-tests" 3 | description = "Integration tests for jsonrpsee" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | publish = false 9 | 10 | [lints.clippy] 11 | manual_async_fn = { level = "allow", priority = -1 } 12 | 13 | [dev-dependencies] 14 | anyhow = { workspace = true } 15 | fast-socks5 = { workspace = true } 16 | futures = { workspace = true, features = ["std"] } 17 | futures-util = { workspace = true, features = ["alloc"]} 18 | http-body-util = { workspace = true } 19 | hyper = { workspace = true } 20 | hyper-util = { workspace = true, features = ["http1", "client", "client-legacy"] } 21 | jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } 22 | jsonrpsee-test-utils = { path = "../test-utils" } 23 | serde = { workspace = true } 24 | serde_json = { workspace = true } 25 | tokio = { workspace = true, features = ["rt-multi-thread", "time"] } 26 | tokio-stream = { workspace = true } 27 | tokio-util = { workspace = true, features = ["compat"]} 28 | tower = { workspace = true } 29 | tower-http = { workspace = true, features = ["cors"] } 30 | tracing = { workspace = true } 31 | tracing-subscriber = { workspace = true } 32 | pin-project = { workspace = true } 33 | -------------------------------------------------------------------------------- /tests/proc-macro-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-proc-macro-core" 3 | description = "Test crate for the proc-macro API to make sure that it compiles with the core features" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | publish = false 9 | 10 | [dependencies] 11 | jsonrpsee = { path = "../../jsonrpsee", features = ["server-core", "client-core", "macros"] } 12 | serde = { workspace = true } -------------------------------------------------------------------------------- /tests/proc-macro-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Test module for the proc-macro API to make sure that it compiles with the core features. 2 | 3 | use jsonrpsee::PendingSubscriptionSink; 4 | use jsonrpsee::core::{SubscriptionResult, async_trait, to_json_raw_value}; 5 | use jsonrpsee::proc_macros::rpc; 6 | use jsonrpsee::types::ErrorObjectOwned; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Serialize, Deserialize)] 10 | pub enum PubSubKind { 11 | A, 12 | B, 13 | } 14 | 15 | #[derive(Debug, Serialize, Deserialize)] 16 | pub struct PubSubParams { 17 | params: String, 18 | } 19 | 20 | #[derive(Debug, Serialize, Deserialize)] 21 | pub struct PubSubItem { 22 | result: String, 23 | } 24 | 25 | #[rpc(client, server)] 26 | pub trait Api { 27 | #[method(name = "sync_call")] 28 | fn sync_call(&self, a: String) -> Result; 29 | 30 | #[method(name = "async_call")] 31 | async fn async_call(&self, a: String) -> Result; 32 | 33 | #[subscription(name = "subscribe", item = PubSubItem)] 34 | async fn sub(&self, kind: PubSubKind, params: PubSubParams) -> SubscriptionResult; 35 | 36 | #[subscription(name = "subscribeSync", item = String)] 37 | fn sync_sub(&self, a: String) -> SubscriptionResult; 38 | 39 | #[method(name = "blocking", blocking)] 40 | fn blocking_method(&self, a: String) -> Result; 41 | } 42 | 43 | #[async_trait] 44 | impl ApiServer for () { 45 | fn sync_call(&self, _: String) -> Result { 46 | Ok("sync_call".to_string()) 47 | } 48 | 49 | async fn async_call(&self, _: String) -> Result { 50 | Ok("async_call".to_string()) 51 | } 52 | 53 | async fn sub(&self, pending: PendingSubscriptionSink, _: PubSubKind, _: PubSubParams) -> SubscriptionResult { 54 | let sink = pending.accept().await?; 55 | let msg = to_json_raw_value("msg")?; 56 | sink.send(msg).await?; 57 | Ok(()) 58 | } 59 | 60 | fn sync_sub(&self, _: PendingSubscriptionSink, _: String) -> SubscriptionResult { 61 | Ok(()) 62 | } 63 | 64 | fn blocking_method(&self, _: String) -> Result { 65 | Ok(42) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/wasm-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-test" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish = false 8 | 9 | [dev-dependencies] 10 | wasm-bindgen-test = "0.3.24" 11 | tracing-wasm = "0.2.1" 12 | console_error_panic_hook = "0.1.7" 13 | serde_json = { workspace = true } 14 | jsonrpsee-wasm-client = { path = "../../client/wasm-client" } 15 | jsonrpsee-core = { path = "../../core" , features = ["client"] } 16 | jsonrpsee-client-transport = { path = "../../client/transport", features = ["web"] } 17 | -------------------------------------------------------------------------------- /tests/wasm-tests/tests/wasm.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | use jsonrpsee_client_transport::web::*; 4 | use jsonrpsee_core::{ 5 | client::{ClientT, ReceivedMessage, Subscription, SubscriptionClientT, TransportReceiverT, TransportSenderT}, 6 | rpc_params, 7 | }; 8 | use jsonrpsee_wasm_client::WasmClientBuilder; 9 | use wasm_bindgen_test::*; 10 | 11 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 12 | 13 | /// Run the tests by `$ wasm-pack test --firefox --headless` 14 | 15 | fn init_tracing() { 16 | console_error_panic_hook::set_once(); 17 | tracing_wasm::set_as_global_default(); 18 | } 19 | 20 | #[wasm_bindgen_test] 21 | async fn wasm_ws_transport_works() { 22 | init_tracing(); 23 | let (mut tx, mut rx) = connect("ws://localhost:9944").await.unwrap(); 24 | 25 | let req = r#"{"jsonrpc": "2.0", "method": "system_name", "id": 1}"#; 26 | let exp = r#"{"jsonrpc":"2.0","result":"Substrate Node","id":1}"#; 27 | 28 | tx.send(req.to_string()).await.unwrap(); 29 | let rp: ReceivedMessage = rx.receive().await.unwrap(); 30 | 31 | match rp { 32 | ReceivedMessage::Text(s) => assert_eq!(exp, &s), 33 | _ => assert!(false, "Expected string message"), 34 | }; 35 | } 36 | 37 | #[wasm_bindgen_test] 38 | async fn rpc_method_call_works() { 39 | let client = WasmClientBuilder::default().build("ws://localhost:9944").await.unwrap(); 40 | 41 | let rp: String = client.request("system_name", rpc_params![]).await.unwrap(); 42 | 43 | assert_eq!("Substrate Node", &rp); 44 | } 45 | 46 | #[wasm_bindgen_test] 47 | async fn rpc_subcription_works() { 48 | let client = WasmClientBuilder::default().build("ws://localhost:9944").await.unwrap(); 49 | 50 | let mut sub: Subscription = 51 | client.subscribe("state_subscribeStorage", rpc_params![], "state_unsubscribeStorage").await.unwrap(); 52 | 53 | for _ in 0..3 { 54 | let val = sub.next().await.unwrap(); 55 | assert!(val.is_ok()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpsee-types" 3 | description = "JSON-RPC v2 specific types" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | documentation.workspace = true 11 | homepage.workspace = true 12 | keywords.workspace = true 13 | readme.workspace = true 14 | publish = true 15 | 16 | [lints] 17 | workspace = true 18 | 19 | [dependencies] 20 | http = { workspace = true } 21 | serde = { workspace = true } 22 | serde_json = { workspace = true } 23 | thiserror = { workspace = true } 24 | -------------------------------------------------------------------------------- /types/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 Parity Technologies (UK) Ltd. 2 | // 3 | // Permission is hereby granted, free of charge, to any 4 | // person obtaining a copy of this software and associated 5 | // documentation files (the "Software"), to deal in the 6 | // Software without restriction, including without 7 | // limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software 10 | // is furnished to do so, subject to the following 11 | // conditions: 12 | // 13 | // The above copyright notice and this permission notice 14 | // shall be included in all copies or substantial portions 15 | // of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | 27 | //! JSON-RPC specific types. 28 | 29 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 30 | #![cfg_attr(docsrs, feature(doc_cfg))] 31 | 32 | extern crate alloc; 33 | 34 | /// JSON-RPC params related types. 35 | pub mod params; 36 | 37 | /// JSON-RPC request object related types 38 | pub mod request; 39 | 40 | /// JSON-RPC response object related types. 41 | pub mod response; 42 | 43 | /// JSON-RPC response error object related types. 44 | pub mod error; 45 | 46 | pub use error::{ErrorCode, ErrorObject, ErrorObjectOwned}; 47 | pub use http::Extensions; 48 | pub use params::{Id, InvalidRequestId, Params, ParamsSequence, SubscriptionId, TwoPointZero}; 49 | pub use request::{InvalidRequest, Notification, Request}; 50 | pub use response::{Response, ResponsePayload, SubscriptionPayload, SubscriptionResponse, Success as ResponseSuccess}; 51 | --------------------------------------------------------------------------------