sync_service 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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