sync_service 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +5 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/Guardfile +5 -0
- data/README.md +66 -0
- data/Rakefile +29 -0
- data/examples/application.rb +17 -0
- data/examples/client.rb +29 -0
- data/examples/config.ru +7 -0
- data/examples/php/client.php +17 -0
- data/examples/php/jsonRPCClient.php +165 -0
- data/examples/python/README +5 -0
- data/examples/python/client.py +16 -0
- data/examples/server.rb +4 -0
- data/lib/mobme/infrastructure/rpc/adaptor.rb +31 -0
- data/lib/mobme/infrastructure/rpc/base.rb +15 -0
- data/lib/mobme/infrastructure/rpc/error.rb +3 -0
- data/lib/mobme/infrastructure/rpc/runner.rb +21 -0
- data/lib/mobme/infrastructure/rpc/version.rb +14 -0
- data/lib/rpc/.gitignore +2 -0
- data/lib/rpc/CHANGELOG +10 -0
- data/lib/rpc/Gemfile +19 -0
- data/lib/rpc/LICENSE +20 -0
- data/lib/rpc/README.textile +7 -0
- data/lib/rpc/examples/em-http-request-json/client.rb +39 -0
- data/lib/rpc/examples/helpers.rb +15 -0
- data/lib/rpc/examples/net-http-json/client.rb +34 -0
- data/lib/rpc/examples/net-http-json/console.rb +13 -0
- data/lib/rpc/examples/server.ru +42 -0
- data/lib/rpc/examples/socket-json/client.rb +36 -0
- data/lib/rpc/examples/socket-json/server.rb +41 -0
- data/lib/rpc/lib/rpc.rb +166 -0
- data/lib/rpc/lib/rpc/clients/amqp/coolio.rb +0 -0
- data/lib/rpc/lib/rpc/clients/amqp/eventmachine.rb +0 -0
- data/lib/rpc/lib/rpc/clients/amqp/socket.rb +0 -0
- data/lib/rpc/lib/rpc/clients/em-http-request.rb +58 -0
- data/lib/rpc/lib/rpc/clients/net-http.rb +55 -0
- data/lib/rpc/lib/rpc/clients/redis.rb +0 -0
- data/lib/rpc/lib/rpc/clients/socket.rb +50 -0
- data/lib/rpc/lib/rpc/encoders/json.rb +142 -0
- data/lib/rpc/lib/rpc/encoders/xml.rb +0 -0
- data/lib/rpc/rpc.gemspec +34 -0
- data/lib/sync_service.rb +20 -0
- data/spec/adaptor_spec.rb +104 -0
- data/spec/base_spec.rb +61 -0
- data/spec/error_spec.rb +14 -0
- data/spec/runner_spec.rb +31 -0
- data/spec/spec_helper.rb +9 -0
- data/sync_service.gemspec +32 -0
- metadata +218 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module MobME::Infrastructure::RPC
|
2
|
+
class Runner
|
3
|
+
def self.start(application, host, port, route)
|
4
|
+
begin
|
5
|
+
require "thin"
|
6
|
+
rescue LoadError
|
7
|
+
puts "Thin must be installed to use the server, please add thin to your Gemfile"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
Thin::Server.start(host, port) do
|
12
|
+
# Since no logger is specified, this will log apache-style strings to STDERR for each request.
|
13
|
+
use Rack::CommonLogger
|
14
|
+
|
15
|
+
map route do
|
16
|
+
run Adaptor.new(application)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/rpc/.gitignore
ADDED
data/lib/rpc/CHANGELOG
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
= Version 0.3
|
2
|
+
* [FEATURE] Added TCP/IP communication layer.
|
3
|
+
|
4
|
+
= Version 0.2
|
5
|
+
* [FEATURE] Full JSON-RPC 2.0 support (added batch, errors and notifications).
|
6
|
+
|
7
|
+
= Version 0.1
|
8
|
+
* [FEATURE] Net::HTTP(S) client.
|
9
|
+
* [FEATURE] EM HTTP Request client.
|
10
|
+
* [FEATURE] JSON-RPC encoder.
|
data/lib/rpc/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/lib/rpc/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.
|
@@ -0,0 +1,39 @@
|
|
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
|
+
|
29
|
+
# Notification isn't supported, because HTTP works in
|
30
|
+
# request/response mode, so it does behave in the same
|
31
|
+
# manner as RPC via method_missing. Sense of this is
|
32
|
+
# only to check, that it won't blow up.
|
33
|
+
puts "Sending a notification ..."
|
34
|
+
client.notification(:log, "Some shit.")
|
35
|
+
|
36
|
+
# Batch.
|
37
|
+
result = client.batch([[:log, ["Message"], nil], [:a_method, []]])
|
38
|
+
puts "Batch result: #{result}"
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class RemoteObject
|
4
|
+
def server_timestamp
|
5
|
+
Time.now.to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def buggy_method
|
9
|
+
raise "This exception is expected."
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
"[SERVER] received method #{name} with #{args.inspect}"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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. Sense of this is
|
28
|
+
# only to check, that it won't blow up.
|
29
|
+
puts "Sending a notification ..."
|
30
|
+
client.notification(:log, "Some shit.")
|
31
|
+
|
32
|
+
# Batch.
|
33
|
+
result = client.batch([[:log, ["Message"], nil], [:a_method, []]])
|
34
|
+
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__)
|
@@ -0,0 +1,42 @@
|
|
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
|
+
require_relative "helpers"
|
12
|
+
|
13
|
+
RPC.logging = true
|
14
|
+
# RPC.development = true
|
15
|
+
|
16
|
+
class RpcRunner
|
17
|
+
def server
|
18
|
+
@server ||= RPC::Server.new(RemoteObject.new)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
request = Rack::Request.new(env)
|
23
|
+
command = request.body.read
|
24
|
+
binary = self.server.execute(command)
|
25
|
+
if binary.match(/NoMethodError/)
|
26
|
+
response(404, binary)
|
27
|
+
else
|
28
|
+
response(200, binary)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def response(status, body)
|
33
|
+
headers = {
|
34
|
+
"Content-Type" => "application/json-rpc",
|
35
|
+
"Content-Length" => body.bytesize.to_s}
|
36
|
+
[status, headers, [body]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
map("/") do
|
41
|
+
run RpcRunner.new
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
# TODO: the second argument could be +/- guessed (amqp -> amqp adapter, ip -> socket)
|
11
|
+
# TODO: IP isn't any existing URI scheme, on the other hand there are so many not existing URI schemes today that I don't think it matters (rsync://, git://, javascript:// and I could go on)
|
12
|
+
client = RPC::Client.setup("ip://localhost:2200", RPC::Clients::Socket)
|
13
|
+
|
14
|
+
# Get result of an existing method.
|
15
|
+
puts "Server timestamp is #{client.server_timestamp}"
|
16
|
+
|
17
|
+
# Get result of a non-existing method via method_missing.
|
18
|
+
puts "Method missing works: #{client + 1}"
|
19
|
+
|
20
|
+
# Synchronous error handling.
|
21
|
+
begin
|
22
|
+
client.buggy_method
|
23
|
+
rescue Exception => exception
|
24
|
+
STDERR.puts "EXCEPTION CAUGHT: #{exception.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Notification isn't supported, because HTTP works in
|
28
|
+
# request/response mode, so it does behave in the same
|
29
|
+
# manner as RPC via method_missing. Sense of this is
|
30
|
+
# only to check, that it won't blow up.
|
31
|
+
puts "Sending a notification ..."
|
32
|
+
client.notification(:log, "Some shit.")
|
33
|
+
|
34
|
+
# Batch.
|
35
|
+
result = client.batch([[:log, ["Message"], nil], [:a_method, []]])
|
36
|
+
puts "Batch result: #{result}"
|
@@ -0,0 +1,41 @@
|
|
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 "socket"
|
8
|
+
|
9
|
+
require_relative "../helpers"
|
10
|
+
|
11
|
+
RPC.logging = true
|
12
|
+
# RPC.development = true
|
13
|
+
|
14
|
+
# Helpers.
|
15
|
+
def wait_for_client(server_socket)
|
16
|
+
# Block for incoming connection from client by accept method.
|
17
|
+
# The return value of accept contains the new client socket
|
18
|
+
# object and the remote socket address
|
19
|
+
client, client_sockaddr = server_socket.accept
|
20
|
+
|
21
|
+
trap(:TERM) { server_socket.close }
|
22
|
+
|
23
|
+
[client, client_sockaddr]
|
24
|
+
end
|
25
|
+
|
26
|
+
server_socket = TCPServer.new("127.0.0.1", 2200)
|
27
|
+
|
28
|
+
client, client_sockaddr = wait_for_client(server_socket)
|
29
|
+
|
30
|
+
server = RPC::Server.new(RemoteObject.new)
|
31
|
+
|
32
|
+
begin
|
33
|
+
while data = client.readline.chomp
|
34
|
+
result = server.execute(data)
|
35
|
+
client.puts(result)
|
36
|
+
end
|
37
|
+
rescue Errno::ECONNRESET, EOFError
|
38
|
+
# This occurs when client closes the connection. We can safely ignore it.
|
39
|
+
client, client_sockaddr = wait_for_client(server_socket)
|
40
|
+
retry
|
41
|
+
end
|
data/lib/rpc/lib/rpc.rb
ADDED
@@ -0,0 +1,166 @@
|
|
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
|
+
autoload :Socket, "rpc/clients/socket"
|
8
|
+
end
|
9
|
+
|
10
|
+
module Encoders
|
11
|
+
autoload :Json, "rpc/encoders/json"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.logging
|
15
|
+
@logging ||= $DEBUG
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.logging=(boolean)
|
19
|
+
@logging = boolean
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.log(message)
|
23
|
+
STDERR.puts(message) if self.logging
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.development=(boolean)
|
27
|
+
@development = boolean
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.development?
|
31
|
+
!! @development
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.full_const_get(const_name)
|
35
|
+
parts = const_name.sub(/^::/, "").split("::")
|
36
|
+
parts.reduce(Object) do |constant, part|
|
37
|
+
constant.const_get(part)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Server
|
42
|
+
def initialize(subject, encoder = RPC::Encoders::Json::Server.new)
|
43
|
+
@subject, @encoder = subject, encoder
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(encoded_command)
|
47
|
+
@encoder.execute(encoded_command, @subject)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module ExceptionsMixin
|
52
|
+
attr_accessor :server_backtrace
|
53
|
+
|
54
|
+
# NOTE: We can't use super to get the client backtrace,
|
55
|
+
# because backtrace is generated only if there is none
|
56
|
+
# yet and because we are redefining the backtrace method,
|
57
|
+
# there always will be some backtrace.
|
58
|
+
def backtrace
|
59
|
+
@backtrace ||= begin
|
60
|
+
caller(3) + ["... server ..."] + self.server_backtrace
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Client < BasicObject
|
66
|
+
def self.setup(uri, client_class = Clients::NetHttp, encoder = Encoders::Json::Client.new)
|
67
|
+
client = client_class.new(uri)
|
68
|
+
self.new(client, encoder)
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(client, encoder = Encoders::Json::Client.new, &block)
|
72
|
+
@client, @encoder = client, encoder
|
73
|
+
|
74
|
+
if block
|
75
|
+
@client.run do
|
76
|
+
block.call(self)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
@client.connect
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def notification(*args)
|
84
|
+
data = @encoder.notification(*args)
|
85
|
+
@client.send(data)
|
86
|
+
end
|
87
|
+
|
88
|
+
def batch(*args)
|
89
|
+
data = @encoder.batch(*args)
|
90
|
+
@client.send(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
# 1) Sync: it'll return the value.
|
94
|
+
# 2) Async: you have to add #subscribe
|
95
|
+
|
96
|
+
# TODO: this should be refactored and moved to the encoder,
|
97
|
+
# because result["error"] and similar are JSON-RPC-specific.
|
98
|
+
def method_missing(method, *args, &callback)
|
99
|
+
binary = @encoder.encode(method, *args)
|
100
|
+
|
101
|
+
if @client.async? && ! callback # Assume notification.
|
102
|
+
@client.send(binary)
|
103
|
+
elsif @client.async? && callback
|
104
|
+
@client.send(binary) do |encoded_result|
|
105
|
+
result = @encoder.decode(encoded_result)
|
106
|
+
|
107
|
+
if result.respond_to?(:merge) # Hash, only one result.
|
108
|
+
callback.call(result["result"], get_exception(result["error"]))
|
109
|
+
else # Array, multiple results.
|
110
|
+
result.map do |result|
|
111
|
+
callback.call(result["result"], get_exception(result["error"]))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
::Kernel.raise("You can't specify callback for a synchronous client.") if callback
|
117
|
+
|
118
|
+
encoded_result = @client.send(binary)
|
119
|
+
|
120
|
+
if encoded_result.nil?
|
121
|
+
::Kernel.raise("Bug in #{@client.class}#send(data), it can never return nil in the sync mode!")
|
122
|
+
end
|
123
|
+
|
124
|
+
result = @encoder.decode(encoded_result)
|
125
|
+
|
126
|
+
if result.respond_to?(:merge) # Hash, only one result.
|
127
|
+
result_or_raise(result)
|
128
|
+
else # Array, multiple results.
|
129
|
+
result.map do |result|
|
130
|
+
result_or_raise(result)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def result_or_raise(result)
|
137
|
+
if error = result["error"]
|
138
|
+
exception = self.get_exception(error)
|
139
|
+
::Kernel.raise(exception)
|
140
|
+
else
|
141
|
+
result["result"]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_exception(error)
|
146
|
+
return unless error
|
147
|
+
exception = error["error"]
|
148
|
+
resolved_class = ::RPC.full_const_get(exception["class"])
|
149
|
+
klass = resolved_class || ::RuntimeError
|
150
|
+
message = resolved_class ? exception["message"] : error["message"]
|
151
|
+
case klass.instance_method(:initialize).arity
|
152
|
+
when 2
|
153
|
+
instance = klass.new(message, nil)
|
154
|
+
else
|
155
|
+
instance = klass.new(message)
|
156
|
+
end
|
157
|
+
instance.extend(::RPC::ExceptionsMixin)
|
158
|
+
instance.server_backtrace = exception["backtrace"]
|
159
|
+
instance
|
160
|
+
end
|
161
|
+
|
162
|
+
def close_connection
|
163
|
+
@client.disconnect
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|