sync_service 0.0.8

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.
Files changed (51) hide show
  1. data/.autotest +5 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +5 -0
  6. data/README.md +66 -0
  7. data/Rakefile +29 -0
  8. data/examples/application.rb +17 -0
  9. data/examples/client.rb +29 -0
  10. data/examples/config.ru +7 -0
  11. data/examples/php/client.php +17 -0
  12. data/examples/php/jsonRPCClient.php +165 -0
  13. data/examples/python/README +5 -0
  14. data/examples/python/client.py +16 -0
  15. data/examples/server.rb +4 -0
  16. data/lib/mobme/infrastructure/rpc/adaptor.rb +31 -0
  17. data/lib/mobme/infrastructure/rpc/base.rb +15 -0
  18. data/lib/mobme/infrastructure/rpc/error.rb +3 -0
  19. data/lib/mobme/infrastructure/rpc/runner.rb +21 -0
  20. data/lib/mobme/infrastructure/rpc/version.rb +14 -0
  21. data/lib/rpc/.gitignore +2 -0
  22. data/lib/rpc/CHANGELOG +10 -0
  23. data/lib/rpc/Gemfile +19 -0
  24. data/lib/rpc/LICENSE +20 -0
  25. data/lib/rpc/README.textile +7 -0
  26. data/lib/rpc/examples/em-http-request-json/client.rb +39 -0
  27. data/lib/rpc/examples/helpers.rb +15 -0
  28. data/lib/rpc/examples/net-http-json/client.rb +34 -0
  29. data/lib/rpc/examples/net-http-json/console.rb +13 -0
  30. data/lib/rpc/examples/server.ru +42 -0
  31. data/lib/rpc/examples/socket-json/client.rb +36 -0
  32. data/lib/rpc/examples/socket-json/server.rb +41 -0
  33. data/lib/rpc/lib/rpc.rb +166 -0
  34. data/lib/rpc/lib/rpc/clients/amqp/coolio.rb +0 -0
  35. data/lib/rpc/lib/rpc/clients/amqp/eventmachine.rb +0 -0
  36. data/lib/rpc/lib/rpc/clients/amqp/socket.rb +0 -0
  37. data/lib/rpc/lib/rpc/clients/em-http-request.rb +58 -0
  38. data/lib/rpc/lib/rpc/clients/net-http.rb +55 -0
  39. data/lib/rpc/lib/rpc/clients/redis.rb +0 -0
  40. data/lib/rpc/lib/rpc/clients/socket.rb +50 -0
  41. data/lib/rpc/lib/rpc/encoders/json.rb +142 -0
  42. data/lib/rpc/lib/rpc/encoders/xml.rb +0 -0
  43. data/lib/rpc/rpc.gemspec +34 -0
  44. data/lib/sync_service.rb +20 -0
  45. data/spec/adaptor_spec.rb +104 -0
  46. data/spec/base_spec.rb +61 -0
  47. data/spec/error_spec.rb +14 -0
  48. data/spec/runner_spec.rb +31 -0
  49. data/spec/spec_helper.rb +9 -0
  50. data/sync_service.gemspec +32 -0
  51. metadata +218 -0
@@ -0,0 +1,15 @@
1
+ module MobME::Infrastructure::RPC
2
+ class Base
3
+ def self.service_name
4
+ @service_name
5
+ end
6
+
7
+ def logger
8
+ unless Syslog.opened?
9
+ Syslog.open(self.class.service_name, Syslog::LOG_PID | Syslog::LOG_CONS)
10
+ end
11
+
12
+ Syslog
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module MobME::Infrastructure::RPC
2
+ class Error < StandardError; end
3
+ end
@@ -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
@@ -0,0 +1,14 @@
1
+ module MobME
2
+ module Infrastructure
3
+ module RPC
4
+ VERSION = '0.0.8'
5
+ class Adaptor; end
6
+ class Runner; end
7
+ class Base; end
8
+ class Error < StandardError; end
9
+ end
10
+ end
11
+ end
12
+
13
+ # Alias it
14
+ SyncService = MobME::Infrastructure::RPC
@@ -0,0 +1,2 @@
1
+ .rvmrc
2
+ /*.gem
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,7 @@
1
+ h1. About
2
+
3
+ Generic RPC library for Ruby. Currently it supports JSON-RPC over HTTP, support for AMQP and Redis will follow soon.
4
+
5
+ h1. Usage
6
+
7
+ See examples.
@@ -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
@@ -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