nasreddin 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14b6167ede8abe24650d6a757cf8559cc8e2855f
4
+ data.tar.gz: cb9108d4eb76c807370a626c736ece44260892ac
5
+ SHA512:
6
+ metadata.gz: 83bc41838bb86061abecba46b6d64f101d9135ce620f1d6fe05b22abaf713a571bc4707b2896f82436c1d128c11044394ffbb18250d0699ee9e5f573ef3dcb8a
7
+ data.tar.gz: 18314db0937c622bb89f01cd41e2d86b19a5830e386450b52bc2b867e81e50d95dd5dca6d6d875a3ea7f371cf156d0e56630ffabe0c4db79ad4cff7e575d05f5
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ > "Nasreddin, your donkey has been lost."
2
+
3
+ > "Thank goodness I was not on the donkey at the time, or I would be lost too."
4
+
5
+
6
+ ## What is Nasreddin?
7
+
8
+ Nasreddin Hoca was a wise scolar from 13th century Anatolia. This, however, is a library designed to enable know-nothing
9
+ distribution of data requests between varying services.
10
+
11
+ To connect two services, one needs to include the Nasreddin::APIServer middleware and indicate what resources it is offering:
12
+ ```ruby
13
+ require 'nasreddin/api-server'
14
+
15
+ use Nasreddin::APIServer, resources: %w| users |
16
+ run MyApp
17
+ ```
18
+ Then, the consumer can use the Nasreddin::Resource class to generate new classes that will talk to the APIServer when making
19
+ requests.
20
+
21
+
22
+ An example implementation of Nasreddin::Resource is provided below
23
+
24
+ ```ruby
25
+ require 'nasreddin/resource'
26
+
27
+ class User < Nasreddin::Resource('users') # name of endpoint for resource
28
+ end
29
+
30
+ ```
31
+
32
+
33
+ ## Caveats
34
+
35
+ Currently, Nasreddin relies on JRuby and TorqueBox/HornetQ.
36
+
37
+
38
+ ## License
39
+
40
+ Copyright (C) 2012-2013 Burnside Digital
41
+
42
+ Licensed under the BSD 2-Clause License. See COPYING for license details.
data/lib/nasreddin.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'nasreddin/version'
2
+ require 'nasreddin/resource'
3
+ require 'nasreddin/remote_torquebox_adapter'
4
+ require 'nasreddin/api-server'
@@ -0,0 +1,136 @@
1
+ require 'stringio'
2
+ require 'uri'
3
+
4
+ module Nasreddin
5
+
6
+ class APIServer
7
+
8
+ def initialize(app, options = {})
9
+ @app = app
10
+ @threads = {}
11
+ @resources = options[:resources]
12
+ @route_prefix = options[:route_prefix]
13
+ if @resources
14
+ @resources.each do |resource|
15
+ @threads[resource] = Thread.new { Nasreddin::APIServerResource.new(@route_prefix,resource,@app).run }
16
+ end
17
+ else
18
+ $stderr.puts "WARNING: Nasreddin::APIServer is being used without any resources specified!"
19
+ end
20
+ end
21
+
22
+ def call(env)
23
+ params = Rack::Request.new(env).params
24
+
25
+ if is_heartbeat?(params)
26
+ res = params['resource'] = params['resources'].pop
27
+ params["resources.#{res}"] = @resources
28
+ Nasreddin::Resource(@resource).remote_call(params)
29
+ else
30
+ @app.call(env)
31
+ end
32
+ end
33
+
34
+ def is_heartbeat?(params)
35
+ params.has_key? '__hearbeat__'
36
+ end
37
+ end
38
+
39
+ class APIServerResource
40
+
41
+ DEFAULT_ENV = {
42
+ 'rack.errors' => $stderr,
43
+ 'rack.input' => StringIO.new,
44
+ 'rack.version' => [1, 1],
45
+ 'rack.multithread' => true,
46
+ 'rack.multiprocess' => true,
47
+ 'rack.run_once' => false,
48
+ 'HTTP_ACCEPT' => 'application/json',
49
+ 'HTTP_USER_AGENT' => 'NasreddinAPI'
50
+ }
51
+
52
+ def initialize(route_prefix,resource,app)
53
+ @resource = resource
54
+ @route_prefix = route_prefix
55
+ @app = app
56
+ end
57
+
58
+ def queue
59
+ @queue ||= TorqueBox::Messaging::Queue.start("/queues/#{@resource}", durable: false)
60
+ end
61
+
62
+ def run
63
+ loop do
64
+ begin
65
+ queue.receive_and_publish &method(:process_incoming_message)
66
+ rescue Exception => err
67
+ $stderr.puts "Error processing request: #{err.class} - #{err.message}"
68
+ if err.kind_of?(Java::JavaxJms::JMSException)
69
+ break
70
+ end
71
+ end
72
+ end
73
+ queue.stop
74
+ end
75
+
76
+ def is_heartbeat?(msg)
77
+ msg[:params] && msg[:params].has_key?('__heartbeat__')
78
+ end
79
+
80
+ def call(env)
81
+ @app.call(env)
82
+ end
83
+
84
+ def process_incoming_message(msg)
85
+ return heartbeat_ok if is_heartbeat?(msg)
86
+
87
+ begin
88
+ status, headers, body = call(env(msg))
89
+
90
+ resp = ''
91
+ body.each { |d| resp += d.to_s }
92
+ body.close if body.respond_to?(:close)
93
+
94
+ rescue Exception => err
95
+ resp = "#{err.message}\n\n#{err.backtrace.join("\n")}"
96
+ end
97
+
98
+ [status, headers, resp]
99
+ end
100
+
101
+ def heartbeat_ok
102
+ [200, nil, "OK"]
103
+ end
104
+
105
+ def env(msg)
106
+ env = DEFAULT_ENV.clone
107
+ env['rack.url_scheme'] = (msg.delete(:secure) ? 'https' : 'http')
108
+ method = msg.delete(:method) || 'GET'
109
+ env['REQUEST_METHOD'] = method.to_s.upcase
110
+ env['QUERY_STRING'] = queryize(msg.delete(:params)) || ''
111
+ env['SCRIPT_NAME'] = "/#{@route_prefix}" || ''
112
+ env['PATH_INFO'] = "/#{@resource}"
113
+ if ((id = msg.delete(:id)))
114
+ env['PATH_INFO'] += "/#{id}"
115
+ end
116
+ if ((path = msg.delete(:path)))
117
+ env['PATH_INFO'] += "/#{path}"
118
+ end
119
+ env['REQUEST_URI'] = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
120
+ env.merge!(Hash[msg.map { |k, v| [k.to_s, v] }])
121
+ env
122
+ end
123
+
124
+ private
125
+
126
+ def queryize(params = {})
127
+ params.map do |key, value|
128
+ if value.is_a? Array
129
+ value.map { |v| URI.encode("#{key}[]=#{v}") }
130
+ else
131
+ URI.encode("#{key}=#{value}")
132
+ end
133
+ end.join('&') unless params.nil?
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,38 @@
1
+ #require 'torquebox/messaging'
2
+ require 'multi_json'
3
+
4
+ module Nasreddin
5
+ class RemoteTorqueboxAdapter
6
+ attr_accessor :resource, :klass
7
+
8
+ def load_data(data,resource, as_objects = true)
9
+ resp = MultiJson.load(data)
10
+ resp = resp[@resource] if resp.respond_to?(:keys) && resp.keys.include?(@resource)
11
+ if resp.kind_of? Array
12
+ as_objects ? resp.map { |r| @klass.new(r) } : resp
13
+ else
14
+ as_objects ? @klass.new(resp) : resp
15
+ end
16
+ end
17
+
18
+ def succeded?(status)
19
+ status != nil && status > 199 && status < 300
20
+ end
21
+
22
+ def queue
23
+ @queue ||= TorqueBox::Messaging::Queue.start("/queues/#{@resource}", durable: false)
24
+ end
25
+
26
+ def call(params, as_new_objects=false)
27
+ status, _, data = *(queue.publish_and_receive(params, persistant: false))
28
+ values = load_data(data,@resource,as_new_objects) if data && !data.empty?
29
+ [ succeded?(status), values ]
30
+ end
31
+
32
+ def initialize(resource, klass)
33
+ @resource = resource
34
+ @klass = klass
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,175 @@
1
+ require 'torquebox/messaging'
2
+ require 'multi_json'
3
+
4
+ module Nasreddin
5
+
6
+ class SaveError < Exception ; end
7
+
8
+ # == Nasreddin Resource
9
+ # Provides a base class for implementing an API backed data object.
10
+ # A minimal implementation could be:
11
+ # class Car < Nasreddin::Resource('cars')
12
+ # end
13
+ class Resource
14
+ class<<self
15
+ attr_accessor :resource
16
+
17
+ def subclasses; (@subclasses ||= []); end
18
+
19
+ def inherited(sub)
20
+ subclasses << sub
21
+ sub.resource = @resource
22
+ end
23
+
24
+ def remote
25
+ @remote ||= Nasreddin::RemoteTorqueboxAdapter.new(@resource, self)
26
+ end
27
+
28
+ def remote_call(params = {}, as_objects=true)
29
+ success, values = remote.call(params, as_objects)
30
+ values
31
+ end
32
+
33
+ # Allows fetching of all entities without requiring filtering
34
+ # parameters.
35
+ def all
36
+ remote_call({ method: 'GET' }, true)
37
+ end
38
+
39
+ # Allows searching for a specific entity or a collection of
40
+ # entities that match a certain criteria.
41
+ # example usage:
42
+ # Car.find(15)
43
+ # # => #<Car:0x5fafa486>
44
+ #
45
+ # Car.find(make: 'Ford')
46
+ # # => [ #<Car:0x5fafa486> ]
47
+ #
48
+ # Car.find(15, make: 'Ford')
49
+ # # => [ #<Car:0x5fafa486> ]
50
+ def find(*args)
51
+ params = args.last.kind_of?(Hash) ? args.pop : {}
52
+ id = args.shift
53
+
54
+ remote_call({ method: 'GET', id: id, params: params })
55
+ end
56
+
57
+ # Allows creating a new record in one shot
58
+ # returns true if the record was created
59
+ # example usage:
60
+ # Car.create make: 'Ford', model: 'Focus'
61
+ # # => true or false
62
+ def create(properties = {})
63
+ new(properties).save
64
+ end
65
+
66
+ # Allows destroying a resource without finding it
67
+ # example usage:
68
+ # Car.destroy(15)
69
+ # # => true or false
70
+ def destroy(id)
71
+ remote_call({ method: 'DELETE', id: id }, false).empty?
72
+ end
73
+ end
74
+
75
+ # Custom to_json implementation
76
+ # passes through options to @data
77
+ def to_json(options={})
78
+ @data.to_json(options)
79
+ end
80
+
81
+ # Custom as_json implementation
82
+ # passes through options to @data
83
+ def as_json(options={})
84
+ @data.as_json(options)
85
+ end
86
+
87
+ # Checks if the current instance has
88
+ # already been deleted
89
+ def deleted?
90
+ @deleted
91
+ end
92
+
93
+ # Saves the current resource instance
94
+ # if the instance has an ID it sends a PUT request
95
+ # otherwise it sends a POST request
96
+ # will raise an error if the object has been deleted
97
+ # example usage:
98
+ # car = Car.find(15)
99
+ # car.miles += 1500
100
+ # car.save
101
+ # # => true or false
102
+ def save
103
+ raise SaveError.new("Cannot save a deleted resource") if deleted?
104
+
105
+ if @data['id'].to_s.empty?
106
+ remote_call({ method: 'POST', params: @data })
107
+ else
108
+ remote_call({ method: 'PUT', id: @data['id'], params: @data })
109
+ end
110
+ end
111
+
112
+ def remote_call(params)
113
+ success, values = remote.call(params, false)
114
+ @data = values if values && !values.empty?
115
+ success
116
+ end
117
+
118
+ # Destroys the current resource instance
119
+ # example usage:
120
+ # car = Car.find(15)
121
+ # car.destroy
122
+ # # => true or false
123
+ def destroy
124
+ if !deleted?
125
+ @deleted = remote_call({ method: 'DELETE', id: @data['id'] })
126
+ end
127
+ end
128
+
129
+ # Initialize a new instance
130
+ # also defines setters for any values given
131
+ # example usage:
132
+ # car = Car.new make: 'Ford', model: 'Mustang'
133
+ # car.respond_to? :make=
134
+ # # => true
135
+ def initialize(data={})
136
+ @deleted = false
137
+ @data = data
138
+ @data.each do |key, value|
139
+ unless respond_to?("#{key.to_s}=")
140
+ self.class.send(:define_method, "#{key.to_s}=") do |other|
141
+ @data[key.to_s] = other
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def method_missing(mid, *args, &block)
148
+ if @data.keys.include?(mid.to_s)
149
+ @data[mid.to_s]
150
+ else
151
+ super
152
+ end
153
+ end
154
+
155
+ def respond_to?(mid, include_private=false)
156
+ @data.keys.include?(mid.to_s) || super
157
+ end
158
+
159
+ private
160
+
161
+ def remote
162
+ self.class.send(:remote)
163
+ end
164
+
165
+ end
166
+
167
+ def self.Resource(name)
168
+ klass = Resource.subclasses.find { |k| k.resource == name }
169
+ unless klass
170
+ klass = Class.new(Resource)
171
+ klass.resource = name
172
+ end
173
+ klass
174
+ end
175
+ end
@@ -0,0 +1,3 @@
1
+ module Nasreddin
2
+ VERSION = '0.3.10'
3
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nasreddin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.10
5
+ platform: ruby
6
+ authors:
7
+ - Josh Ballanco
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: torquebox
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: '2.0'
25
+ prerelease: false
26
+ type: :runtime
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.1
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: 1.4.1
39
+ prerelease: false
40
+ type: :runtime
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.5.0
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: 1.5.0
53
+ prerelease: false
54
+ type: :runtime
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.1
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 0.6.1
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: torquebox-server
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: '2.0'
81
+ prerelease: false
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ name: kramdown
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 0.13.7
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ version: 0.13.7
95
+ prerelease: false
96
+ type: :development
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 0.8.2
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ version: 0.8.2
109
+ prerelease: false
110
+ type: :development
111
+ - !ruby/object:Gem::Dependency
112
+ name: bacon
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.1.0
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ~>
121
+ - !ruby/object:Gem::Version
122
+ version: 1.1.0
123
+ prerelease: false
124
+ type: :development
125
+ - !ruby/object:Gem::Dependency
126
+ name: mocha-on-bacon
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ prerelease: false
138
+ type: :development
139
+ description: ' Nasreddin is a library to make distributed calls via HornetQ '
140
+ email:
141
+ - jballanc@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ./lib/nasreddin.rb
147
+ - ./lib/nasreddin/api-server.rb
148
+ - ./lib/nasreddin/remote_torquebox_adapter.rb
149
+ - ./lib/nasreddin/resource.rb
150
+ - ./lib/nasreddin/version.rb
151
+ - README.md
152
+ homepage: ''
153
+ licenses: []
154
+ metadata: {}
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - '>='
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubyforge_project: ''
171
+ rubygems_version: 2.1.10
172
+ signing_key:
173
+ specification_version: 4
174
+ summary: A library for making distributed calls via HornetQ
175
+ test_files: []
176
+ has_rdoc: