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