zmqjsonrpc 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +23 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/Vagrantfile +31 -0
- data/lib/simple_json_rpc/client.rb +63 -0
- data/lib/simple_json_rpc/server.rb +87 -0
- data/lib/zmqjsonrpc.rb +6 -0
- data/test/test_client_server.rb +52 -0
- data/zmqjsonrpc.gemspec +24 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 275b2e7bd02313197bd72c9791bfb20774a8f0c7
|
4
|
+
data.tar.gz: 32c9c8d5bba102217c2f19472575e8d893821de2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d636755631636c90c151348753be54440f0b2ff212a1a712f111514bcf2a1a615c48e81a366ad7d08a9a5c4c3ffc496e038bda0a7903cbd03215c81650af5bf
|
7
|
+
data.tar.gz: b6c77e276528cf85b0040aa7e71ef0fceb3105bd5caadf8511b573aad9e90923c4905df61cc56d1b72858d411f84a6f37649756e71676d0f17904405215b3c3d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
simplejsonrpc (0.1)
|
5
|
+
ffi-rzmq (~> 2.0.4)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ffi (1.9.6)
|
11
|
+
ffi-rzmq (2.0.4)
|
12
|
+
ffi-rzmq-core (>= 1.0.1)
|
13
|
+
ffi-rzmq-core (1.0.3)
|
14
|
+
ffi (~> 1.9)
|
15
|
+
rake (10.4.2)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
bundler (~> 1.3)
|
22
|
+
rake
|
23
|
+
simplejsonrpc!
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# zmqjsonrpc
|
2
|
+
|
3
|
+
This gem implements a very simple [JSON RPC 2.0](http://www.jsonrpc.org/specification) client and server which uses zeroMQ for transport.
|
4
|
+
Let's not talk to much, let's see some code:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
require 'rubygems'
|
8
|
+
require 'zmqjsonrpc'
|
9
|
+
|
10
|
+
# client request to a running server
|
11
|
+
client = ZmqJsonRpc::Client.new("tcp://127.0.0.1:49200")
|
12
|
+
client.some_method(1, "b", [1,{a:1}])
|
13
|
+
```
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'rubygems'
|
17
|
+
require 'zmqjsonrpc'
|
18
|
+
|
19
|
+
class Proxy
|
20
|
+
def some_method(a,b,c)
|
21
|
+
# do you thing
|
22
|
+
return ["xyz", 77]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# blocking server
|
27
|
+
proxy = Proxy.new()
|
28
|
+
server = ZmqJsonRpc::Server.new(proxy, "tcp://*:49200")
|
29
|
+
server.server_loop
|
30
|
+
|
31
|
+
# -or- dispatch a thread
|
32
|
+
server = ZmqJsonRpc::Server.new(proxy, "tcp://*:49200")
|
33
|
+
thread = Thread.new {
|
34
|
+
server.server_loop
|
35
|
+
}
|
36
|
+
# and cancel the thread if you want to shut the server down
|
37
|
+
thread.exit
|
38
|
+
```
|
39
|
+
|
40
|
+
## Resources
|
41
|
+
|
42
|
+
* The [JSON RPC 2.0 Spec](http://www.jsonrpc.org/specification)
|
43
|
+
* The used [ZeroMQ gem](https://github.com/chuckremes/ffi-rzmq) and [good examples](http://github.com/andrewvc/learn-ruby-zeromq)
|
44
|
+
* [Gem making](http://guides.rubygems.org/make-your-own-gem/)
|
45
|
+
|
46
|
+
## Stuff left to do
|
47
|
+
|
48
|
+
* Add support for by-name parameters (see [the spec](http://www.jsonrpc.org/specification#parameter_structures))
|
49
|
+
* Add individual exception classes in the client.
|
50
|
+
* Send different error codes if something goes wrong in the server.
|
51
|
+
* Keep the client connection alive instead of re-establishing every time.
|
52
|
+
* Add more tests.
|
53
|
+
|
54
|
+
## Licence
|
55
|
+
|
56
|
+
This code is released under the terms of MIT License.
|
57
|
+
|
58
|
+
## Contribute
|
59
|
+
|
60
|
+
Please do so! Just send a messahe or send a pull request.
|
61
|
+
Especially, adding webrick for transport would be nice.
|
data/Rakefile
ADDED
data/Vagrantfile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
provision = <<SCRIPT
|
5
|
+
# Update system
|
6
|
+
sudo apt-get -y update
|
7
|
+
sudo apt-get -y upgrade
|
8
|
+
sudo apt-get -y install build-essential git
|
9
|
+
|
10
|
+
# ruby
|
11
|
+
sudo apt-get -y install ruby2.0 ruby2.0-dev bundler rake
|
12
|
+
sudo ln -sf /usr/bin/ruby2.0 /usr/bin/ruby # make ruby 2 default
|
13
|
+
sudo ln -sf /usr/bin/gem2.0 /usr/bin/gem
|
14
|
+
sudo ln -sf /usr/bin/irb2.0 /usr/bin/irb
|
15
|
+
|
16
|
+
# install tools for development
|
17
|
+
sudo apt-get -y install vim man
|
18
|
+
|
19
|
+
# install dependencies
|
20
|
+
sudo apt-get install libzmq3 libzmq3-dev
|
21
|
+
SCRIPT
|
22
|
+
|
23
|
+
Vagrant.configure("2") do |config|
|
24
|
+
config.vm.box = "ubuntu/trusty64"
|
25
|
+
config.vm.hostname = "simplejsonrpc"
|
26
|
+
config.vm.provision :shell, inline: provision
|
27
|
+
|
28
|
+
config.vm.provider "virtualbox" do |vb|
|
29
|
+
vb.name = "simplejsonrpc"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module ZmqJsonRpc
|
5
|
+
class ClientError < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
# You shall instanciate this class with a connect string which follows the zeroMQ conventions: http://api.zeromq.org/2-1:zmq-connect
|
9
|
+
# After connecting you can call any method on this class and it will be sent to the server.
|
10
|
+
# If something goes wrong ZmqJsonRpc::ClientError is thrown.
|
11
|
+
# timeout is measured in milliseconds.
|
12
|
+
#
|
13
|
+
# NOTE
|
14
|
+
# The client does not keep the connection alive. For each request, a new connection is esablished and torn down after the response.
|
15
|
+
# This could become a performance issue.
|
16
|
+
class Client
|
17
|
+
def initialize(connect="tcp://127.0.0.1:49200", timeout=10000)
|
18
|
+
@connect = connect
|
19
|
+
@timeout = timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_rpc(method, params=[])
|
23
|
+
begin
|
24
|
+
# connect socket
|
25
|
+
@context = ZMQ::Context.new(1)
|
26
|
+
@socket = @context.socket(ZMQ::REQ)
|
27
|
+
@socket.connect(@connect)
|
28
|
+
@socket.setsockopt(ZMQ::SNDTIMEO, @timeout)
|
29
|
+
@socket.setsockopt(ZMQ::RCVTIMEO, @timeout)
|
30
|
+
|
31
|
+
# build and send request
|
32
|
+
req_id = SecureRandom.uuid
|
33
|
+
request = {
|
34
|
+
id: req_id,
|
35
|
+
jsonrpc: "2.0",
|
36
|
+
method: method.to_s,
|
37
|
+
params: params
|
38
|
+
}
|
39
|
+
rc = @socket.send_string(request.to_json) # this will always succeed, even if the server is not reachable.
|
40
|
+
|
41
|
+
# interpret response
|
42
|
+
response = ''
|
43
|
+
rc = @socket.recv_string(response)
|
44
|
+
raise "Could talk to the server (server unreachable? time out?)" if rc < 0
|
45
|
+
resjson = JSON.parse(response)
|
46
|
+
# check response
|
47
|
+
raise "Response's id did not match the sent id" if resjson["id"] != req_id
|
48
|
+
raise "Response's version number is not supported (#{resjson["jsonrpc"]})" if resjson["jsonrpc"].strip != "2.0"
|
49
|
+
raise "Server returned error (#{resjson["error"]["code"] || "?"}): #{resjson["error"]["message"]}\n#{resjson["error"]["data"]}" if resjson["error"]
|
50
|
+
|
51
|
+
return resjson["result"]
|
52
|
+
rescue => e
|
53
|
+
raise ClientError, e.message
|
54
|
+
ensure
|
55
|
+
@socket.close rescue ''
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(meth, *args, &block)
|
60
|
+
self.send_rpc(meth, args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ffi-rzmq'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ZmqJsonRpc
|
5
|
+
# This class implements the server:
|
6
|
+
#
|
7
|
+
# USE
|
8
|
+
# class Proxy
|
9
|
+
# def some_method(a,b)
|
10
|
+
# return [a,b,{"XXXX"=> 1}]
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# server = ZmqJsonRpc::Server.new(Proxy.new())
|
14
|
+
# server.server_loop
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# If you want a non blocking server do
|
18
|
+
# server = ZmqJsonRpc::Server.new(Proxy.new())
|
19
|
+
# thread = Thread.new {
|
20
|
+
# server.server_loop
|
21
|
+
# }
|
22
|
+
# # later either do
|
23
|
+
# thread.join # waiting for the server to finish -- which is never!
|
24
|
+
# # or
|
25
|
+
# thread.exit # or end the thread
|
26
|
+
class Server
|
27
|
+
# For errors the spec says:
|
28
|
+
# The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range, but not defined explicitly below is reserved for future use. The error codes are nearly the same as those suggested for XML-RPC at the following url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
29
|
+
#
|
30
|
+
# For details on the zmq gem, please see https://github.com/chuckremes/ffi-rzmq (best to look in the code)
|
31
|
+
def initialize(proxy, connect="tcp://*:49200", logger=nil)
|
32
|
+
@connect = connect
|
33
|
+
@proxy = proxy
|
34
|
+
@logger = logger
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_request(request)
|
38
|
+
begin
|
39
|
+
req_id = nil
|
40
|
+
rpc = JSON.parse(request)
|
41
|
+
raise "Received unsupprted jsonrpc version (#{rpc['jsonrpc']})" if rpc["jsonrpc"].strip != "2.0"
|
42
|
+
rid = rpc["id"]
|
43
|
+
method = rpc["method"]
|
44
|
+
params = rpc["params"]
|
45
|
+
|
46
|
+
@logger.info "Received JSON RPC request: #{method}(#{params.collect {|p| p.inspect}.join(", ")})" unless @logger.nil?
|
47
|
+
result = @proxy.send(method.to_sym, *params)
|
48
|
+
response = {
|
49
|
+
id: rid,
|
50
|
+
jsonrpc: "2.0",
|
51
|
+
result: result
|
52
|
+
}
|
53
|
+
return response.to_json
|
54
|
+
rescue => e
|
55
|
+
# If there is more time to spare, we could implement the actual error codes here.
|
56
|
+
@logger.warn "Returning error for RPC request: #{e.message})" unless @logger.nil?
|
57
|
+
response = {
|
58
|
+
id: rid,
|
59
|
+
jsonrpc: "2.0",
|
60
|
+
error: {
|
61
|
+
code: 32603,
|
62
|
+
message: e.message,
|
63
|
+
data: e.backtrace.inspect
|
64
|
+
}
|
65
|
+
}
|
66
|
+
return response.to_json
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def server_loop
|
71
|
+
@context = ZMQ::Context.new(1)
|
72
|
+
@socket = @context.socket(ZMQ::REP)
|
73
|
+
@socket.bind(@connect)
|
74
|
+
begin
|
75
|
+
loop do
|
76
|
+
request = ''
|
77
|
+
rc = @socket.recv_string(request)
|
78
|
+
response = handle_request(request)
|
79
|
+
@socket.send_string(response)
|
80
|
+
end
|
81
|
+
ensure
|
82
|
+
@socket.close
|
83
|
+
# @context.terminate
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/zmqjsonrpc.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require "logger"
|
3
|
+
require "stringio"
|
4
|
+
require_relative '../lib/zmqjsonrpc'
|
5
|
+
|
6
|
+
class TestClientServer < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@buffer = StringIO.new
|
9
|
+
@logger = Logger.new(@buffer)
|
10
|
+
@logger.level = Logger::WARN
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_empty_log
|
14
|
+
@buffer.seek(0)
|
15
|
+
assert_equal @buffer.read, "", "Server log included warnings."
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_client_with_server
|
19
|
+
proxy_class = Class.new() do
|
20
|
+
def initialize(test_case)
|
21
|
+
@test_case = test_case
|
22
|
+
@method_was_called = false
|
23
|
+
end
|
24
|
+
def method_was_called?
|
25
|
+
return @method_was_called
|
26
|
+
end
|
27
|
+
|
28
|
+
def some_method(a,b,c)
|
29
|
+
@test_case.assert_equal a, 1
|
30
|
+
@test_case.assert_equal b, "b"
|
31
|
+
@test_case.assert_equal c, [1,{"a" => 1}]
|
32
|
+
@method_was_called = true
|
33
|
+
return "abc"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
proxy = proxy_class.new(self)
|
38
|
+
server = ZmqJsonRpc::Server.new(proxy, "tcp://*:49200", @logger)
|
39
|
+
thread = Thread.new {
|
40
|
+
server.server_loop
|
41
|
+
}
|
42
|
+
client = ZmqJsonRpc::Client.new("tcp://127.0.0.1:49200")
|
43
|
+
assert_equal client.some_method(1, "b", [1,{a:1}]), "abc"
|
44
|
+
assert proxy.method_was_called?, "Server method was not called"
|
45
|
+
|
46
|
+
assert_raise ZmqJsonRpc::ClientError do
|
47
|
+
client.fishy_method()
|
48
|
+
end
|
49
|
+
|
50
|
+
thread.exit
|
51
|
+
end
|
52
|
+
end
|
data/zmqjsonrpc.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# more info here: http://guides.rubygems.org/specification-reference/
|
2
|
+
|
3
|
+
# coding: utf-8
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "zmqjsonrpc"
|
9
|
+
spec.version = "0.1"
|
10
|
+
spec.authors = ["Tom Rothe"]
|
11
|
+
spec.email = ["tom@bisdn.de"]
|
12
|
+
spec.description = 'Simple JSON RPC 2.0 client and server via zmq.'
|
13
|
+
spec.summary = 'ZeroMQ JSON RPC client and server.'
|
14
|
+
spec.homepage = "https://github.com/bisdn/zmqjsonrpc"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.test_files = Dir.glob('test/*.rb')
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency("ffi-rzmq", "~> 2.0.4")
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zmqjsonrpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Rothe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi-rzmq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Simple JSON RPC 2.0 client and server via zmq.
|
56
|
+
email:
|
57
|
+
- tom@bisdn.de
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- Gemfile.lock
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- Vagrantfile
|
68
|
+
- lib/simple_json_rpc/client.rb
|
69
|
+
- lib/simple_json_rpc/server.rb
|
70
|
+
- lib/zmqjsonrpc.rb
|
71
|
+
- test/test_client_server.rb
|
72
|
+
- zmqjsonrpc.gemspec
|
73
|
+
homepage: https://github.com/bisdn/zmqjsonrpc
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.0.14
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: ZeroMQ JSON RPC client and server.
|
97
|
+
test_files:
|
98
|
+
- test/test_client_server.rb
|