carnivore-http 0.1.8 → 0.2.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 +6 -0
- data/Gemfile.lock +35 -15
- data/README.md +49 -2
- data/carnivore-http.gemspec +1 -0
- data/lib/carnivore-http/http.rb +23 -129
- data/lib/carnivore-http/http_endpoints.rb +26 -15
- data/lib/carnivore-http/http_paths.rb +99 -0
- data/lib/carnivore-http/http_source.rb +357 -0
- data/lib/carnivore-http/retry_delivery.rb +88 -0
- data/lib/carnivore-http/version.rb +1 -1
- data/lib/carnivore-http.rb +4 -0
- data/test/specs/http.rb +35 -32
- data/test/specs/paths.rb +87 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f9f4a7936dcc06d41a919c221612c146426790a
|
4
|
+
data.tar.gz: 6153beb3e86f2dd7a3bf7eadad967a3fde8826b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 459ee7c23877b7638eadf34b397dcfacec31237b7b6543ed0aaf7567ac26d155a87a422a1ea309bc7f09aaff0f82702321d51ab25a08e1e8c129f9042172697a
|
7
|
+
data.tar.gz: 029e4ebfc459957bb46db5fc1f3590efa1126d3dedb0e4f2831b70cb480860d4a33d4675d5a87acdd798f00451d3276a0a953c8e181f7ffdbc03072fa3480f7a
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,40 +1,60 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
carnivore-http (0.1.
|
4
|
+
carnivore-http (0.1.9)
|
5
5
|
blockenspiel
|
6
6
|
carnivore (>= 0.1.8)
|
7
|
+
htauth
|
7
8
|
reel (~> 0.5.0)
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: https://rubygems.org/
|
11
12
|
specs:
|
13
|
+
attribute_struct (0.2.8)
|
12
14
|
blockenspiel (0.4.5)
|
13
|
-
|
15
|
+
bogo (0.1.6)
|
16
|
+
hashie
|
17
|
+
multi_json
|
18
|
+
bogo-config (0.1.6)
|
19
|
+
attribute_struct
|
20
|
+
bogo (>= 0.1.4, < 1.0)
|
21
|
+
multi_json
|
22
|
+
multi_xml
|
23
|
+
carnivore (0.3.4)
|
24
|
+
bogo-config
|
14
25
|
celluloid
|
15
26
|
hashie
|
16
|
-
mixlib-config
|
17
27
|
multi_json
|
18
|
-
celluloid (0.
|
19
|
-
timers (~>
|
20
|
-
celluloid-io (0.
|
21
|
-
celluloid (>= 0.
|
22
|
-
nio4r (>=
|
23
|
-
|
24
|
-
|
28
|
+
celluloid (0.16.0)
|
29
|
+
timers (~> 4.0.0)
|
30
|
+
celluloid-io (0.16.2)
|
31
|
+
celluloid (>= 0.16.0)
|
32
|
+
nio4r (>= 1.1.0)
|
33
|
+
form_data (0.1.0)
|
34
|
+
hashie (3.3.2)
|
35
|
+
highline (1.6.21)
|
36
|
+
hitimes (1.2.2)
|
37
|
+
hitimes (1.2.2-java)
|
38
|
+
htauth (1.1.0)
|
39
|
+
highline (~> 1.6)
|
40
|
+
http (0.7.1)
|
41
|
+
form_data (~> 0.1.0)
|
25
42
|
http_parser.rb (~> 0.6.0)
|
26
43
|
http_parser.rb (0.6.0)
|
27
|
-
|
28
|
-
multi_json (1.10.
|
29
|
-
|
44
|
+
http_parser.rb (0.6.0-java)
|
45
|
+
multi_json (1.10.1)
|
46
|
+
multi_xml (0.5.5)
|
47
|
+
nio4r (1.1.0)
|
48
|
+
nio4r (1.1.0-java)
|
30
49
|
reel (0.5.0)
|
31
50
|
celluloid (>= 0.15.1)
|
32
51
|
celluloid-io (>= 0.15.0)
|
33
52
|
http (>= 0.6.0.pre)
|
34
53
|
http_parser.rb (>= 0.6.0)
|
35
54
|
websocket_parser (>= 0.1.6)
|
36
|
-
timers (
|
37
|
-
|
55
|
+
timers (4.0.1)
|
56
|
+
hitimes
|
57
|
+
websocket_parser (1.0.0)
|
38
58
|
|
39
59
|
PLATFORMS
|
40
60
|
java
|
data/README.md
CHANGED
@@ -6,30 +6,77 @@ Provides HTTP `Carnivore::Source`
|
|
6
6
|
|
7
7
|
## HTTP
|
8
8
|
|
9
|
+
All requests are processed via single source
|
10
|
+
|
9
11
|
```ruby
|
10
12
|
require 'carnivore'
|
11
13
|
require 'carnivore-http'
|
12
14
|
|
13
15
|
Carnivore.configure do
|
14
16
|
source = Carnivore::Source.build(
|
15
|
-
:type => :http,
|
17
|
+
:type => :http,
|
18
|
+
:args => {
|
19
|
+
:port => 8080
|
20
|
+
}
|
16
21
|
)
|
17
22
|
end
|
18
23
|
```
|
19
24
|
|
20
25
|
## HTTP with configured end points
|
21
26
|
|
27
|
+
All point builder definitions are hooked into source:
|
28
|
+
|
22
29
|
```ruby
|
23
30
|
require 'carnivore'
|
24
31
|
require 'carnivore-http'
|
25
32
|
|
26
33
|
Carnivore.configure do
|
27
34
|
source = Carnivore::Source.build(
|
28
|
-
:type => :http_endpoints,
|
35
|
+
:type => :http_endpoints,
|
36
|
+
:args => {
|
37
|
+
:auto_respond => false
|
38
|
+
}
|
29
39
|
)
|
30
40
|
end.start!
|
31
41
|
```
|
32
42
|
|
43
|
+
## HTTP paths
|
44
|
+
|
45
|
+
Multiple sources share same listener, and incoming messages
|
46
|
+
are matched based on HTTP method + path
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require 'carnivore'
|
50
|
+
require 'carnivore-http'
|
51
|
+
|
52
|
+
Carnivore.configure do
|
53
|
+
source = Carnivore::Source.build(
|
54
|
+
:type => :http_paths,
|
55
|
+
:args => {
|
56
|
+
:port => 8080,
|
57
|
+
:path => '/test',
|
58
|
+
:method => 'get'
|
59
|
+
}
|
60
|
+
)
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Available options for `:args`
|
65
|
+
|
66
|
+
* `:bind` address to bind
|
67
|
+
* `:port` port to listen
|
68
|
+
* `:auto_respond` confirm request immediately
|
69
|
+
* `:ssl` ssl configuration
|
70
|
+
* `:cert` path to cert file
|
71
|
+
* `:key` path to key file
|
72
|
+
* `:authorization` access restrictors
|
73
|
+
* `:allowed_origins` list of IP or IP ranges
|
74
|
+
* `:htpasswd` htpasswd for authentication
|
75
|
+
* `:credentials` username/password key pair for authentication
|
76
|
+
* `:valid_on` 'any' match any restrictor, 'all' match all restrictors
|
77
|
+
* `:endpoint` specific uri to transmit (can include auth + path)
|
78
|
+
* `:method` HTTP method for transmission
|
79
|
+
|
33
80
|
# Info
|
34
81
|
* Carnivore: https://github.com/carnivore-rb/carnivore
|
35
82
|
* Repository: https://github.com/carnivore-rb/carnivore-http
|
data/carnivore-http.gemspec
CHANGED
data/lib/carnivore-http/http.rb
CHANGED
@@ -1,143 +1,37 @@
|
|
1
|
-
require '
|
2
|
-
require 'tempfile'
|
3
|
-
require 'carnivore/source'
|
4
|
-
require 'carnivore-http/utils'
|
1
|
+
require 'carnivore-http'
|
5
2
|
|
6
3
|
module Carnivore
|
7
4
|
class Source
|
8
|
-
|
9
|
-
# Carnivore HTTP source
|
10
|
-
class Http < Source
|
11
|
-
|
12
|
-
include Carnivore::Http::Utils::Params
|
13
|
-
|
14
|
-
# @return [Hash] source arguments
|
15
|
-
attr_reader :args
|
16
|
-
|
17
|
-
# Setup the source
|
18
|
-
#
|
19
|
-
# @params args [Hash] setup arguments
|
20
|
-
def setup(args={})
|
21
|
-
@args = default_args(args)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Default configuration arguments. If hash is provided, it
|
25
|
-
# will be merged into the default arguments.
|
26
|
-
#
|
27
|
-
# @param args [Hash]
|
28
|
-
# @return [Hash]
|
29
|
-
def default_args(args={})
|
30
|
-
Smash.new(
|
31
|
-
:bind => '0.0.0.0',
|
32
|
-
:port => '3000',
|
33
|
-
:auto_respond => true
|
34
|
-
).merge(args)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Tranmit message. The transmission can be a response
|
38
|
-
# back to an open connection, or a request to a remote
|
39
|
-
# source (remote carnivore-http source generally)
|
40
|
-
#
|
41
|
-
# @param message [Object] message to transmit
|
42
|
-
# @param extras [Object] argument list
|
43
|
-
def transmit(message, *extra)
|
44
|
-
options = extra.detect{|x| x.is_a?(Hash)} || {}
|
45
|
-
orig = extra.detect{|x| x.is_a?(Carnivore::Message)}
|
46
|
-
con = options[:connection]
|
47
|
-
if(orig && con.nil?)
|
48
|
-
con = orig[:message][:connection]
|
49
|
-
end
|
50
|
-
if(con) # response
|
51
|
-
payload = message.is_a?(String) ? message : MultiJson.dump(message)
|
52
|
-
# TODO: add `options` options for marshaling: json/xml/etc
|
53
|
-
debug "Transmit response type with payload: #{payload}"
|
54
|
-
con.respond(options[:code] || :ok, payload)
|
55
|
-
else # request
|
56
|
-
url = File.join("http://#{args[:bind]}:#{args[:port]}", options[:path].to_s)
|
57
|
-
method = (options[:method] || :post).to_sym
|
58
|
-
if(options[:headers])
|
59
|
-
base = HTTP.with_headers(options[:headers])
|
60
|
-
else
|
61
|
-
base = HTTP
|
62
|
-
end
|
63
|
-
payload = message.is_a?(String) ? message : MultiJson.dump(message)
|
64
|
-
debug "Transmit request type with payload: #{payload}"
|
65
|
-
base.send(method, url, :body => payload)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Confirm processing of message
|
70
|
-
#
|
71
|
-
# @param message [Carnivore::Message]
|
72
|
-
# @param args [Hash]
|
73
|
-
# @option args [Symbol] :code return code
|
74
|
-
def confirm(message, args={})
|
75
|
-
code = args.delete(:code) || :ok
|
76
|
-
args[:response_body] = 'Thanks' if code == :ok && args.empty?
|
77
|
-
debug "Confirming #{message} with: Code: #{code.inspect} Args: #{args.inspect}"
|
78
|
-
message[:message][:request].respond(code, args[:response_body] || args)
|
79
|
-
end
|
5
|
+
class Http < HttpSource
|
80
6
|
|
81
7
|
# Process requests
|
82
8
|
def process(*process_args)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
9
|
+
unless(@processing)
|
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!')
|
25
|
+
end
|
26
|
+
rescue => e
|
27
|
+
req.respond(:bad_request, "Failed to process request -> #{e}")
|
91
28
|
end
|
92
|
-
req.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
|
93
|
-
rescue => e
|
94
|
-
req.respond(:bad_request, "Failed to process request -> #{e}")
|
95
29
|
end
|
96
30
|
end
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
# Size limit for inline body
|
101
|
-
BODY_TO_FILE_SIZE = 1024 * 10 # 10K
|
102
|
-
|
103
|
-
# Build message hash from request
|
104
|
-
#
|
105
|
-
# @param con [Reel::Connection]
|
106
|
-
# @param req [Reel::Request]
|
107
|
-
# @return [Hash]
|
108
|
-
# @note
|
109
|
-
# if body size is greater than BODY_TO_FILE_SIZE
|
110
|
-
# the body will be a temp file instead of a string
|
111
|
-
def build_message(con, req)
|
112
|
-
msg = Smash.new(
|
113
|
-
:request => req,
|
114
|
-
:headers => Smash[
|
115
|
-
req.headers.map{ |k,v| [k.downcase.tr('-', '_'), v]}
|
116
|
-
],
|
117
|
-
:connection => con,
|
118
|
-
:query => parse_query_string(req.query_string)
|
119
|
-
)
|
120
|
-
if(msg[:headers][:content_type] == 'application/json')
|
121
|
-
msg[:body] = MultiJson.load(
|
122
|
-
req.body.to_s
|
123
|
-
)
|
124
|
-
elsif(msg[:headers][:content_type] == 'application/x-www-form-urlencoded')
|
125
|
-
msg[:body] = parse_query_string(
|
126
|
-
req.body.to_s
|
127
|
-
)
|
128
|
-
if(msg[:body].size == 1 && msg[:body].values.first.is_a?(Array) && msg[:body].values.first.empty?)
|
129
|
-
msg[:body] = msg[:body].keys.first
|
130
|
-
end
|
131
|
-
elsif(msg[:headers][:content_length].to_i > BODY_TO_FILE_SIZE)
|
132
|
-
msg[:body] = Tempfile.new('carnivore-http')
|
133
|
-
while((chunk = req.body.readpartial(2048)))
|
134
|
-
msg[:body] << chunk
|
135
|
-
end
|
136
|
-
msg[:body].rewind
|
31
|
+
true
|
137
32
|
else
|
138
|
-
|
33
|
+
false
|
139
34
|
end
|
140
|
-
format(msg)
|
141
35
|
end
|
142
36
|
|
143
37
|
end
|
@@ -76,28 +76,39 @@ module Carnivore
|
|
76
76
|
set_points
|
77
77
|
end
|
78
78
|
|
79
|
-
#
|
80
|
-
def
|
81
|
-
|
82
|
-
async.process
|
79
|
+
# Always auto start
|
80
|
+
def auto_process?
|
81
|
+
true
|
83
82
|
end
|
84
83
|
|
85
84
|
# Process requests
|
86
85
|
def process(*process_args)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
86
|
+
unless(processing)
|
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!')
|
101
|
+
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')
|
94
106
|
end
|
95
|
-
rescue => e
|
96
|
-
error "Failed to process message: #{e.class} - #{e}"
|
97
|
-
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
98
|
-
req.respond(:bad_request, 'Failed to process request')
|
99
107
|
end
|
100
108
|
end
|
109
|
+
true
|
110
|
+
else
|
111
|
+
false
|
101
112
|
end
|
102
113
|
end
|
103
114
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'bogo'
|
2
|
+
require 'carnivore-http/http'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
|
7
|
+
# Carnivore HTTP paths
|
8
|
+
class HttpPaths < HttpSource
|
9
|
+
|
10
|
+
finalizer :halt_listener
|
11
|
+
include Bogo::Memoization
|
12
|
+
|
13
|
+
# @return [String] end point path
|
14
|
+
attr_reader :http_path
|
15
|
+
# @return [Symbol] http method
|
16
|
+
attr_reader :http_method
|
17
|
+
|
18
|
+
# Kill listener on shutdown
|
19
|
+
def halt_listener
|
20
|
+
listener = memoize("#{args[:bind]}-#{args[:port]}", :global){ nil }
|
21
|
+
if(listener && listener.alive?)
|
22
|
+
listener.terminate
|
23
|
+
end
|
24
|
+
unmemoize("#{args[:bind]}-#{args[:port]}", :global)
|
25
|
+
unmemoize("#{args[:bind]}-#{args[:port]}-queues", :global)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Setup message queue for source
|
29
|
+
def setup(*_)
|
30
|
+
@http_path = args.fetch(:path, '/')
|
31
|
+
@http_method = args.fetch(:method, 'get').to_s.downcase.to_sym
|
32
|
+
if(message_queues[queue_key])
|
33
|
+
raise ArgumentError.new "Conflicting HTTP path source provided! path: #{http_path} method: #{http_method}"
|
34
|
+
else
|
35
|
+
message_queues[queue_key] = Queue.new
|
36
|
+
end
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
# Setup the HTTP listener source
|
41
|
+
def connect
|
42
|
+
start_listener!
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Queue] Message queue
|
46
|
+
def message_queues
|
47
|
+
memoize("#{args[:bind]}-#{args[:port]}-queues", :global) do
|
48
|
+
Smash.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def queue_key
|
54
|
+
"#{http_path}-#{http_method}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Queue]
|
58
|
+
def message_queue
|
59
|
+
message_queues[queue_key]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Start the HTTP(S) listener
|
63
|
+
def start_listener!
|
64
|
+
memoize("#{args[:bind]}-#{args[:port]}", :global) do
|
65
|
+
build_listener do |con|
|
66
|
+
con.each_request do |req|
|
67
|
+
begin
|
68
|
+
msg = build_message(con, req)
|
69
|
+
msg_queue = message_queues["#{req.path}-#{req.method.to_s.downcase}"]
|
70
|
+
if(msg_queue)
|
71
|
+
if(authorized?(msg))
|
72
|
+
msg_queue << msg
|
73
|
+
req.respond(:ok, 'So long and thanks for all the fish!')
|
74
|
+
else
|
75
|
+
req.respond(:unauthorized, 'You are not authorized to perform requested action!')
|
76
|
+
end
|
77
|
+
else
|
78
|
+
req.respond(:not_found, 'Requested path not found!')
|
79
|
+
end
|
80
|
+
rescue => e
|
81
|
+
req.respond(:bad_request, "Failed to process request -> #{e}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Object]
|
89
|
+
def receive(*_)
|
90
|
+
val = nil
|
91
|
+
until(val)
|
92
|
+
val = Celluloid::Future.new{ message_queue.pop }.value
|
93
|
+
end
|
94
|
+
val
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'reel'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'carnivore/source'
|
4
|
+
require 'carnivore-http/utils'
|
5
|
+
|
6
|
+
module Carnivore
|
7
|
+
class Source
|
8
|
+
|
9
|
+
# Carnivore HTTP source
|
10
|
+
class HttpSource < Source
|
11
|
+
|
12
|
+
trap_exit :retry_delivery_failure
|
13
|
+
|
14
|
+
include Carnivore::Http::Utils::Params
|
15
|
+
|
16
|
+
# @return [Hash] source arguments
|
17
|
+
attr_reader :args
|
18
|
+
# @return [Carnivore::Http::RetryDelivery]
|
19
|
+
attr_reader :retry_delivery
|
20
|
+
# @return [Array<IPAddr>] allowed request origin addresses
|
21
|
+
attr_reader :auth_allowed_origins
|
22
|
+
# @return [HTAuth::PasswdFile]
|
23
|
+
attr_reader :auth_htpasswd
|
24
|
+
|
25
|
+
# Setup the source
|
26
|
+
#
|
27
|
+
# @params args [Hash] setup arguments
|
28
|
+
def setup(args={})
|
29
|
+
require 'fileutils'
|
30
|
+
@args = default_args(args)
|
31
|
+
@retry_delivery = Carnivore::Http::RetryDelivery.new(retry_directory)
|
32
|
+
self.link retry_delivery
|
33
|
+
if(args.get(:authorization, :allowed_origins))
|
34
|
+
require 'ipaddr'
|
35
|
+
@allowed_origins = [args.get(:authorization, :allowed_origins)].flatten.compact.map do |origin_check|
|
36
|
+
IPAddr.new(origin_check)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if(args.get(:authorization, :htpasswd))
|
40
|
+
require 'htauth'
|
41
|
+
@auth_htpasswd = HTAuth::PasswdFile.open(
|
42
|
+
args.get(:authorization, :htpasswd)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
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
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [String, NilClass] directory storing failed messages
|
67
|
+
def retry_directory
|
68
|
+
if(args[:retry_directory])
|
69
|
+
FileUtils.mkdir_p(File.join(args[:retry_directory], name.to_s)).first
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String, NilClass] cache directory for initial writes
|
74
|
+
def retry_write_directory
|
75
|
+
base = retry_directory
|
76
|
+
if(base)
|
77
|
+
FileUtils.mkdir_p(File.join(base, '.write')).first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Default configuration arguments. If hash is provided, it
|
82
|
+
# will be merged into the default arguments.
|
83
|
+
#
|
84
|
+
# @param args [Hash]
|
85
|
+
# @return [Hash]
|
86
|
+
def default_args(args={})
|
87
|
+
Smash.new(
|
88
|
+
:bind => '0.0.0.0',
|
89
|
+
:port => '3000',
|
90
|
+
:auto_respond => true,
|
91
|
+
:retry_directory => '/tmp/.carnivore-resend'
|
92
|
+
).merge(args)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Always auto start
|
96
|
+
def auto_process?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# Message is authorized for processing
|
101
|
+
#
|
102
|
+
# @param message [Carnivore::Message]
|
103
|
+
# @return [TrueClass, FalseClass]
|
104
|
+
# @note Authorization is driven via the source configuration.
|
105
|
+
# Valid structure looks like:
|
106
|
+
# {
|
107
|
+
# :type => 'http',
|
108
|
+
# :args => {
|
109
|
+
# :authorization => {
|
110
|
+
# :allowed_origins => ['127.0.0.1', '192.168.0.2', '192.168.6.0/24'],
|
111
|
+
# :htpasswd => '/path/to/htpasswd.file',
|
112
|
+
# :credentials => {
|
113
|
+
# :username1 => 'password1'
|
114
|
+
# },
|
115
|
+
# :valid_on => :all # or :any
|
116
|
+
# }
|
117
|
+
# }
|
118
|
+
# }
|
119
|
+
# When multiple authorization items are provided, the
|
120
|
+
# `:valid_on` will define behavior. It will default to `:all`.
|
121
|
+
def authorized?(message)
|
122
|
+
if(args.fetch(:authorization))
|
123
|
+
valid_on = args.fetch(:authorization, :valid_on, :all).to_sym
|
124
|
+
case valid_on
|
125
|
+
when :all
|
126
|
+
allowed_origin?(message) &&
|
127
|
+
allowed_htpasswd?(message) &&
|
128
|
+
allowed_credentials?(message)
|
129
|
+
when :any
|
130
|
+
allowed_origin?(message) ||
|
131
|
+
allowed_htpasswd?(message) ||
|
132
|
+
allowed_credentials?(message)
|
133
|
+
when :none
|
134
|
+
true
|
135
|
+
else
|
136
|
+
raise ArgumentError.new "Unknown authorization `:valid_on` provided! Given: #{valid_on}. Allowed: `any` or `all`"
|
137
|
+
end
|
138
|
+
else
|
139
|
+
true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Check if message is allowed based on htpasswd file
|
144
|
+
#
|
145
|
+
# @param message [Carnivore::Message]
|
146
|
+
# @return [TrueClass, FalseClass]
|
147
|
+
def allowed_htpasswd?(message)
|
148
|
+
if(auth_htpasswd)
|
149
|
+
entry = auth_htpasswd.fetch(message[:message][:authentication][:username])
|
150
|
+
if(entry)
|
151
|
+
entry.authenticated?(message[:message][:authentication][:password])
|
152
|
+
else
|
153
|
+
false
|
154
|
+
end
|
155
|
+
else
|
156
|
+
true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if message is allowed based on config credentials
|
161
|
+
#
|
162
|
+
# @param message [Carnivore::Message]
|
163
|
+
# @return [TrueClass, FalseClass]
|
164
|
+
def allowed_credentials?(message)
|
165
|
+
if(creds = args.get(:authorization, :credentials))
|
166
|
+
creds[message[:message][:authentication][:username]] == message[:message][:authentication][:password]
|
167
|
+
else
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Check if message is allowed based on origin
|
173
|
+
#
|
174
|
+
# @param message [Carnivore::Message]
|
175
|
+
# @return [TrueClass, FalseClass]
|
176
|
+
def allowed_origin?(message)
|
177
|
+
if(auth_allowed_origins)
|
178
|
+
!!auth_allowed_origins.detect do |allowed_check|
|
179
|
+
allowed_check.include?(message[:message][:origin])
|
180
|
+
end
|
181
|
+
else
|
182
|
+
true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Tranmit message. The transmission can be a response
|
187
|
+
# back to an open connection, or a request to a remote
|
188
|
+
# source (remote carnivore-http source generally)
|
189
|
+
#
|
190
|
+
# @param message [Object] message to transmit
|
191
|
+
# @param extras [Object] argument list
|
192
|
+
def transmit(message, *extra)
|
193
|
+
options = extra.detect{|x| x.is_a?(Hash)} || {}
|
194
|
+
orig = extra.detect{|x| x.is_a?(Carnivore::Message)}
|
195
|
+
con = options[:connection]
|
196
|
+
if(orig && con.nil?)
|
197
|
+
con = orig[:message][:connection]
|
198
|
+
end
|
199
|
+
if(con) # response
|
200
|
+
payload = message.is_a?(String) ? message : MultiJson.dump(message)
|
201
|
+
# TODO: add `options` options for marshaling: json/xml/etc
|
202
|
+
code = options.fetch(:code, :ok)
|
203
|
+
info "Transmit response type with code: #{code}"
|
204
|
+
con.respond(code, payload)
|
205
|
+
else # request
|
206
|
+
if(args[:endpoint])
|
207
|
+
url = args[:endpoint]
|
208
|
+
else
|
209
|
+
url = "http#{'s' if args[:ssl]}://#{args[:bind]}"
|
210
|
+
if(args[:port])
|
211
|
+
url << ":#{args[:port]}"
|
212
|
+
end
|
213
|
+
url = URI.join(url, args.fetch(:path, '/')).to_s
|
214
|
+
end
|
215
|
+
if(options[:path])
|
216
|
+
url = URI.join(url, options[:path].to_s)
|
217
|
+
end
|
218
|
+
method = options.fetch(:method,
|
219
|
+
args.fetch(:method, :post)
|
220
|
+
).to_s.downcase.to_sym
|
221
|
+
message_id = message.is_a?(Hash) ? message.fetch(:id, Celluloid.uuid) : Celluloid.uuid
|
222
|
+
payload = message.is_a?(String) ? message : MultiJson.dump(message)
|
223
|
+
info "Transmit request type for Message ID: #{message_id}"
|
224
|
+
async.perform_transmission(message_id.to_s, payload, method, url, options.fetch(:headers, {}))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Transmit message to HTTP endpoint
|
229
|
+
#
|
230
|
+
# @param message_id [String]
|
231
|
+
# @param payload [String] serialized payload
|
232
|
+
# @param method [Symbol] HTTP method (:get, :post, etc)
|
233
|
+
# @param url [String] endpoint URL
|
234
|
+
# @param headers [Hash] request headers
|
235
|
+
# @return [NilClass]
|
236
|
+
def perform_transmission(message_id, payload, method, url, headers={})
|
237
|
+
write_for_retry(message_id, payload, method, url, headers)
|
238
|
+
retry_delivery.async.attempt_redelivery(message_id)
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
|
242
|
+
# Persist message if enabled for send retry
|
243
|
+
#
|
244
|
+
# @param message_id [String] ID of originating message
|
245
|
+
# @param payload [String] serialized payload
|
246
|
+
# @param method [Symbol] HTTP method (:get, :post, etc)
|
247
|
+
# @param url [String] endpoint URL
|
248
|
+
# @param headers [Hash] request headers
|
249
|
+
# @return [TrueClass, FalseClass] message persisted
|
250
|
+
def write_for_retry(message_id, payload, method, url, headers)
|
251
|
+
data = {
|
252
|
+
:message_id => message_id,
|
253
|
+
:payload => payload,
|
254
|
+
:method => method,
|
255
|
+
:url => url,
|
256
|
+
:headers => headers
|
257
|
+
}
|
258
|
+
if(retry_directory)
|
259
|
+
stage_path = File.join(retry_write_directory, "#{message_id}.json")
|
260
|
+
final_path = File.join(retry_directory, File.basename(stage_path))
|
261
|
+
File.open(stage_path, 'w+') do |file|
|
262
|
+
file.write MultiJson.dump(data)
|
263
|
+
end
|
264
|
+
FileUtils.move(stage_path, final_path)
|
265
|
+
info "Failed message (ID: #{message_id}) persisted for resend"
|
266
|
+
true
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Confirm processing of message
|
271
|
+
#
|
272
|
+
# @param message [Carnivore::Message]
|
273
|
+
# @param args [Hash]
|
274
|
+
# @option args [Symbol] :code return code
|
275
|
+
def confirm(message, args={})
|
276
|
+
code = args.delete(:code) || :ok
|
277
|
+
args[:response_body] = 'Thanks' if code == :ok && args.empty?
|
278
|
+
debug "Confirming #{message} with: Code: #{code.inspect} Args: #{args.inspect}"
|
279
|
+
message[:message][:request].respond(code, args[:response_body] || args)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Initialize http listener correctly based on configuration
|
283
|
+
#
|
284
|
+
# @param block [Proc] processing block
|
285
|
+
# @return [Reel::Server::HTTP, Reel::Server::HTTPS]
|
286
|
+
def build_listener(&block)
|
287
|
+
if(args[:ssl])
|
288
|
+
ssl_config = Smash.new(args[:ssl][key].dup)
|
289
|
+
[:key, :cert].each do |key|
|
290
|
+
if(ssl_config[key])
|
291
|
+
ssl_config[key] = File.open(ssl_config.delete(key))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
Reel::Server::HTTPS.supervise(args[:bind], args[:port], ssl_config, &block)
|
295
|
+
else
|
296
|
+
Reel::Server::HTTP.supervise(args[:bind], args[:port], &block)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Size limit for inline body
|
301
|
+
BODY_TO_FILE_SIZE = 1024 * 10 # 10K
|
302
|
+
|
303
|
+
# Build message hash from request
|
304
|
+
#
|
305
|
+
# @param con [Reel::Connection]
|
306
|
+
# @param req [Reel::Request]
|
307
|
+
# @return [Hash]
|
308
|
+
# @note
|
309
|
+
# if body size is greater than BODY_TO_FILE_SIZE
|
310
|
+
# the body will be a temp file instead of a string
|
311
|
+
def build_message(con, req)
|
312
|
+
msg = Smash.new(
|
313
|
+
:request => req,
|
314
|
+
:headers => Smash[
|
315
|
+
req.headers.map{ |k,v| [k.downcase.tr('-', '_'), v]}
|
316
|
+
],
|
317
|
+
:connection => con,
|
318
|
+
:query => parse_query_string(req.query_string),
|
319
|
+
:origin => req.remote_addr,
|
320
|
+
:authentication => {}
|
321
|
+
)
|
322
|
+
if(msg[:headers][:content_type] == 'application/json')
|
323
|
+
msg[:body] = MultiJson.load(
|
324
|
+
req.body.to_s
|
325
|
+
)
|
326
|
+
elsif(msg[:headers][:content_type] == 'application/x-www-form-urlencoded')
|
327
|
+
msg[:body] = parse_query_string(
|
328
|
+
req.body.to_s
|
329
|
+
)
|
330
|
+
if(msg[:body].size == 1 && msg[:body].values.first.is_a?(Array) && msg[:body].values.first.empty?)
|
331
|
+
msg[:body] = msg[:body].keys.first
|
332
|
+
end
|
333
|
+
elsif(msg[:headers][:content_length].to_i > BODY_TO_FILE_SIZE)
|
334
|
+
msg[:body] = Tempfile.new('carnivore-http')
|
335
|
+
while((chunk = req.body.readpartial(2048)))
|
336
|
+
msg[:body] << chunk
|
337
|
+
end
|
338
|
+
msg[:body].rewind
|
339
|
+
else
|
340
|
+
msg[:body] = req.body.to_s
|
341
|
+
end
|
342
|
+
if(msg[:headers][:authorization])
|
343
|
+
user, pass = Base64.urlsafe_decode64(
|
344
|
+
msg[:headers][:authorization].split(' ').last
|
345
|
+
).split(':', 2)
|
346
|
+
msg[:authentication] = {
|
347
|
+
:username => user,
|
348
|
+
:password => pass
|
349
|
+
}
|
350
|
+
end
|
351
|
+
msg
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'carnivore-http'
|
2
|
+
|
3
|
+
module Carnivore
|
4
|
+
module Http
|
5
|
+
|
6
|
+
class RetryDelivery
|
7
|
+
|
8
|
+
include Celluloid
|
9
|
+
include Carnivore::Utils::Logging
|
10
|
+
|
11
|
+
# @return [String] message directory
|
12
|
+
attr_reader :message_directory
|
13
|
+
|
14
|
+
# Create new instance
|
15
|
+
#
|
16
|
+
# @param directory [String] path to messages
|
17
|
+
# @return [self]
|
18
|
+
def initialize(directory)
|
19
|
+
@message_directory = directory
|
20
|
+
every(60){ attempt_redelivery }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Attempt to deliver messages found in message directory
|
24
|
+
#
|
25
|
+
# @return [TrueClass, FalseClass] attempt was made
|
26
|
+
# @note will not attempt if attempt is currently in progress
|
27
|
+
def attempt_redelivery(message_id = '*')
|
28
|
+
attempt = false
|
29
|
+
begin
|
30
|
+
unless(@delivering)
|
31
|
+
@delivering = true
|
32
|
+
attempt = true
|
33
|
+
Dir.glob(File.join(message_directory, "#{message_id}.json")).each do |file|
|
34
|
+
debug "Redelivery processing: #{file}"
|
35
|
+
begin
|
36
|
+
args = MultiJson.load(File.read(file)).to_smash
|
37
|
+
debug "Restored from file #{file}: #{args.inspect}"
|
38
|
+
if(redeliver(args[:message_id], args[:payload], args[:method], args[:url], args[:headers]))
|
39
|
+
FileUtils.rm(file)
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
error "Failed to process file (#{file}): #{e.class}: #{e}"
|
43
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue => e
|
48
|
+
error "Unexpected error encountered during message redelivery! #{e.class}: #{e}"
|
49
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
50
|
+
ensure
|
51
|
+
@delivering = false
|
52
|
+
end
|
53
|
+
attempt
|
54
|
+
end
|
55
|
+
|
56
|
+
# Attempt redelivery of message
|
57
|
+
#
|
58
|
+
# @param message [Carnivore::Message]
|
59
|
+
# @param payload [String] serialized payload
|
60
|
+
# @param method [Symbol] HTTP method (:get, :post, etc)
|
61
|
+
# @param url [String] endpoint URL
|
62
|
+
# @param headers [Hash] request headers
|
63
|
+
# @return [TrueClass, FalseClass] redelivery was successful
|
64
|
+
def redeliver(message_id, payload, method, url, headers)
|
65
|
+
begin
|
66
|
+
base = headers.empty? ? HTTP : HTTP.with_headers(headers)
|
67
|
+
uri = URI.parse(url)
|
68
|
+
if(uri.userinfo)
|
69
|
+
base = base.basic_auth(:user => uri.user, :pass => uri.password)
|
70
|
+
end
|
71
|
+
result = base.send(method, url, :body => payload)
|
72
|
+
if(result.code < 200 || result.code > 299)
|
73
|
+
error "Invalid response code received for #{message_id}: #{result.code} - #{result.reason}"
|
74
|
+
false
|
75
|
+
else
|
76
|
+
info "Successful delivery of message on retry! Message ID: #{message_id}"
|
77
|
+
true
|
78
|
+
end
|
79
|
+
rescue => e
|
80
|
+
error "Transmission redelivery failure (Message ID: #{message_id}) - #{e.class}: #{e}"
|
81
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/carnivore-http.rb
CHANGED
@@ -6,10 +6,13 @@ module Carnivore
|
|
6
6
|
# HTTP namespace
|
7
7
|
module Http
|
8
8
|
autoload :PointBuilder, 'carnivore-http/point_builder'
|
9
|
+
autoload :RetryDelivery, 'carnivore-http/retry_delivery'
|
9
10
|
end
|
10
11
|
|
11
12
|
class Source
|
12
13
|
autoload :Http, 'carnivore-http/http'
|
14
|
+
autoload :HttpSource, 'carnivore-http/http_source'
|
15
|
+
autoload :HttpPaths, 'carnivore-http/http_paths'
|
13
16
|
autoload :HttpEndpoints, 'carnivore-http/http_endpoints'
|
14
17
|
end
|
15
18
|
|
@@ -20,4 +23,5 @@ module Carnivore
|
|
20
23
|
end
|
21
24
|
|
22
25
|
Carnivore::Source.provide(:http, 'carnivore-http/http')
|
26
|
+
Carnivore::Source.provide(:http_paths, 'carnivore-http/http_paths')
|
23
27
|
Carnivore::Source.provide(:http_endpoints, 'carnivore-http/http_endpoints')
|
data/test/specs/http.rb
CHANGED
@@ -4,62 +4,65 @@ require 'carnivore-http'
|
|
4
4
|
|
5
5
|
describe 'Carnivore::Source::Http' do
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
)
|
18
|
-
|
19
|
-
source_wait
|
20
|
-
Carnivore::Supervisor.supervisor[:http_source].wont_be_nil
|
21
|
-
t.terminate
|
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!
|
22
19
|
end
|
20
|
+
@runner = Thread.new{ Carnivore.start! }
|
21
|
+
source_wait
|
22
|
+
end
|
23
23
|
|
24
|
+
after do
|
25
|
+
@runner.terminate
|
24
26
|
end
|
25
27
|
|
26
28
|
describe 'HTTP source based communication' do
|
29
|
+
|
27
30
|
before do
|
28
|
-
MessageStore.
|
29
|
-
Carnivore::Source.build(
|
30
|
-
:type => :http,
|
31
|
-
:args => {
|
32
|
-
:name => :http_source,
|
33
|
-
:bind => '127.0.0.1',
|
34
|
-
:port => '8705'
|
35
|
-
}
|
36
|
-
).add_callback(:store) do |message|
|
37
|
-
MessageStore.messages.push(message[:message][:body])
|
38
|
-
end
|
39
|
-
@runner = Thread.new{ Carnivore.start! }
|
40
|
-
source_wait
|
31
|
+
MessageStore.messages.clear
|
41
32
|
end
|
42
33
|
|
43
|
-
|
44
|
-
|
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
|
+
|
45
40
|
end
|
46
41
|
|
47
42
|
describe 'message transmissions' do
|
43
|
+
|
48
44
|
it 'should accept message transmits' do
|
49
45
|
Carnivore::Supervisor.supervisor[:http_source].transmit('test message')
|
50
46
|
end
|
51
47
|
|
52
48
|
it 'should receive messages' do
|
53
49
|
Carnivore::Supervisor.supervisor[:http_source].transmit('test message 2')
|
54
|
-
source_wait
|
50
|
+
source_wait(2) do
|
51
|
+
!MessageStore.messages.empty?
|
52
|
+
end
|
53
|
+
MessageStore.messages.wont_be_empty
|
55
54
|
MessageStore.messages.pop.must_equal 'test message 2'
|
56
55
|
end
|
57
56
|
|
58
57
|
it 'should accept http requests' do
|
59
58
|
HTTP.get('http://127.0.0.1:8705/')
|
60
|
-
source_wait
|
59
|
+
source_wait(2) do
|
60
|
+
!MessageStore.messages.empty?
|
61
|
+
end
|
62
|
+
MessageStore.messages.wont_be_empty
|
61
63
|
MessageStore.messages.pop.wont_be_nil
|
62
64
|
end
|
65
|
+
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
data/test/specs/paths.rb
ADDED
@@ -0,0 +1,87 @@
|
|
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
|
+
}
|
21
|
+
).add_callback(:store) do |message|
|
22
|
+
MessageStore.messages.push(message[:message][:body])
|
23
|
+
message.confirm!
|
24
|
+
end
|
25
|
+
Carnivore::Source.build(
|
26
|
+
:type => :http_paths,
|
27
|
+
:args => {
|
28
|
+
:name => :ohai_source,
|
29
|
+
:path => '/ohai',
|
30
|
+
:method => :get,
|
31
|
+
:bind => '127.0.0.1',
|
32
|
+
:port => '8706'
|
33
|
+
}
|
34
|
+
).add_callback(:store) do |message|
|
35
|
+
MessageStore.messages.push(message[:message][:body])
|
36
|
+
message.confirm!
|
37
|
+
end
|
38
|
+
@runner = Thread.new{ Carnivore.start! }
|
39
|
+
source_wait
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
after do
|
44
|
+
@runner.terminate
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'HTTP source based communication' do
|
48
|
+
|
49
|
+
before do
|
50
|
+
MessageStore.messages.clear
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'Building an HTTP based source' do
|
54
|
+
|
55
|
+
it 'returns the sources' do
|
56
|
+
Carnivore::Supervisor.supervisor[:fubar_source].wont_be_nil
|
57
|
+
Carnivore::Supervisor.supervisor[:ohai_source].wont_be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'message transmissions' do
|
63
|
+
|
64
|
+
it 'should accept message transmits' do
|
65
|
+
Carnivore::Supervisor.supervisor[:fubar_source].transmit('test message')
|
66
|
+
Carnivore::Supervisor.supervisor[:ohai_source].transmit('test message')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should receive messages' do
|
70
|
+
Carnivore::Supervisor.supervisor[:fubar_source].transmit('test message to fubar')
|
71
|
+
source_wait(4) do
|
72
|
+
!MessageStore.messages.empty?
|
73
|
+
end
|
74
|
+
MessageStore.messages.wont_be_empty
|
75
|
+
MessageStore.messages.pop.must_equal 'test message to fubar'
|
76
|
+
Carnivore::Supervisor.supervisor[:ohai_source].transmit('test message to ohai')
|
77
|
+
source_wait(4) do
|
78
|
+
!MessageStore.messages.empty?
|
79
|
+
end
|
80
|
+
MessageStore.messages.wont_be_empty
|
81
|
+
MessageStore.messages.pop.must_equal 'test message to ohai'
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
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.
|
4
|
+
version: 0.2.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:
|
11
|
+
date: 2015-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: carnivore
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: htauth
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Carnivore HTTP source
|
56
70
|
email: chrisroberts.code@gmail.com
|
57
71
|
executables: []
|
@@ -68,11 +82,15 @@ files:
|
|
68
82
|
- lib/carnivore-http.rb
|
69
83
|
- lib/carnivore-http/http.rb
|
70
84
|
- lib/carnivore-http/http_endpoints.rb
|
85
|
+
- lib/carnivore-http/http_paths.rb
|
86
|
+
- lib/carnivore-http/http_source.rb
|
71
87
|
- lib/carnivore-http/point_builder.rb
|
88
|
+
- lib/carnivore-http/retry_delivery.rb
|
72
89
|
- lib/carnivore-http/utils.rb
|
73
90
|
- lib/carnivore-http/version.rb
|
74
91
|
- test/spec.rb
|
75
92
|
- test/specs/http.rb
|
93
|
+
- test/specs/paths.rb
|
76
94
|
homepage: https://github.com/carnivore-rb/carnivore-http
|
77
95
|
licenses:
|
78
96
|
- Apache 2.0
|