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
File without changes
File without changes
File without changes
@@ -0,0 +1,58 @@
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
+ if callback
46
+ callback.call(response.response)
47
+ end
48
+
49
+ @in_progress -= 1
50
+ end
51
+ end
52
+
53
+ def async?
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,55 @@
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
+
43
+ begin
44
+ @client.post(path, data, HEADERS).body
45
+ rescue EOFError
46
+ retry
47
+ end
48
+ end
49
+
50
+ def async?
51
+ false
52
+ end
53
+ end
54
+ end
55
+ end
File without changes
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require "uri"
4
+ require "socket"
5
+
6
+ module RPC
7
+ module Clients
8
+ class Socket
9
+ def initialize(uri)
10
+ @uri = URI.parse(uri)
11
+
12
+ # Localhost doesn't work for me for some reason.
13
+ @uri.host = "127.0.0.1" if @uri.host.eql?("localhost")
14
+ end
15
+
16
+ def connect
17
+ @client = TCPSocket.new(@uri.host, @uri.port)
18
+ rescue Errno::ECONNREFUSED
19
+ raise Errno::ECONNREFUSED.new("You have to start the server first!")
20
+ end
21
+
22
+ def disconnect
23
+ @client.close
24
+ end
25
+
26
+ def run(&block)
27
+ self.connect
28
+ block.call
29
+ self.disconnect
30
+ end
31
+
32
+ # TODO: support for notifications, probably refactor send to:
33
+ # def send(encoder, data)
34
+ # binary = encoder.encode(data)
35
+ # @client.puts(binary)
36
+ # @client.readline if data[:id]
37
+ # end
38
+ # ... and don't forget to add support for notifications to the example socket server!
39
+ def send(data)
40
+ @client.puts(data)
41
+ @client.readline
42
+ # TODO: sync vs. async: @socket.read or a callback and a loop
43
+ end
44
+
45
+ def async?
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,142 @@
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
+ data.to_json
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
+ data.to_json
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
+ data.to_json
74
+ end
75
+
76
+ # TODO: support batch
77
+ def decode(binary)
78
+ if binary.nil?
79
+ raise TypeError.new("#{self.class}#decode takes binary data as an argument, not nil!")
80
+ end
81
+
82
+ object = JSON.parse(binary)
83
+ RPC.log "CLIENT DECODE #{object.inspect}"
84
+ object
85
+ rescue JSON::ParserError => error
86
+ self.exception(error, -32600, "Invalid Request.")
87
+ end
88
+ end
89
+
90
+ class Server
91
+ include Errors
92
+
93
+ def decode(binary)
94
+ object = JSON.parse(binary)
95
+ RPC.log "SERVER DECODE #{object.inspect}"
96
+ object
97
+ rescue JSON::ParserError => error
98
+ # This is supposed to result in HTTP 500.
99
+ raise self.exception(error, -32700, "Parse error.")
100
+ end
101
+
102
+ def execute(encoded_result, subject)
103
+ result = self.decode(encoded_result)
104
+
105
+ if result.respond_to?(:merge) # Hash, only one result.
106
+ self.encode(result_or_error(subject, result))
107
+ else # Array, multiple results.
108
+ self.encode(
109
+ result.map do |result|
110
+ result_or_error(subject, result)
111
+ end
112
+ )
113
+ end
114
+ end
115
+
116
+ def result_or_error(subject, command)
117
+ method, args = command["method"], command["params"]
118
+ result = subject.send(method, *args)
119
+ self.response(result, nil, command["id"])
120
+ rescue NoMethodError => error
121
+ error = self.exception(error, -32601, "Method not found.")
122
+ self.response(nil, error, command["id"])
123
+ rescue ArgumentError => error
124
+ error = self.exception(error, -32602, "Invalid params.")
125
+ self.response(nil, error, command["id"])
126
+ rescue Exception => exception
127
+ error = self.exception(exception)
128
+ self.response(nil, error, command["id"])
129
+ end
130
+
131
+ def response(result, error, id)
132
+ {jsonrpc: JSON_RPC_VERSION, result: result, error: error, id: id}
133
+ end
134
+
135
+ def encode(response)
136
+ RPC.log "SERVER ENCODE: #{response.inspect}"
137
+ response.to_json
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
File without changes
@@ -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.3.1"
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 = []
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
@@ -0,0 +1,20 @@
1
+ # Standard
2
+ require 'syslog'
3
+
4
+ # Gems
5
+ require "rack/request"
6
+ require 'thin'
7
+
8
+ # Bundled RPC
9
+ $:.push(Pathname.new(File.dirname(__FILE__)).join('rpc', 'lib').to_path)
10
+ require 'rpc'
11
+
12
+ # Local
13
+ require_relative 'mobme/infrastructure/rpc/version'
14
+ require_relative 'mobme/infrastructure/rpc/error'
15
+ require_relative 'mobme/infrastructure/rpc/base'
16
+ require_relative 'mobme/infrastructure/rpc/adaptor'
17
+ require_relative 'mobme/infrastructure/rpc/runner'
18
+
19
+ # Alias the client too
20
+ SyncService::Client = RPC::Client
@@ -0,0 +1,104 @@
1
+ # Gems
2
+ require 'rspec'
3
+
4
+ # Local
5
+ require_relative 'spec_helper'
6
+ require_relative '../lib/async_service'
7
+
8
+ module MobME::Infrastructure::RPC
9
+ describe Adaptor do
10
+ let(:dummy_service_object) { double "DummyService" }
11
+ let(:dummy_server) { double(RPC::Server).as_null_object }
12
+
13
+ before :each do
14
+ RPC::Server.stub(:new).and_return(dummy_server)
15
+ end
16
+
17
+ subject { Adaptor.new dummy_service_object }
18
+
19
+ it "accepts service object" do
20
+ Adaptor.should respond_to(:new).with(1).argument
21
+ Adaptor.new dummy_service_object
22
+ end
23
+
24
+ it { should respond_to(:server).with(0).arguments }
25
+ it { should respond_to(:call).with(1).argument }
26
+ it { should respond_to(:response).with(2).arguments }
27
+
28
+ describe "#server" do
29
+ it "creates an instance of RPC::Server" do
30
+ RPC::Server.should_receive(:new).with(dummy_service_object)
31
+ subject.server
32
+ end
33
+
34
+ it "returns an instance of RPC::Server" do
35
+ subject.server.should be dummy_server
36
+ end
37
+
38
+ context "when called multiple times" do
39
+ it "returns the same (original) instance of RPC::Server" do
40
+ subject.server.should be dummy_server
41
+ second_dummy_server = double(RPC::Server)
42
+ RPC::Server.stub(:new).and_return(second_dummy_server)
43
+ subject.server.should be dummy_server
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#call" do
49
+ let(:dummy_environment) { double("Environment") }
50
+ let(:dummy_command) { double("Request Command") }
51
+ let(:dummy_body) { double("Request Body", :read => dummy_command) }
52
+ let(:dummy_request) { double(Rack::Request, :body => dummy_body) }
53
+
54
+ before :each do
55
+ Rack::Request.stub(:new).and_return(dummy_request)
56
+ subject.stub(:response)
57
+ end
58
+
59
+ it "creates a new rack request object" do
60
+ Rack::Request.should_receive(:new).with(dummy_environment)
61
+ subject.call(dummy_environment)
62
+ end
63
+
64
+ it "extracts command from request" do
65
+ dummy_request.should_receive(:body)
66
+ dummy_body.should_receive(:read)
67
+ subject.call(dummy_environment)
68
+ end
69
+
70
+ it "executes the extracted command" do
71
+ dummy_server.stub(:execute).and_return(double("Binary Response").as_null_object)
72
+ dummy_server.should_receive(:execute).with(dummy_command)
73
+ subject.call(dummy_environment)
74
+ end
75
+
76
+ context "when response from executed command contains string NoMethodError" do
77
+ it "calls response method with status 404" do
78
+ dummy_server.stub(:execute).and_return('NoMethodError')
79
+ subject.should_receive(:response).with(404, 'NoMethodError')
80
+ subject.call(dummy_environment)
81
+ end
82
+ end
83
+
84
+ context "when response from executed command does not contain NoMethodError" do
85
+ it "calls response method with status 200" do
86
+ dummy_server.stub(:execute).and_return('Method Exists!')
87
+ subject.should_receive(:response).with(200, 'Method Exists!')
88
+ subject.call(dummy_environment)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#response" do
94
+ it "returns response array acceptable to Rack" do
95
+ dummy_body = "Dummy Body Content"
96
+ headers = {
97
+ "Content-Type" => "application/json-rpc",
98
+ "Content-Length" => dummy_body.bytesize.to_s
99
+ }
100
+ subject.response(200, dummy_body).should == [200, headers, [dummy_body]]
101
+ end
102
+ end
103
+ end
104
+ end