carnivore-http 0.2.8 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2aeabfa6f6dc0811a7cdeb3249e701a58e9eb899
4
- data.tar.gz: 7d01fd0e327f4d9172dd064e556482456a6b1483
3
+ metadata.gz: 9595b652c27534813c2b972ca1d51be428cb3ac7
4
+ data.tar.gz: c279093c9392ec09668b29c57603e4b0aca6c91c
5
5
  SHA512:
6
- metadata.gz: 056b6335c5c9c622f2f159423076e4e974250a504fe30b4e55b563f125954a01235b6e60d12b5d99bb96dfc1e272456396c052076c953861d8acd3c40d6d2fd5
7
- data.tar.gz: a7db515e4fe78aca2aa5581edf95b0f9a5895b763b48370b4c9eed700ef3741ebeedb67d26253d8862e3bf41b99a808e46720770ed4b10edbf4864f5862dadd9
6
+ metadata.gz: 76dab7d9fbd67731c3cb8130c335045222574f480bbb21688efcded5caa1697d99b61ab3506d2545267a1e7d42ff047856ca76124d57dfbaf058c5864743076d
7
+ data.tar.gz: d628ecc733fd036c324f85e9418fcaebafdd0f70b2d6a849ac32192e41e1b2db1c71dd2cdcae08707b4458d7194eebce95c20832fe71aa8b926bed28c40f281b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # v0.3.0
2
+ * Updates for carnivore 1.0
3
+ * Remove reel and replace with puma
4
+
1
5
  # v0.2.8
2
6
  * Set confirmed state directly into message
3
7
  * Support body on non-200 type responses
@@ -10,9 +10,13 @@ Gem::Specification.new do |s|
10
10
  s.description = 'Carnivore HTTP source'
11
11
  s.license = 'Apache 2.0'
12
12
  s.require_path = 'lib'
13
- s.add_dependency 'carnivore', '>= 0.1.8'
14
- s.add_dependency 'reel', '~> 0.5.0'
15
- s.add_dependency 'blockenspiel'
16
- s.add_dependency 'htauth'
17
- s.files = Dir['**/*']
13
+ s.add_runtime_dependency 'carnivore', '>= 1.0.0', '< 2.0'
14
+ s.add_runtime_dependency 'puma', '~> 2.13.4'
15
+ s.add_runtime_dependency 'rack', '~> 1.6.4'
16
+ s.add_runtime_dependency 'blockenspiel', '~> 0.4.5'
17
+ s.add_runtime_dependency 'htauth', '~> 2.0.0'
18
+ s.add_development_dependency 'http'
19
+ s.add_development_dependency 'minitest'
20
+ s.add_development_dependency 'pry'
21
+ s.files = Dir['lib/**/*'] + %w(carnivore-http.gemspec README.md CHANGELOG.md)
18
22
  end
@@ -0,0 +1,218 @@
1
+ require 'rack'
2
+ require 'timeout'
3
+ require 'carnivore-http'
4
+
5
+ module Carnivore
6
+ module Http
7
+ # Rack app for processing messages
8
+ class App
9
+
10
+ # Customized response
11
+ class Response < Rack::Response
12
+
13
+ # Lazy status mapping
14
+ STATUS_CODES = Smash.new(
15
+ "continue" => 100,
16
+ "switching_protocols" => 101,
17
+ "processing" => 102,
18
+ "ok" => 200,
19
+ "created" => 201,
20
+ "accepted" => 202,
21
+ "non_authoritative_information" => 203,
22
+ "no_content" => 204,
23
+ "reset_content" => 205,
24
+ "partial_content" => 206,
25
+ "multi_status" => 207,
26
+ "already_reported" => 208,
27
+ "im_used" => 226,
28
+ "multiple_choices" => 300,
29
+ "moved_permanently" => 301,
30
+ "found" => 302,
31
+ "see_other" => 303,
32
+ "not_modified" => 304,
33
+ "use_proxy" => 305,
34
+ "temporary_redirect" => 307,
35
+ "permanent_redirect" => 308,
36
+ "bad_request" => 400,
37
+ "unauthorized" => 401,
38
+ "payment_required" => 402,
39
+ "forbidden" => 403,
40
+ "not_found" => 404,
41
+ "method_not_allowed" => 405,
42
+ "not_acceptable" => 406,
43
+ "proxy_authentication_required" => 407,
44
+ "request_timeout" => 408,
45
+ "conflict" => 409,
46
+ "gone" => 410,
47
+ "length_required" => 411,
48
+ "precondition_failed" => 412,
49
+ "payload_too_large" => 413,
50
+ "uri_too_long" => 414,
51
+ "unsupported_media_type" => 415,
52
+ "range_not_satisfiable" => 416,
53
+ "expectation_failed" => 417,
54
+ "misdirected_request" => 421,
55
+ "unprocessable_entity" => 422,
56
+ "locked" => 423,
57
+ "failed_dependency" => 424,
58
+ "upgrade_required" => 426,
59
+ "precondition_required" => 428,
60
+ "too_many_requests" => 429,
61
+ "request_header_fields_too_large" => 431,
62
+ "internal_server_error" => 500,
63
+ "not_implemented" => 501,
64
+ "bad_gateway" => 502,
65
+ "service_unavailable" => 503,
66
+ "gateway_timeout" => 504,
67
+ "http_version_not_supported" => 505,
68
+ "variant_also_negotiates" => 506,
69
+ "insufficient_storage" => 507,
70
+ "loop_detected" => 508,
71
+ "not_extended" => 510,
72
+ "network_authentication_required" => 511
73
+ )
74
+
75
+ # Create a new response
76
+ #
77
+ # @param code [String, Symbol, Integer] status code of response
78
+ # @param string_or_args [String, Hash] response content
79
+ # @option :body [String] response body
80
+ # @option :json [Hash, Array] response body to serialize
81
+ # @option :form [Hash] response body to encode
82
+ # @option :headers [Hash] response headers
83
+ # @return [self]
84
+ def initialize(code, string_or_args, &block)
85
+ status = STATUS_CODES.fetch(code, code).to_i
86
+ case string_or_args
87
+ when String
88
+ body = string_or_args
89
+ headers = {}
90
+ when Hash
91
+ headers = string_or_args.fetch(:headers, {})
92
+ if(string_or_args[:body])
93
+ body = string_or_args[:body]
94
+ unless(headers['Content-Type'])
95
+ headers['Content-Type'] = 'text/plain'
96
+ end
97
+ elsif(string_or_args[:json])
98
+ body = MultiJson.dump(string_or_args[:json])
99
+ unless(headers['Content-Type'])
100
+ headers['Content-Type'] = 'application/json'
101
+ end
102
+ elsif(string_or_args[:form])
103
+ body = dump_query_string(string_or_args[:form])
104
+ unless(headers['Content-Type'])
105
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
106
+ end
107
+ end
108
+ else
109
+ raise TypeError.new "Invalid type provided. Expected `String` or `Hash` but got `#{string_or_args.class}`"
110
+ end
111
+ super(body, status, headers, &block)
112
+ end
113
+
114
+ end
115
+
116
+
117
+ # Customized request
118
+ class Request < Rack::Request
119
+
120
+ include Zoidberg::SoftShell
121
+
122
+ option :cache_signals
123
+
124
+ # @return [Response]
125
+ attr_reader :response_value
126
+
127
+ # Respond to the request
128
+ #
129
+ # @param code [String, Symbol, Integer] response status code
130
+ # @param string_or_args [String, Hash]
131
+ # @return [TrueClass, FalseClass]
132
+ def respond(code, string_or_args='')
133
+ unless(@response_value)
134
+ signal(:response, Response.new(code, string_or_args))
135
+ else
136
+ raise 'Response was already set!'
137
+ end
138
+ end
139
+
140
+ # Response to this request
141
+ #
142
+ # @param timeout [Integer] maximum number of seconds to wait
143
+ # @return [Response]
144
+ def response(timeout=5)
145
+ unless(@response_value)
146
+ begin
147
+ Timeout.timeout(timeout) do
148
+ @response_value = wait_for(:response)
149
+ end
150
+ rescue Timeout::Error
151
+ @response_value = Response.new(:internal_server_error, 'Timeout waiting for response')
152
+ end
153
+ end
154
+ @response_value
155
+ end
156
+
157
+ # @return [Smash]
158
+ def headers
159
+ Smash[
160
+ env.map do |k,v|
161
+ k.start_with?('rack.') ? nil : [k.downcase.sub(/^http_/, '').to_sym,v]
162
+ end.compact
163
+ ]
164
+ end
165
+
166
+ # @return [String]
167
+ def remote_addr
168
+ headers['REMOTE_ADDR']
169
+ end
170
+
171
+ # @return [Symbol]
172
+ def method
173
+ request_method.to_s.downcase.to_sym
174
+ end
175
+
176
+ end
177
+
178
+ # @return [Proc] action to process request
179
+ attr_reader :action
180
+
181
+ # Create a new instance
182
+ #
183
+ # @param args [Hash]
184
+ # @yield processes request
185
+ # @return [self]
186
+ def initialize(args={}, &block)
187
+ @action = block
188
+ end
189
+
190
+ # Process the request
191
+ #
192
+ # @param env [Hash]
193
+ # @return [Array]
194
+ def call(env)
195
+ request = Request.new(env)
196
+ action.call(request)
197
+ request.response.finish
198
+ end
199
+
200
+ class << self
201
+
202
+ # Build a new app
203
+ #
204
+ # @param args [Hash] options
205
+ # @param block [Proc]
206
+ # @return [App]
207
+ def build_app(args={}, &block)
208
+ Rack::Builder.new do
209
+ use Rack::Chunked
210
+ run self.new(args, &block)
211
+ end
212
+ end
213
+
214
+ end
215
+
216
+ end
217
+ end
218
+ end
@@ -8,24 +8,22 @@ module Carnivore
8
8
  def process(*process_args)
