rpc 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGELOG +7 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +28 -0
- data/LICENSE +20 -0
- data/README.textile +7 -0
- data/examples/em-http-request-json/client.rb +28 -0
- data/examples/net-http-json/client.rb +33 -0
- data/examples/net-http-json/console.rb +13 -0
- data/examples/server.ru +54 -0
- data/lib/rpc.rb +143 -0
- data/lib/rpc/clients/amqp/coolio.rb +0 -0
- data/lib/rpc/clients/amqp/eventmachine.rb +0 -0
- data/lib/rpc/clients/amqp/socket.rb +0 -0
- data/lib/rpc/clients/em-http-request.rb +55 -0
- data/lib/rpc/clients/net-http.rb +50 -0
- data/lib/rpc/clients/redis.rb +0 -0
- data/lib/rpc/encoders/json.rb +138 -0
- data/lib/rpc/encoders/xml.rb +0 -0
- data/rpc.gemspec +34 -0
- metadata +24 -6
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
source "http://gemcutter.org"
|
4
|
+
|
5
|
+
group(:examples) do
|
6
|
+
gem "rack"
|
7
|
+
end
|
8
|
+
|
9
|
+
group(:em) do
|
10
|
+
gem "em-http-request"
|
11
|
+
end
|
12
|
+
|
13
|
+
# group(:amqp) do
|
14
|
+
# gem "amq-client"
|
15
|
+
# end
|
16
|
+
|
17
|
+
group(:test) do
|
18
|
+
gem "rspec", ">=2.0.0"
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://gemcutter.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.5)
|
5
|
+
diff-lcs (1.1.2)
|
6
|
+
em-http-request (0.3.0)
|
7
|
+
addressable (>= 2.0.0)
|
8
|
+
escape_utils
|
9
|
+
eventmachine (>= 0.12.9)
|
10
|
+
escape_utils (0.2.3)
|
11
|
+
eventmachine (0.12.10)
|
12
|
+
rack (1.2.2)
|
13
|
+
rspec (2.5.0)
|
14
|
+
rspec-core (~> 2.5.0)
|
15
|
+
rspec-expectations (~> 2.5.0)
|
16
|
+
rspec-mocks (~> 2.5.0)
|
17
|
+
rspec-core (2.5.1)
|
18
|
+
rspec-expectations (2.5.0)
|
19
|
+
diff-lcs (~> 1.1.2)
|
20
|
+
rspec-mocks (2.5.0)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
ruby
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
em-http-request
|
27
|
+
rack
|
28
|
+
rspec (>= 2.0.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jakub Stastny aka botanicus
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
|
5
|
+
|
6
|
+
require "rpc"
|
7
|
+
|
8
|
+
RPC.logging = true
|
9
|
+
|
10
|
+
client = RPC::Clients::EmHttpRequest.new("http://127.0.0.1:8081")
|
11
|
+
|
12
|
+
RPC::Client.new(client) do |client|
|
13
|
+
# Get result of an existing method.
|
14
|
+
client.server_timestamp do |result, error|
|
15
|
+
puts "Server timestamp is #{result}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get result of a non-existing method via method_missing.
|
19
|
+
client.send(:+, 1) do |result, error|
|
20
|
+
puts "Method missing works: #{result}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Synchronous error handling.
|
24
|
+
client.buggy_method do |result, error|
|
25
|
+
STDERR.puts "EXCEPTION CAUGHT:"
|
26
|
+
STDERR.puts "#{error.class} #{error.message}"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
|
5
|
+
|
6
|
+
require "rpc"
|
7
|
+
|
8
|
+
RPC.logging = true
|
9
|
+
|
10
|
+
client = RPC::Client.setup("http://127.0.0.1:8081")
|
11
|
+
|
12
|
+
# Get result of an existing method.
|
13
|
+
puts "Server timestamp is #{client.server_timestamp}"
|
14
|
+
|
15
|
+
# Get result of a non-existing method via method_missing.
|
16
|
+
puts "Method missing works: #{client + 1}"
|
17
|
+
|
18
|
+
# Synchronous error handling.
|
19
|
+
begin
|
20
|
+
client.buggy_method
|
21
|
+
rescue Exception => exception
|
22
|
+
STDERR.puts "EXCEPTION CAUGHT: #{exception.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Notification isn't supported, because HTTP works in
|
26
|
+
# request/response mode, so it does behave in the same
|
27
|
+
# manner as RPC via method_missing.
|
28
|
+
puts "Sending a notification ..."
|
29
|
+
client.notification(:log, "Some shit.")
|
30
|
+
|
31
|
+
# Batch.
|
32
|
+
result = client.batch([[:log, ["Message"], nil], [:a_method, []]])
|
33
|
+
puts "Batch result: #{result}"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
|
5
|
+
|
6
|
+
require "rpc"
|
7
|
+
require "irb"
|
8
|
+
|
9
|
+
@client = RPC::Client.setup("http://127.0.0.1:8081")
|
10
|
+
|
11
|
+
puts "~ RPC Client initialised, use @client to access it."
|
12
|
+
|
13
|
+
IRB.start(__FILE__)
|
data/examples/server.ru
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env rackup --port 8081
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# http://groups.google.com/group/json-rpc/web/json-rpc-over-http
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
7
|
+
|
8
|
+
require "rpc"
|
9
|
+
require "rack/request"
|
10
|
+
|
11
|
+
RPC.logging = true
|
12
|
+
# RPC.development = true
|
13
|
+
|
14
|
+
class RpcRunner
|
15
|
+
def server
|
16
|
+
@server ||= RPC::Server.new(RemoteObject.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
request = Rack::Request.new(env)
|
21
|
+
command = request.body.read
|
22
|
+
binary = self.server.execute(command)
|
23
|
+
if binary.match(/NoMethodError/)
|
24
|
+
response(404, binary)
|
25
|
+
else
|
26
|
+
response(200, binary)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def response(status, body)
|
31
|
+
headers = {
|
32
|
+
"Content-Type" => "application/json-rpc",
|
33
|
+
"Content-Length" => body.bytesize.to_s}
|
34
|
+
[status, headers, [body]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class RemoteObject
|
39
|
+
def server_timestamp
|
40
|
+
Time.now.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def buggy_method
|
44
|
+
raise "It doesn't work!"
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(name, *args)
|
48
|
+
"[SERVER] received method #{name} with #{args.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
map("/") do
|
53
|
+
run RpcRunner.new
|
54
|
+
end
|
data/lib/rpc.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module RPC
|
4
|
+
module Clients
|
5
|
+
autoload :NetHttp, "rpc/clients/net-http"
|
6
|
+
autoload :EmHttpRequest, "rpc/clients/em-http-request"
|
7
|
+
end
|
8
|
+
|
9
|
+
module Encoders
|
10
|
+
autoload :Json, "rpc/encoders/json"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logging
|
14
|
+
@logging ||= $DEBUG
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.logging=(boolean)
|
18
|
+
@logging = boolean
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.log(message)
|
22
|
+
STDERR.puts(message) if self.logging
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.development=(boolean)
|
26
|
+
@development = boolean
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.development?
|
30
|
+
!! @development
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.full_const_get(const_name)
|
34
|
+
parts = const_name.sub(/^::/, "").split("::")
|
35
|
+
parts.reduce(Object) do |constant, part|
|
36
|
+
constant.const_get(part)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Server
|
41
|
+
def initialize(subject, encoder = RPC::Encoders::Json::Server.new)
|
42
|
+
@subject, @encoder = subject, encoder
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute(encoded_command)
|
46
|
+
@encoder.execute(encoded_command, @subject)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module ExceptionsMixin
|
51
|
+
attr_accessor :server_backtrace
|
52
|
+
|
53
|
+
# NOTE: We can't use super to get the client backtrace,
|
54
|
+
# because backtrace is generated only if there is none
|
55
|
+
# yet and because we are redefining the backtrace method,
|
56
|
+
# there always will be some backtrace.
|
57
|
+
def backtrace
|
58
|
+
@backtrace ||= begin
|
59
|
+
caller(3) + ["... server ..."] + self.server_backtrace
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Client < BasicObject
|
65
|
+
def self.setup(uri, client_class = Clients::NetHttp, encoder = Encoders::Json::Client.new)
|
66
|
+
client = client_class.new(uri)
|
67
|
+
self.new(client, encoder)
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(client, encoder = Encoders::Json::Client.new, &block)
|
71
|
+
@client, @encoder = client, encoder
|
72
|
+
|
73
|
+
if block
|
74
|
+
@client.run do
|
75
|
+
block.call(self)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@client.connect
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def notification(*args)
|
83
|
+
data = @encoder.notification(*args)
|
84
|
+
@client.send(data)
|
85
|
+
end
|
86
|
+
|
87
|
+
def batch(*args)
|
88
|
+
data = @encoder.batch(*args)
|
89
|
+
@client.send(data)
|
90
|
+
end
|
91
|
+
|
92
|
+
# 1) Sync: it'll return the value.
|
93
|
+
# 2) Async: you have to add #subscribe
|
94
|
+
def method_missing(method, *args, &callback)
|
95
|
+
binary = @encoder.encode(method, *args)
|
96
|
+
|
97
|
+
if @client.async?
|
98
|
+
@client.send(binary) do |encoded_result|
|
99
|
+
result = @encoder.decode(encoded_result)
|
100
|
+
callback.call(result["result"], get_exception(result["error"]))
|
101
|
+
end
|
102
|
+
else
|
103
|
+
::Kernel.raise("You can't specify callback for a synchronous client.") if callback
|
104
|
+
|
105
|
+
encoded_result = @client.send(binary)
|
106
|
+
result = @encoder.decode(encoded_result)
|
107
|
+
|
108
|
+
if result.respond_to?(:merge) # Hash, only one result.
|
109
|
+
result_or_raise(result)
|
110
|
+
else # Array, multiple results.
|
111
|
+
result.map do |result|
|
112
|
+
result_or_raise(result)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def result_or_raise(result)
|
119
|
+
if error = result["error"]
|
120
|
+
exception = self.get_exception(error)
|
121
|
+
::Kernel.raise(exception)
|
122
|
+
else
|
123
|
+
result["result"]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_exception(error)
|
128
|
+
return unless error
|
129
|
+
exception = error["error"]
|
130
|
+
resolved_class = ::RPC.full_const_get(exception["class"])
|
131
|
+
klass = resolved_class || ::RuntimeError
|
132
|
+
message = resolved_class ? exception["message"] : error["message"]
|
133
|
+
instance = klass.new(message)
|
134
|
+
instance.extend(::RPC::ExceptionsMixin)
|
135
|
+
instance.server_backtrace = exception["backtrace"]
|
136
|
+
instance
|
137
|
+
end
|
138
|
+
|
139
|
+
def close_connection
|
140
|
+
@client.disconnect
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# https://github.com/eventmachine/em-http-request
|
4
|
+
|
5
|
+
require "eventmachine"
|
6
|
+
require "em-http-request"
|
7
|
+
|
8
|
+
# Note that we support only HTTP POST. JSON-RPC can be done
|
9
|
+
# via HTTP GET as well, but since HTTP POST is the preferred
|
10
|
+
# method, I decided to implement only it. More info can is here:
|
11
|
+
# http://groups.google.com/group/json-rpc/web/json-rpc-over-http
|
12
|
+
|
13
|
+
module RPC
|
14
|
+
module Clients
|
15
|
+
class EmHttpRequest
|
16
|
+
HEADERS ||= {"Accept" => "application/json-rpc"}
|
17
|
+
|
18
|
+
def initialize(uri)
|
19
|
+
@client = EventMachine::HttpRequest.new(uri)
|
20
|
+
@in_progress = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def disconnect
|
27
|
+
end
|
28
|
+
|
29
|
+
def run(&block)
|
30
|
+
EM.run do
|
31
|
+
block.call
|
32
|
+
|
33
|
+
# Note: There's no way how to stop the
|
34
|
+
# reactor when there are no remaining events.
|
35
|
+
EM.add_periodic_timer(0.1) do
|
36
|
+
EM.stop if @in_progress == 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def send(data, &callback)
|
42
|
+
request = @client.post(head: HEADERS, body: data)
|
43
|
+
@in_progress += 1
|
44
|
+
request.callback do |response|
|
45
|
+
callback.call(response.response)
|
46
|
+
@in_progress -= 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def async?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
# Note that we support only HTTP POST. JSON-RPC can be done
|
6
|
+
# via HTTP GET as well, but since HTTP POST is the preferred
|
7
|
+
# method, I decided to implement only it. More info can is here:
|
8
|
+
# http://groups.google.com/group/json-rpc/web/json-rpc-over-http
|
9
|
+
|
10
|
+
module Net
|
11
|
+
autoload :HTTP, "net/http"
|
12
|
+
autoload :HTTPS, "net/https"
|
13
|
+
end
|
14
|
+
|
15
|
+
module RPC
|
16
|
+
module Clients
|
17
|
+
class NetHttp
|
18
|
+
HEADERS ||= {"Accept" => "application/json-rpc"}
|
19
|
+
|
20
|
+
def initialize(uri)
|
21
|
+
@uri = URI.parse(uri)
|
22
|
+
klass = Net.const_get(@uri.scheme.upcase)
|
23
|
+
@client = klass.new(@uri.host, @uri.port)
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect
|
27
|
+
@client.start
|
28
|
+
end
|
29
|
+
|
30
|
+
def disconnect
|
31
|
+
@client.finish
|
32
|
+
end
|
33
|
+
|
34
|
+
def run(&block)
|
35
|
+
self.connect
|
36
|
+
block.call
|
37
|
+
self.disconnect
|
38
|
+
end
|
39
|
+
|
40
|
+
def send(data)
|
41
|
+
path = @uri.path.empty? ? "/" : @uri.path
|
42
|
+
@client.post(path, data, HEADERS).body
|
43
|
+
end
|
44
|
+
|
45
|
+
def async?
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
File without changes
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# http://en.wikipedia.org/wiki/JSON-RPC
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "yajl/json_gem"
|
7
|
+
rescue LoadError
|
8
|
+
require "json"
|
9
|
+
end
|
10
|
+
|
11
|
+
module RPC
|
12
|
+
module Encoders
|
13
|
+
module Json
|
14
|
+
# This library works with JSON-RPC 2.0
|
15
|
+
# http://groups.google.com/group/json-rpc/web/json-rpc-2-0
|
16
|
+
JSON_RPC_VERSION ||= "2.0"
|
17
|
+
|
18
|
+
# http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html#ErrorObject
|
19
|
+
module Errors
|
20
|
+
# @note The exceptions are "eaten", because no client should be able to shut the server down.
|
21
|
+
def exception(exception, code = 000, message = "#{exception.class}: #{exception.message}")
|
22
|
+
unless RPC.development?
|
23
|
+
object = {class: exception.class.to_s, message: exception.message, backtrace: exception.backtrace}
|
24
|
+
self.error(message, code, object)
|
25
|
+
else
|
26
|
+
raise exception
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def error(message, code, object)
|
31
|
+
error = {name: "JSONRPCError", code: code, message: message, error: object}
|
32
|
+
RPC.log "ERROR #{message} (#{code}) #{error[:error].inspect}"
|
33
|
+
error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Request
|
38
|
+
attr_reader :data
|
39
|
+
def initialize(method, params, id = self.generate_id)
|
40
|
+
@data = {jsonrpc: JSON_RPC_VERSION, method: method, params: params}
|
41
|
+
@data.merge!(id: id) unless id.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_id
|
45
|
+
rand(999_999_999_999)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Client
|
50
|
+
include Errors
|
51
|
+
|
52
|
+
def encode(method, *args)
|
53
|
+
data = Request.new(method, args).data
|
54
|
+
RPC.log "CLIENT ENCODE #{data.inspect}"
|
55
|
+
JSON.generate(data)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Notifications are calls which don't require response.
|
59
|
+
# They look just the same, but they don't have any id.
|
60
|
+
def notification(method, *args)
|
61
|
+
data = Request.new(method, args, nil).data
|
62
|
+
RPC.log "CLIENT ENCODE NOTIFICATION #{data.inspect}"
|
63
|
+
JSON.generate(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Provide list of requests and notifications to run on the server.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# ["list", ["/"], ["clear", "logs", nil]]
|
70
|
+
def batch(requests)
|
71
|
+
data = requests.map { |request| Request.new(*request).data }
|
72
|
+
RPC.log "CLIENT ENCODE BATCH #{data.inspect}"
|
73
|
+
JSON.generate(data)
|
74
|
+
end
|
75
|
+
|
76
|
+
# TODO: support batch
|
77
|
+
def decode(binary)
|
78
|
+
object = JSON.parse(binary)
|
79
|
+
RPC.log "CLIENT DECODE #{object.inspect}"
|
80
|
+
object
|
81
|
+
rescue JSON::ParserError => error
|
82
|
+
self.exception(error, -32600, "Invalid Request.")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Server
|
87
|
+
include Errors
|
88
|
+
|
89
|
+
def decode(binary)
|
90
|
+
object = JSON.parse(binary)
|
91
|
+
RPC.log "SERVER DECODE #{object.inspect}"
|
92
|
+
object
|
93
|
+
rescue JSON::ParserError => error
|
94
|
+
# This is supposed to result in HTTP 500.
|
95
|
+
raise self.exception(error, -32700, "Parse error.")
|
96
|
+
end
|
97
|
+
|
98
|
+
def execute(encoded_result, subject)
|
99
|
+
result = self.decode(encoded_result)
|
100
|
+
|
101
|
+
if result.respond_to?(:merge) # Hash, only one result.
|
102
|
+
self.encode(result_or_error(subject, result))
|
103
|
+
else # Array, multiple results.
|
104
|
+
self.encode(
|
105
|
+
result.map do |result|
|
106
|
+
result_or_error(subject, result)
|
107
|
+
end
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def result_or_error(subject, command)
|
113
|
+
method, args = command["method"], command["params"]
|
114
|
+
result = subject.send(method, *args)
|
115
|
+
self.response(result, nil, command["id"])
|
116
|
+
rescue NoMethodError => error
|
117
|
+
error = self.exception(error, -32601, "Method not found.")
|
118
|
+
self.response(nil, error, command["id"])
|
119
|
+
rescue ArgumentError => error
|
120
|
+
error = self.exception(error, -32602, "Invalid params.")
|
121
|
+
self.response(nil, error, command["id"])
|
122
|
+
rescue Exception => exception
|
123
|
+
error = self.exception(exception)
|
124
|
+
self.response(nil, error, command["id"])
|
125
|
+
end
|
126
|
+
|
127
|
+
def response(result, error, id)
|
128
|
+
{jsonrpc: JSON_RPC_VERSION, result: result, error: error, id: id}
|
129
|
+
end
|
130
|
+
|
131
|
+
def encode(response)
|
132
|
+
RPC.log "SERVER ENCODE: #{response.inspect}"
|
133
|
+
JSON.generate(response)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
File without changes
|
data/rpc.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require "base64"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "rpc"
|
8
|
+
s.version = "0.2"
|
9
|
+
s.authors = ["Jakub Stastny aka botanicus"]
|
10
|
+
s.homepage = "http://github.com/ruby-amqp/rpc"
|
11
|
+
s.summary = "Generic RPC library for Ruby."
|
12
|
+
s.description = "#{s.summary} Currently it supports JSON-RPC over HTTP, support for AMQP and Redis will follow soon."
|
13
|
+
s.cert_chain = nil
|
14
|
+
s.email = Base64.decode64("c3Rhc3RueUAxMDFpZGVhcy5jeg==\n")
|
15
|
+
s.has_rdoc = true
|
16
|
+
|
17
|
+
# files
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# Ruby version
|
22
|
+
s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
|
23
|
+
|
24
|
+
begin
|
25
|
+
require "changelog"
|
26
|
+
rescue LoadError
|
27
|
+
warn "You have to have changelog gem installed for post install message"
|
28
|
+
else
|
29
|
+
s.post_install_message = CHANGELOG.new.version_changes
|
30
|
+
end
|
31
|
+
|
32
|
+
# RubyForge
|
33
|
+
s.rubyforge_project = "rpc"
|
34
|
+
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: rpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: "0.
|
5
|
+
version: "0.2"
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jakub Stastny aka botanicus
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain:
|
12
|
-
date: 2011-04
|
12
|
+
date: 2011-05-04 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -21,14 +21,32 @@ extensions: []
|
|
21
21
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
|
-
files:
|
25
|
-
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- CHANGELOG
|
27
|
+
- Gemfile
|
28
|
+
- Gemfile.lock
|
29
|
+
- LICENSE
|
30
|
+
- README.textile
|
31
|
+
- examples/em-http-request-json/client.rb
|
32
|
+
- examples/net-http-json/client.rb
|
33
|
+
- examples/net-http-json/console.rb
|
34
|
+
- examples/server.ru
|
35
|
+
- lib/rpc.rb
|
36
|
+
- lib/rpc/clients/amqp/coolio.rb
|
37
|
+
- lib/rpc/clients/amqp/eventmachine.rb
|
38
|
+
- lib/rpc/clients/amqp/socket.rb
|
39
|
+
- lib/rpc/clients/em-http-request.rb
|
40
|
+
- lib/rpc/clients/net-http.rb
|
41
|
+
- lib/rpc/clients/redis.rb
|
42
|
+
- lib/rpc/encoders/json.rb
|
43
|
+
- lib/rpc/encoders/xml.rb
|
44
|
+
- rpc.gemspec
|
26
45
|
has_rdoc: true
|
27
46
|
homepage: http://github.com/ruby-amqp/rpc
|
28
47
|
licenses: []
|
29
48
|
|
30
|
-
post_install_message: "[\e[32mVersion 0.
|
31
|
-
[\e[32mVersion 0.1\e[0m] [FEATURE] JSON-RPC encoder.\n"
|
49
|
+
post_install_message: "[\e[32mVersion 0.2\e[0m] [FEATURE] Full JSON-RPC 2.0 support (added batch, errors and notifications).\n"
|
32
50
|
rdoc_options: []
|
33
51
|
|
34
52
|
require_paths:
|