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 +4 -4
- data/CHANGELOG.md +4 -0
- data/carnivore-http.gemspec +9 -5
- data/lib/carnivore-http/app.rb +218 -0
- data/lib/carnivore-http/http.rb +14 -16
- data/lib/carnivore-http/http_endpoints.rb +15 -17
- data/lib/carnivore-http/http_paths.rb +30 -41
- data/lib/carnivore-http/http_source.rb +38 -42
- data/lib/carnivore-http/point_builder.rb +4 -4
- data/lib/carnivore-http/retry_delivery.rb +2 -1
- data/lib/carnivore-http/utils.rb +10 -2
- data/lib/carnivore-http/version.rb +1 -1
- data/lib/carnivore-http.rb +1 -0
- metadata +74 -19
- data/CONTRIBUTING.md +0 -25
- data/Gemfile +0 -3
- data/LICENSE +0 -13
- data/carnivore-http-0.2.6.gem +0 -0
- data/test/spec.rb +0 -1
- data/test/specs/http.rb +0 -69
- data/test/specs/paths.rb +0 -124
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9595b652c27534813c2b972ca1d51be428cb3ac7
|
4
|
+
data.tar.gz: c279093c9392ec09668b29c57603e4b0aca6c91c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76dab7d9fbd67731c3cb8130c335045222574f480bbb21688efcded5caa1697d99b61ab3506d2545267a1e7d42ff047856ca76124d57dfbaf058c5864743076d
|
7
|
+
data.tar.gz: d628ecc733fd036c324f85e9418fcaebafdd0f70b2d6a849ac32192e41e1b2db1c71dd2cdcae08707b4458d7194eebce95c20832fe71aa8b926bed28c40f281b
|
data/CHANGELOG.md
CHANGED
data/carnivore-http.gemspec
CHANGED
@@ -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.
|
14
|
-
s.
|
15
|
-
s.
|
16
|
-
s.
|
17
|
-
s.
|
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
|
data/lib/carnivore-http/http.rb
CHANGED
@@ -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 |
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
27
|
-
|
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 |
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
103
|
-
|
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
|
23
|
+
def terminate
|
25
24
|
listener = memoize("#{args[:bind]}-#{args[:port]}", :global){ nil }
|
26
|
-
if(listener && listener.
|
27
|
-
listener.
|
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 |
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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(:
|
97
|
+
req.respond(:unauthorized, 'You are not authorized to perform requested action!')
|
113
98
|
end
|
114
|
-
|
115
|
-
req.respond(:
|
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 =
|
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 '
|
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
|
-
|
32
|
-
|
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
|
-
#
|
48
|
-
|
49
|
-
|
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,
|
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
|
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]
|
298
|
-
[:
|
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
|
-
|
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
|
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(
|
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.
|
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.
|
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.
|
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
|
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
|
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(
|
235
|
+
store(Zoidberg.uuid, block)
|
236
236
|
end
|
237
237
|
|
238
238
|
# Store block
|
data/lib/carnivore-http/utils.rb
CHANGED
@@ -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 =
|
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]
|
data/lib/carnivore-http.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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:
|
48
|
+
name: rack
|
29
49
|
requirement: !ruby/object:Gem::Requirement
|
30
50
|
requirements:
|
31
51
|
- - "~>"
|
32
52
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
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:
|
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: :
|
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:
|
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: :
|
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.
|
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
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.
|
data/carnivore-http-0.2.6.gem
DELETED
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
|