9
9
  unless(@processing)
10
10
  @processing = true
11
- srv = build_listener do |con|
12
- con.each_request do |req|
13
- begin
14
- msg = build_message(con, req)
15
- msg = format(msg)
16
- if(authorized?(msg))
17
- callbacks.each do |name|
18
- c_name = callback_name(name)
19
- debug "Dispatching #{msg} to callback<#{name} (#{c_name})>"
20
- callback_supervisor[c_name].call(msg)
21
- end
22
- req.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
23
- else
24
- req.respond(:unauthorized, 'You are not authorized to perform requested action!')
11
+ srv = build_listener do |req|
12
+ begin
13
+ msg = build_message(req)
14
+ msg = format(msg)
15
+ if(authorized?(msg))
16
+ callbacks.each do |name|
17
+ c_name = callback_name(name)
18
+ debug "Dispatching #{msg} to callback<#{name} (#{c_name})>"
19
+ callback_supervisor[c_name].call(msg)
25
20
  end
26
- rescue => e
27
- req.respond(:bad_request, "Failed to process request -> #{e}")
21
+ req.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
22
+ else
23
+ req.respond(:unauthorized, 'You are not authorized to perform requested action!')
28
24
  end
25
+ rescue => e
26
+ req.respond(:bad_request, "Failed to process request -> #{e}")
29
27
  end
30
28
  end
31
29
  true
@@ -85,25 +85,23 @@ module Carnivore
85
85
  def process(*process_args)
86
86
  unless(processing)
87
87
  @processing = true
88
- srv = build_listener do |con|
89
- con.each_request do |req|
90
- begin
91
- msg = build_message(con, req)
92
- msg = format(msg)
93
- if(authorized?(msg))
94
- unless(@points.deliver(msg))
95
- warn "No match found for request: #{msg} (path: #{msg[:message][:request].url})"
96
- debug "Unmatched message (#{msg}): #{msg.inspect}"
97
- req.respond(:not_found, 'So long, and thanks for all the fish!')
98
- end
99
- else
100
- req.respond(:unauthorized, 'You are not authorized to perform requested action!')
88
+ srv = build_listener do |req|
89
+ begin
90
+ msg = build_message(req)
91
+ msg = format(msg)
92
+ if(authorized?(msg))
93
+ unless(@points.deliver(msg))
94
+ warn "No match found for request: #{msg} (path: #{msg[:message][:request].url})"
95
+ debug "Unmatched message (#{msg}): #{msg.inspect}"
96
+ req.respond(:not_found, 'So long, and thanks for all the fish!')
101
97
  end
102
- rescue => e
103
- error "Failed to process message: #{e.class} - #{e}"
104
- debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
105
- req.respond(:bad_request, 'Failed to process request')
98
+ else
99
+ req.respond(:unauthorized, 'You are not authorized to perform requested action!')
106
100
  end
101
+ rescue => e
102
+ error "Failed to process message: #{e.class} - #{e}"
103
+ debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
104
+ req.respond(:bad_request, 'Failed to process request')
107
105
  end
108
106
  end
109
107
  true
@@ -12,7 +12,6 @@ module Carnivore
12
12
  # Default response wait time stepping
13
13
  DEFAULT_RESPONSE_WAIT_STEP = 0.1
14
14
 
15
- finalizer :halt_listener
16
15
  include Bogo::Memoization
17
16
 
18
17
  # @return [String] end point path
@@ -21,10 +20,10 @@ module Carnivore
21
20
  attr_reader :http_method
22
21
 
23
22
  # Kill listener on shutdown
24
- def halt_listener
23
+ def terminate
25
24
  listener = memoize("#{args[:bind]}-#{args[:port]}", :global){ nil }
26
- if(listener && listener.alive?)
27
- listener.terminate
25
+ if(listener && listener.running)
26
+ listener.stop(:sync)
28
27
  end
29
28
  unmemoize("#{args[:bind]}-#{args[:port]}", :global)
30
29
  unmemoize("#{args[:bind]}-#{args[:port]}-queues", :global)
@@ -72,48 +71,37 @@ module Carnivore
72
71
  # Start the HTTP(S) listener
73
72
  def start_listener!
74
73
  memoize("#{args[:bind]}-#{args[:port]}", :global) do
75
- build_listener do |con|
76
- con.each_request do |req|
77
- begin
78
- msg = build_message(con, req)
79
- # Start with static path lookup since it's the
80
- # cheapest, then fallback to iterative globbing
81
- msg_queue = nil
82
- unless(msg_queue = message_queues["#{req.path}-#{req.method.to_s.downcase}"])
83
- message_queues.each do |k,v|
84
- path_glob, http_method = k.split('-')
85
- if(req.method.to_s.downcase == http_method && File.fnmatch(path_glob, req.path))
86
- msg_queue = v
87
- end
74
+ build_listener do |req|
75
+ begin
76
+ msg = build_message(req)
77
+ # Start with static path lookup since it's the
78
+ # cheapest, then fallback to iterative globbing
79
+ msg_queue = nil
80
+ unless(msg_queue = message_queues["#{req.path}-#{req.method.to_s.downcase}"])
81
+ message_queues.each do |k,v|
82
+ path_glob, http_method = k.split('-')
83
+ if(req.method.to_s.downcase == http_method && File.fnmatch(path_glob, req.path))
84
+ msg_queue = v
88
85
  end
