{"id":14966056,"url":"https://github.com/bbkr/json-rpc","last_synced_at":"2025-10-25T13:31:08.004Z","repository":{"id":1171386,"uuid":"1065711","full_name":"bbkr/JSON-RPC","owner":"bbkr","description":"JSON-RPC client and server for Raku language.","archived":false,"fork":false,"pushed_at":"2023-04-27T19:50:16.000Z","size":156,"stargazers_count":12,"open_issues_count":0,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-31T07:21:47.536Z","etag":null,"topics":["json-rpc","raku"],"latest_commit_sha":null,"homepage":"","language":"Raku","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"artistic-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bbkr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2010-11-09T17:12:53.000Z","updated_at":"2023-12-03T19:21:06.000Z","dependencies_parsed_at":"2023-07-05T19:01:47.148Z","dependency_job_id":null,"html_url":"https://github.com/bbkr/JSON-RPC","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbkr%2FJSON-RPC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbkr%2FJSON-RPC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbkr%2FJSON-RPC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbkr%2FJSON-RPC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bbkr","download_url":"https://codeload.github.com/bbkr/JSON-RPC/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238147581,"owners_count":19424287,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-rpc","raku"],"created_at":"2024-09-24T13:35:45.928Z","updated_at":"2025-10-25T13:31:02.730Z","avatar_url":"https://github.com/bbkr.png","language":"Raku","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSON-RPC client and server for [Raku](https://www.raku.org) language\n\n[![.github/workflows/test.yml](https://github.com/bbkr/JSON-RPC/actions/workflows/test.yml/badge.svg)](https://github.com/bbkr/JSON-RPC/actions/workflows/test.yml)\n\nSupports [2.0 specification](http://www.jsonrpc.org/specification).\n\n## CLIENT\n\n```raku\nuse JSON::RPC::Client;\n\n# create new client with url to server\nmy $c = JSON::RPC::Client.new( url =\u003e 'http://127.0.0.1:8080' );\n\n# method without params\nsay $c.ping;\n\n# method with positional params\nsay $c.hi( 'John Doe' );\n\n# method with named params\nsay $c.hello( name =\u003e 'John Doe' );\n```\n\n## SERVER\n\n```raku\nuse JSON::RPC::Server;\n\n# define application class\n# that will handle remote procedure calls\nclass My::App {\n\n    # method without params\n    method ping { return 'pong' }\n\n    # method with positional params\n    method hi ( Str $name! ) { return 'Hi ' ~ $name }\n\n    # method with named params\n    method hello ( Str :$name! ) { return 'Hello ' ~ $name }\n\n    # multi method with different signatures\n    multi method offer ( Int $age where { $age \u003c 8 } ) {\n        return [ 'Toy' ];\n    }\n    multi method offer ( Int $age where { 8 \u003c= $age \u003c= 16 } ) {\n        return [ 'Computer', 'Pet' ];\n    }\n\n}\n\n# start server with your application as handler\nJSON::RPC::Server.new( application =\u003e My::App ).run( );\n```\n\nYour server is now available at [http://localhost:8080](http://localhost:8080).\n\n## ADVANCED STUFF\n\nExamples above _make easy things easy_, now it is time to make _hard things possible_.\n\n### Protocol versions\n\nThere are 4 specs of JSON-RPC published so far:\n\n* [1.0](http://json-rpc.org/wiki/specification) - Not implemented. Does not support named params, error objects or batch requests and has different way of handling notifications compared to current spec. It is rarely used nowadays and because of that there are no plans to implement it, however contributions are welcome if someone wants to add backward compatibility.\n* [1.1](http://web.archive.org/web/20100718181845/http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html) - Rejected. This working draft forces error reporting through HTTP codes making whole communication transport-dependent.\n* [1.2](http://jsonrpc.org/historical/jsonrpc12_proposal.html) - Proposal of 2.0 (see below).\n* [2.0](http://www.jsonrpc.org/specification) - Fully implemented!\n\n### Can I use URI object to initialize client?\n\nUse `uri` param in constructor.\n\n```raku\nJSON::RPC::Client.new( uri =\u003e URI.new( 'http://127.0.0.1:8080' ) );\n```\n\n### Can I bind server to port other than 8080?\n\nUse `port` param in `run( )` method.\n\n```raku\nJSON::RPC::Server.new( application =\u003e My::App ).run( port =\u003e 9999 );\n```\n\n### Should I use class name or object instance as server handler?\n\nYou can use both. Using class name results in static dispatch while using object instance allows you to initialize attributes in your class.\n\n```raku\nclass My::App {\n\n    has $!db;\n    submethod BEGIN { $!db = ... }  # connect to database\n\n    method ping ( ) { return 'pong' }\n\n}\n\n# BEGIN is not called\nJSON::RPC::Server.new( application =\u003e My::App ).run( );\n\n# BEGIN is called\nJSON::RPC::Server.new( application =\u003e My::App.new ).run( );\n```\n\n### How can method be excluded from server handler dispatch?\n\nDeclare it as private.\n\n```raku\nmethod !get_database_info ( ) {\n    return 'username', 'password';\n}\n```\n\n### Should I declare signatures for server handler methods?\n\nIt is recommended that you validate params in signatures instead of method bodies. This way server correctly returns \"Invalid params\" error (more info later) and method is not called if signature does not match - you can easily separate validation from logic.\n\n```raku\nmethod add_programmer (\n    Str :$name!,\n    Int :$age! where { $age \u003e= 0 },\n    Int :$experience! where { $experience \u003c= $age }\n) {\n    # params can be trusted here\n    # all fields are required and\n    # negative age or experience exceeding age shall not pass\n    $!db.insert( $name, $age, $experience );\n}\n```\n\n### What happens when more than one server handler candidate matches?\n\nWhen request can be dispatched to more than one multi method then first candidate is chosen and called. JSON-RPC protocol design does not include multi methods - it can not mimic [calling sets](https://docs.raku.org/language/operators#methodop_.*) mechanism and does not have \"Ambiguous call\" error in specification like Raku does. Therefore such request is not considered an error.\n\n### Can I use my own transport layers?\n\nThis is useful when you want to use JSON-RPC on some framework which provides its own data exchange methods. It is even possible to use JSON-RPC over protocols different than HTTP.\n\n**Client**\n\nPass `transport` param to `new( )` instead of `uri`/ `url` param. This should be a closure that accepts JSON request and returns JSON response.\n\n```raku\nsub transport ( Str :$json, Bool :$get_response ) {\n    return send_request_in_my_own_way_and_obtain_response_if_needed( $request );\n}\n\nmy $client = JSON::RPC::Client.new( transport =\u003e \u0026transport );\n```\n\nYour transport will be given extra param `get_response` which informs if response is expected from the server or not (for example in case of Notification or Batch of Notifications).\n\n**Server**\n\nDo not `run( )` server. Instead use `handler( )` method which takes JSON request param and returns JSON response.\n\n```raku\nmy $server = JSON::RPC::Server.new( application =\u003e My::App );\n\nmy $response = handler( json =\u003e receive_request_in_my_own_way( ) );\nsend_response_in_my_own_way( $_ ) with $response;\n```\n\nIt is possible that request is a Notification or Batch of Notifications and `$response` is not returned from the server.\n\n**Notifications**\n\nWhen request is a Notification or Batch of Notifications then client is not expecting response and server should not return one. That is not always possible due to specification of used protocol or assumptions in framework used. In this case try to use most un:!canonicalize response possible.\n\nFor example code `204 No Content` should be used in HTTP transport.\n\n### How to enable debugging?\n\n**Client** has no debugging yet.\n\n**Server** accepts `debug` param in `run( )` method.\n\n```raku\nJSON::RPC::Server.new( application =\u003e My::App ).run( :debug );\n```\n\n### How to implement Error handling?\n\nErrors :!canonicalize in 2.0 spec are represented by `X::JSON::RPC` exceptions:\n\n* `X::JSON::RPC::ParseError` - Invalid JSON was received by the server.\n* `X::JSON::RPC::InvalidRequest` - The structure sent by client is not a valid Request object.\n* `X::JSON::RPC::MethodNotFound` - The method does not exist in server handler application.\n* `X::JSON::RPC::InvalidParams` - Invalid method parameters, no handler candidates with matching signature found.\n* `X::JSON::RPC::InternalError` - Remote method died.\n* `X::JSON::RPC::ProtocolError` - Other deviation from specification.\n\nEvery exception has numeric `code` attribute that indicates the error type that occurred, text `message` attribute that provides a short description of the error and optional `data` attribute that contains additional information about the error.\n\n**Client** can catch those exceptions.\n\n```raku\ntry {\n    $c.hello( 'John Doe' );\n    CATCH {\n        when X::JSON::RPC::MethodNotFound {\n            say 'Server is rude';\n        }\n        default {\n            # stringified exception is in human-readable form\n            say ~$_;\n        }\n    }\n}\n```\n\n**Server** does all the exception handling automatically. For example if you provide application handler without some method client will receive \"Method not found\" error on call to this method. However if you want to report error from method it can be done in two ways.\n\n* End method using die.\n\n```raku\nmethod divide ( Int $x, Int $y ) {\n    die 'Cannot divide by 0' if $y ~~ 0;\n    return $x / $y;\n}\n```\n\nClient will receive `message` attribute \"Internal error\" with explanation \"Cannot divide by 0\" as `data` attribute.\n\n* Throw `X::JSON::RPC` exception.\n\n```raku\nclass My::App {\n    method treasure {\n        X::JSON::RPC.new( code =\u003e -1, message =\u003e 'Access denied', data =\u003e 'Thou shall not pass!' ).throw( );\n    }\n}\n```\n\nException `X::JSON::RPC` is composable so you can easily define your own errors.\n\n```raku\n    class My::Error does X::JSON::RPC {\n        method new {\n            self.bless( code =\u003e -1, message =\u003e 'Access denied', data =\u003e 'Thou shall not pass!' );\n        }\n    }\n```\n\nAnd use them in application handler.\n\n```raku\nmethod treasure {\n    My::Error.new.throw( );\n}\n```\n\n### How to make Notification call?\n\nMethod `'rpc.notification'( )` puts client in notification context.\nNote that this method contains dot in name and it must be quoted.\n\n```raku\n$client.'rpc.notification'( ).heartbeat( ); # no response from this one\nsay $client.ping( ) # regular call again\n```\n\nYou can save client context to avoid typing.\n\n```raku\nmy $n = $client.'rpc.notification'( );\nfor ^1024 {\n    $n.heartbeat( ); # no responses from those\n}\n```\n\n### How to make Batch call?\n\nMethod `'rpc.batch'( )` puts client in batch context while method `'rpc.flush'( )` sends Requests.\nNote that those methods contain dot in names and they must be quoted.\n\n```raku\n$client.'rpc.batch'( ).add( 2, 2 );\n$client.'rpc.batch'( ).'rpc.notification'( ).heartbeat( );\n$client.'rpc.batch'( ).suicide( );\n\nfor $client.'rpc.flush'( ) -\u003e $response {\n    try {\n        $response.say;\n        CATCH {\n            when X::JSON::RPC {\n                say 'Oops! ', .message;\n            }\n        }\n    }\n}\n\n# Output:\n# 4\n# Opps! Suicide served.\n```\n\nImportant things to remember:\n\n* Server may process methods in Batch in any order and with any width of parallelism.\n* Client will sort responses to match order in which methods were stacked.\n* Notifications do not have corresponding response.\n* Batch containing only Notifications will return Nil on flush.\n* Attempt to flush empty Batch will result in `X::JSON::RPC::InvalidRequest` exception.\n* Individual exceptions are returned as Failures, thrown when called in sink context.\n\nYou can save client context to avoid typing.\n\n```raku\nmy $b = $client.'rpc.batch'( );\nfor ^1024 {\n    $b.is_prime( $_ );\n}\nmy @responses = $b.'rpc.flush'( );\n```\n\n### How to call method that has name used by language itself?\n\nEvery object instance has some methods inherited from [Mu](https://docs.raku.org/type/Mu) and [Any](https://docs.raku.org/type/Any) classes.\nThis rule also applies to `JSON::RPC::Client` and in rare cases you may fall into the trap.\nBelow example calls `Mu::can( )` instead of doing remote procedure call of method named \"can\".\n\n```raku\n$client.can( 'tuna' );\n```\n\nThe workaround is to prefix method name with `rpc.`.\nNote that whole name must be quoted because it contains dot.\n\n```raku\n$client.'rpc.can'( 'tuna' );\n```\n\nYou can get full list of those troublemakers by invoking following code.\n\n```raku\nJSON::RPC::Client.^mro\u003e\u003e.^methods\u003e\u003e.say\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbkr%2Fjson-rpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbbkr%2Fjson-rpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbkr%2Fjson-rpc/lists"}