89
86
  end
90
- if(msg_queue)
91
- if(authorized?(msg))
92
- msg_queue[:queue] << msg
93
- if(msg_queue[:config][:auto_respond])
94
- code = msg_queue[:config].fetch(:response, :code, 'ok').to_sym
95
- response = msg_queue[:config].fetch(:response, :message, 'So long and thanks for all the fish!')
96
- req.respond(code, response)
97
- else
98
- wait_time = msg_queue[:config].fetch(:response_timeout, DEFAULT_RESPONSE_TIMEOUT).to_f
99
- wait_step = msg_queue[:config].fetch(:response_wait_step, DEFAULT_RESPONSE_WAIT_STEP).to_f
100
- while(!con.socket.closed? && wait_time > 0)
101
- sleep(wait_step)
102
- wait_time -= wait_step
103
- end
104
- if(con.response_state == :headers)
105
- raise "Timeout has been exceeded waiting for response! (#{msg})"
106
- end
107
- end
108
- else
109
- req.respond(:unauthorized, 'You are not authorized to perform requested action!')
87
+ end
88
+ if(msg_queue)
89
+ if(authorized?(msg))
90
+ msg_queue[:queue] << msg
91
+ if(msg_queue[:config][:auto_respond])
92
+ code = msg_queue[:config].fetch(:response, :code, 'ok').to_sym
93
+ response = msg_queue[:config].fetch(:response, :message, 'So long and thanks for all the fish!')
94
+ req.respond(code, response)
110
95
  end
111
96
  else
112
- req.respond(:not_found, 'Requested path not found!')
97
+ req.respond(:unauthorized, 'You are not authorized to perform requested action!')
113
98
  end
114
- rescue => e
115
- req.respond(:bad_request, "Failed to process request -> #{e}")
99
+ else
100
+ req.respond(:not_found, 'Requested path not found!')
116
101
  end
102
+ rescue => e
103
+ req.respond(:bad_request, "Failed to process request -> #{e}")
104
+ puts "#{e}\n#{e.backtrace.join("\n")}"
117
105
  end
118
106
  end
119
107
  end
@@ -123,8 +111,9 @@ module Carnivore
123
111
  def receive(*_)
124
112
  val = nil
125
113
  until(val)
126
- val = Celluloid::Future.new{ message_queue[:queue].pop }.value
114
+ val = defer{ message_queue[:queue].pop }
127
115
  end
116
+ info "PROCESSING MSG: #{val}"
128
117
  val
129
118
  end
130
119
 
@@ -1,4 +1,4 @@
1
- require 'reel'
1
+ require 'puma'
2
2
  require 'tempfile'
3
3
  require 'carnivore/source'
4
4
  require 'carnivore-http/utils'
@@ -9,14 +9,10 @@ module Carnivore
9
9
  # Carnivore HTTP source
10
10
  class HttpSource < Source
11
11
 
12
- trap_exit :retry_delivery_failure
13
-
14
12
  include Carnivore::Http::Utils::Params
15
13
 
16
14
  # @return [Hash] source arguments
17
15
  attr_reader :args
18
- # @return [Carnivore::Http::RetryDelivery]
19
- attr_reader :retry_delivery
20
16
  # @return [Array<IPAddr>] allowed request origin addresses
21
17
  attr_reader :auth_allowed_origins
22
18
  # @return [HTAuth::PasswdFile]
@@ -28,8 +24,13 @@ module Carnivore
28
24
  def setup(args={})
29
25
  require 'fileutils'
30
26
  @args = default_args(args)
31
- @retry_delivery = Carnivore::Http::RetryDelivery.new(retry_directory)
32
- self.link retry_delivery
27
+ unless(retry_delivery)
28
+ Carnivore::Supervisor.supervisor.supervise_as(
29
+ :http_retry_delivery,
30
+ Carnivore::Http::RetryDelivery,
31
+ retry_directory
32
+ )
33
+ end
33
34
  if(args.get(:authorization, :allowed_origins))
34
35
  require 'ipaddr'
35
36
  @allowed_origins = [args.get(:authorization, :allowed_origins)].flatten.compact.map do |origin_check|
@@ -42,25 +43,12 @@ module Carnivore
42
43
  args.get(:authorization, :htpasswd)
43
44
  )
44
45
  end
46
+ @listeners = []
45
47
  end
46
48
 
47
- # Handle failed retry deliveries
48
- #
49
- # @param actor [Object] terminated actor
50
- # @param reason [Exception] reason for termination
51
- # @return [NilClass]
52
- def retry_delivery_failure(actor, reason)
53
- if(actor == retry_delivery)
54
- if(reason)
55
- error "Failed RetryDelivery encountered: #{reason}. Rebuilding."
56
- @retry_delivery = Carnivore::Http::RetryDelivery.new(retry_directory)
57
- else
58
- info 'Encountered RetryDelivery failure. No reason so assuming teardown.'
59
- end
60
- else
61
- error "Unknown actor failure encountered: #{reason}"
62
- end
63
- nil
49
+ # @return [RetryDelivery]
50
+ def retry_delivery
51
+ Carnivore::Supervisor.supervisor[:http_retry_delivery]
64
52
  end
65
53
 
66
54
  # @return [String, NilClass] directory storing failed messages
@@ -218,7 +206,7 @@ module Carnivore
218
206
  method = options.fetch(:method,
219
207
  args.fetch(:method, :post)
220
208
  ).to_s.downcase.to_sym
221
- message_id = message.is_a?(Hash) ? message.fetch(:id, Celluloid.uuid) : Celluloid.uuid
209
+ message_id = message.is_a?(Hash) ? message.fetch(:id, Carnivore.uuid) : Carnivore.uuid
222
210
  payload = message.is_a?(String) ? message : MultiJson.dump(message)
223
211
  info "Transmit request type for Message ID: #{message_id}"
224
212
  async.perform_transmission(message_id.to_s, payload, method, url, options.fetch(:headers, {}))
@@ -280,8 +268,7 @@ module Carnivore
280
268
  args[:response_body] = 'Thanks' if code == :ok && args.empty?
281
269
  body = args.delete(:response_body)
282
270
  debug "Confirming #{message} with: Code: #{code.inspect} Args: #{args.inspect} Body: #{body}"
283
- message[:message][:request].respond(code, *(args.empty? ? [body] : [args, body]))
284
- message[:message][:connection].close
271
+ message[:message][:request].respond(code, *(args.empty? ? [body] : [args.merge(:body => body)]))
285
272
  message[:message][:confirmed] = true
286
273
  else
287
274
  warn "Message was already confimed. Confirmation not sent! (#{message})"
@@ -293,16 +280,27 @@ module Carnivore
293
280
  # @param block [Proc] processing block
294
281
  # @return [Reel::Server::HTTP, Reel::Server::HTTPS]
295
282
  def build_listener(&block)
283
+ app = Carnivore::Http::App.new(&block)
284
+ options = {:bind => []}
296
285
  if(args[:ssl])
297
- ssl_config = Smash.new(args[:ssl][key].dup)
298
- [:key, :cert].each do |key|
299
- if(ssl_config[key])
300
- ssl_config[key] = File.open(ssl_config.delete(key))
301
- end
302
- end
303
- Reel::Server::HTTPS.supervise(args[:bind], args[:port], ssl_config, &block)
286
+ ssl_config = Smash.new(args[:ssl])
287
+ options[:bind] << "ssl://#{args[:bind]}:#{args[:port]}?cert=#{ssl_config[:cert]}&key=#{ssl_config[:key]}"
304
288
  else
305
- Reel::Server::HTTP.supervise(args[:bind], args[:port], &block)
289
+ options[:bind] << "tcp://#{args[:bind]}:#{args[:port]}"
290
+ end
291
+ srv = Puma::Server.new(app, Puma::Events.stdio, options)
292
+ @listeners.push(srv)
293
+ srv.binder.parse(options[:bind], Puma::Events.stdio)
294
+ srv.run
295
+ srv
296
+ end
297
+
298
+ def terminate
299
+ if(@listeners)
300
+ @listeners.each do |l|
301
+ l.stop(:sync)
302
+ end
303
+ @listeners.clear
306
304
  end
307
305
  end
308
306
 
@@ -311,30 +309,28 @@ module Carnivore
311
309
 
312
310
  # Build message hash from request
313
311
  #
314
- # @param con [Reel::Connection]
315
- # @param req [Reel::Request]
312
+ # @param req [Carnivore::Http::App::Request]
316
313
  # @return [Hash]
317
314
  # @note
318
315
  # if body size is greater than BODY_TO_FILE_SIZE
319
316
  # the body will be a temp file instead of a string
320
- def build_message(con, req)
317
+ def build_message(req)
321
318
  msg = Smash.new(
322
319
  :request => req,
323
320
  :headers => Smash[
324
321
  req.headers.map{ |k,v| [k.downcase.tr('-', '_'), v]}
325
322
  ],
326
- :connection => con,
327
323
  :query => parse_query_string(req.query_string),
328
324
  :origin => req.remote_addr,
329
325
  :authentication => {}
330
326
  )
331
327
  if(msg[:headers][:content_type] == 'application/json')
332
328
  msg[:body] = MultiJson.load(
333
- req.body.to_s
329
+ req.body.read
334
330
  )
335
331
  elsif(msg[:headers][:content_type] == 'application/x-www-form-urlencoded')
336
332
  msg[:body] = parse_query_string(
337
- req.body.to_s
333
+ req.body.read
338
334
  )
339
335
  if(msg[:body].size == 1 && msg[:body].values.first.is_a?(Array) && msg[:body].values.first.empty?)
340
336
  msg[:body] = msg[:body].keys.first
@@ -346,7 +342,7 @@ module Carnivore
346
342
  end
347
343
  msg[:body].rewind
348
344
  else
349
- msg[:body] = req.body.to_s
345
+ msg[:body] = req.body.read
350
346
  end
351
347
  if(msg[:headers][:authorization])
352
348
  user, pass = Base64.urlsafe_decode64(
@@ -1,7 +1,6 @@
1
1
  require 'blockenspiel'
2
2
  require 'singleton'
3
3
  require 'carnivore/utils'
4
- require 'celluloid'
5
4
 
6
5
  module Carnivore
7
6
  module Http
@@ -12,7 +11,8 @@ module Carnivore
12
11
  # End point
13
12
  class Endpoint
14
13
 
15
- include Celluloid
14
+ include Zoidberg::SoftShell
15
+ include Zoidberg::Supervise
16
16
  include Carnivore::Utils::Params
17
17
  include Carnivore::Utils::Logging
18
18
 
@@ -58,7 +58,7 @@ module Carnivore
58
58
  end
59
59
 
60
60
  include Carnivore::Utils::Params
61
- include Celluloid::Logger
61
+ include Carnivore::Utils::Logging
62
62
  include Blockenspiel::DSL
63
63
 
64
64
  # @return [Hash] static path endpoints
@@ -232,7 +232,7 @@ module Carnivore
232
232
  #
233
233
  # @yield new API block
234
234
  def define(&block)
235
- store(Celluloid.uuid, block)
235
+ store(Zoidberg.uuid, block)
236
236
  end
237
237
 
238
238
  # Store block
@@ -5,7 +5,8 @@ module Carnivore
5
5
 
6
6
  class RetryDelivery
7
7
 
8
- include Celluloid
8
+ include Zoidberg::SoftShell
9
+ include Zoidberg::Supervise
9
10
  include Carnivore::Utils::Logging
10
11
 
11
12
  # @return [String] message directory
@@ -24,13 +24,21 @@ module Carnivore
24
24
  # @return [Hash]
25
25
  def parse_query_string(string)
26
26
  unless(string.to_s.empty?)
27
- args = CGI.parse(string)
27
+ args = Rack::Utils.parse_nested_query(string)
28
28
  format_query_args(args)
29
29
  else
30
- {}
30
+ Smash.new
31
31
  end
32
32
  end
33
33
 
34
+ # Generate query string
35
+ #
36
+ # @param hash [Hash] request parameters
37
+ # @return [String]
38
+ def dump_query_string(hash)
39
+ Rack::Utils.build_nested_query(hash)
40
+ end
41
+
34
42
  # Cast hash values when possible
35
43
  #
36
44
  # @param args [Hash]
@@ -1,6 +1,6 @@
1
1
  module Carnivore
2
2
  module Http
3
3
  # current library version
4
- VERSION = Gem::Version.new('0.2.8')
4
+ VERSION = Gem::Version.new('0.3.0')
5
5
  end
6
6
  end
@@ -5,6 +5,7 @@ require 'multi_json'
5
5
  module Carnivore
6
6
  # HTTP namespace
7
7
  module Http
8
+ autoload :App, 'carnivore-http/app'
8
9
  autoload :PointBuilder, 'carnivore-http/point_builder'
9
10
  autoload :RetryDelivery, 'carnivore-http/retry_delivery'
10
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carnivore-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-13 00:00:00.000000000 Z
11
+ date: 2015-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: carnivore
@@ -16,36 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.8
19
+ version: 1.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: 0.1.8
29
+ version: 1.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: puma
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.13.4
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 2.13.4
27
47
  - !ruby/object:Gem::Dependency
28
- name: reel
48
+ name: rack
29
49
  requirement: !ruby/object:Gem::Requirement
30
50
  requirements:
31
51
  - - "~>"
32
52
  - !ruby/object:Gem::Version
33
- version: 0.5.0
53
+ version: 1.6.4
34
54
  type: :runtime
35
55
  prerelease: false
36
56
  version_requirements: !ruby/object:Gem::Requirement
37
57
  requirements:
38
58
  - - "~>"
39
59
  - !ruby/object:Gem::Version
40
- version: 0.5.0
60
+ version: 1.6.4
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: blockenspiel
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.4.5
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.4.5
75
+ - !ruby/object:Gem::Dependency
76
+ name: htauth
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 2.0.0
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 2.0.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: http
43
91
  requirement: !ruby/object:Gem::Requirement
44
92
  requirements:
45
93
  - - ">="
46
94
  - !ruby/object:Gem::Version
47
95
  version: '0'
48
- type: :runtime
96
+ type: :development
49
97
  prerelease: false
50
98
  version_requirements: !ruby/object:Gem::Requirement
51
99
  requirements:
@@ -53,13 +101,27 @@ dependencies:
53
101
  - !ruby/object:Gem::Version
54
102
  version: '0'
55
103
  - !ruby/object:Gem::Dependency
56
- name: htauth
104
+ name: minitest
57
105
  requirement: !ruby/object:Gem::Requirement
58
106
  requirements:
59
107
  - - ">="
60
108
  - !ruby/object:Gem::Version
61
109
  version: '0'
62
- type: :runtime
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: pry
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
63
125
  prerelease: false
64
126
  version_requirements: !ruby/object:Gem::Requirement
65
127
  requirements:
@@ -73,13 +135,10 @@ extensions: []
73
135
  extra_rdoc_files: []
74
136
  files:
75
137
  - CHANGELOG.md
76
- - CONTRIBUTING.md
77
- - Gemfile
78
- - LICENSE
79
138
  - README.md
80
- - carnivore-http-0.2.6.gem
81
139
  - carnivore-http.gemspec
82
140
  - lib/carnivore-http.rb
141
+ - lib/carnivore-http/app.rb
83
142
  - lib/carnivore-http/http.rb
84
143
  - lib/carnivore-http/http_endpoints.rb
85
144
  - lib/carnivore-http/http_paths.rb
@@ -88,9 +147,6 @@ files:
88
147
  - lib/carnivore-http/retry_delivery.rb
89
148
  - lib/carnivore-http/utils.rb
90
149
  - lib/carnivore-http/version.rb
91
- - test/spec.rb
92
- - test/specs/http.rb
93
- - test/specs/paths.rb
94
150
  homepage: https://github.com/carnivore-rb/carnivore-http
95
151
  licenses:
96
152
  - Apache 2.0
@@ -111,9 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
167
  version: '0'
112
168
  requirements: []
113
169
  rubyforge_project:
114
- rubygems_version: 2.2.2
170
+ rubygems_version: 2.4.8
115
171
  signing_key:
116
172
  specification_version: 4
117
173
  summary: Message processing helper
118
174
  test_files: []
119
- has_rdoc:
data/CONTRIBUTING.md DELETED
@@ -1,25 +0,0 @@
1
- # Contributing
2
-
3
- ## Branches
4
-
5
- ### `master` branch
6
-
7
- The master branch is the current stable released version.
8
-
9
- ### `develop` branch
10
-
11
- The develop branch is the current edge of development.
12
-
13
- ## Pull requests
14
-
15
- * https://github.com/carnivore-rb/carnivore-http/pulls
16
-
17
- Please base all pull requests of the `develop` branch. Merges to
18
- `master` only occur through the `develop` branch. Pull requests
19
- based on `master` will likely be cherry picked.
20
-
21
- ## Issues
22
-
23
- Need to report an issue? Use the github issues:
24
-
25
- * https://github.com/carnivore-rb/carnivore-http/issues
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
data/LICENSE DELETED
@@ -1,13 +0,0 @@
1
- Copyright 2014 Chris Roberts
2
-
3
- Licensed under the Apache License, Version 2.0 (the "License");
4
- you may not use this file except in compliance with the License.
5
- You may obtain a copy of the License at
6
-
7
- http://www.apache.org/licenses/LICENSE-2.0
8
-
9
- Unless required by applicable law or agreed to in writing, software
10
- distributed under the License is distributed on an "AS IS" BASIS,
11
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- See the License for the specific language governing permissions and
13
- limitations under the License.
Binary file
data/test/spec.rb DELETED
@@ -1 +0,0 @@
1
- require 'carnivore/spec_helper'
data/test/specs/http.rb DELETED
@@ -1,69 +0,0 @@
1
- require 'http'
2
- require 'minitest/autorun'
3
- require 'carnivore-http'
4
-
5
- describe 'Carnivore::Source::Http' do
6
-
7
- before do
8
- MessageStore.init
9
- Carnivore::Source.build(
10
- :type => :http,
11
- :args => {
12
- :name => :http_source,
13
- :bind => '127.0.0.1',
14
- :port => '8705'
15
- }
16
- ).add_callback(:store) do |message|
17
- MessageStore.messages.push(message[:message][:body])
18
- message.confirm!
19
- end
20
- @runner = Thread.new{ Carnivore.start! }
21
- source_wait
22
- end
23
-
24
- after do
25
- @runner.terminate
26
- end
27
-
28
- describe 'HTTP source based communication' do
29
-
30
- before do
31
- MessageStore.messages.clear
32
- end
33
-
34
- describe 'Building an HTTP based source' do
35
-
36
- it 'returns the source' do
37
- Carnivore::Supervisor.supervisor[:http_source].wont_be_nil
38
- end
39
-
40
- end
41
-
42
- describe 'message transmissions' do
43
-
44
- it 'should accept message transmits' do
45
- Carnivore::Supervisor.supervisor[:http_source].transmit('test message')
46
- end
47
-
48
- it 'should receive messages' do
49
- Carnivore::Supervisor.supervisor[:http_source].transmit('test message 2')
50
- source_wait(2) do
51
- !MessageStore.messages.empty?
52
- end
53
- MessageStore.messages.wont_be_empty
54
- MessageStore.messages.pop.must_equal 'test message 2'
55
- end
56
-
57
- it 'should accept http requests' do
58
- HTTP.get('http://127.0.0.1:8705/')
59
- source_wait(2) do
60
- !MessageStore.messages.empty?
61
- end
62
- MessageStore.messages.wont_be_empty
63
- MessageStore.messages.pop.wont_be_nil
64
- end
65
-
66
- end
67
- end
68
-
69
- end
data/test/specs/paths.rb DELETED
@@ -1,124 +0,0 @@
1
- require 'http'
2
- require 'minitest/autorun'
3
- require 'carnivore-http'
4
-
5
-
6
- describe 'Carnivore::Source::Http' do
7
-
8
- before do
9
- MessageStore.init
10
-
11
- unless(@runner)
12
- Carnivore::Source.build(
13
- :type => :http_paths,
14
- :args => {
15
- :name => :fubar_source,
16
- :path => '/fubar',
17
- :method => :post,
18
- :bind => '127.0.0.1',
19
- :port => '8706',
20
- :auto_respond => false
21
- }
22
- ).add_callback(:store) do |message|
23
- MessageStore.messages.push(message[:message][:body])
24
- message.confirm!(:response_body => 'custom response')
25
- end
26
- Carnivore::Source.build(
27
- :type => :http_paths,
28
- :args => {
29
- :name => :ohai_source,
30
- :path => '/ohai',
31
- :method => :get,
32
- :bind => '127.0.0.1',
33
- :port => '8706'
34
- }
35
- ).add_callback(:store) do |message|
36
- MessageStore.messages.push(message[:message][:body])
37
- end
38
- Carnivore::Source.build(
39
- :type => :http_paths,
40
- :args => {
41
- :name => :glob_source,
42
- :path => '/glob/v*/*',
43
- :method => :get,
44
- :bind => '127.0.0.1',
45
- :port => '8706'
46
- }
47
- ).add_callback(:store) do |message|
48
- MessageStore.messages.push(message[:message][:body])
49
- end
50
- @runner = Thread.new{ Carnivore.start! }
51
- source_wait
52
- end
53
- end
54
-
55
- after do
56
- @runner.terminate
57
- end
58
-
59
- describe 'HTTP source based communication' do
60
-
61
- before do
62
- MessageStore.messages.clear
63
- end
64
-
65
- describe 'Building an HTTP based source' do
66
-
67
- it 'returns the sources' do
68
- Carnivore::Supervisor.supervisor[:fubar_source].wont_be_nil
69
- Carnivore::Supervisor.supervisor[:ohai_source].wont_be_nil
70
- end
71
-
72
- end
73
-
74
- describe 'source message transmissions' do
75
-
76
- it 'should accept message transmits' do
77
- Carnivore::Supervisor.supervisor[:fubar_source].transmit('test message')
78
- Carnivore::Supervisor.supervisor[:ohai_source].transmit('test message')
79
- end
80
-
81
- it 'should receive messages' do
82
- Carnivore::Supervisor.supervisor[:fubar_source].transmit('test message to fubar')
83
- source_wait(4) do
84
- !MessageStore.messages.empty?
85
- end
86
- MessageStore.messages.wont_be_empty
87
- MessageStore.messages.pop.must_equal 'test message to fubar'
88
- Carnivore::Supervisor.supervisor[:ohai_source].transmit('test message to ohai')
89
- source_wait(4) do
90
- !MessageStore.messages.empty?
91
- end
92
- MessageStore.messages.wont_be_empty
93
- MessageStore.messages.pop.must_equal 'test message to ohai'
94
- end
95
-
96
- end
97
-
98
- describe 'HTTP message transmissions' do
99
-
100
- it 'should receive messages and provide custom response' do
101
- response = HTTP.post('http://127.0.0.1:8706/fubar', :body => 'test')
102
- response.body.to_s.must_equal 'custom response'
103
- source_wait{ !MessageStore.messages.empty? }
104
- MessageStore.messages.pop.must_equal 'test'
105
- end
106
-
107
- it 'should receive messages and provide default response' do
108
- response = HTTP.get('http://127.0.0.1:8706/ohai')
109
- source_wait{ !MessageStore.messages.empty? }
110
- response.body.to_s.must_equal 'So long and thanks for all the fish!'
111
- MessageStore.messages.pop.must_be :empty?
112
- end
113
-
114
- it 'should receive messages via glob matching' do
115
- response = HTTP.get('http://127.0.0.1:8706/glob/v2/things')
116
- source_wait{ !MessageStore.messages.empty? }
117
- response.body.to_s.must_equal 'So long and thanks for all the fish!'
118
- MessageStore.messages.pop.must_be :empty?
119
- end
120
-
121
- end
122
- end
123
-
124
- end