carnivore-http 0.1.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.
- data/CHANGELOG.md +2 -0
- data/README.md +36 -0
- data/carnivore-http.gemspec +16 -0
- data/lib/carnivore-http.rb +5 -0
- data/lib/carnivore-http/http.rb +54 -0
- data/lib/carnivore-http/http_endpoints.rb +85 -0
- data/lib/carnivore-http/point_builder.rb +161 -0
- data/lib/carnivore-http/version.rb +7 -0
- metadata +101 -0
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Carnivore HTTP
|
2
|
+
|
3
|
+
Provides HTTP `Carnivore::Source`
|
4
|
+
|
5
|
+
# Usage
|
6
|
+
|
7
|
+
## HTTP
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'carnivore'
|
11
|
+
require 'carnivore-http'
|
12
|
+
|
13
|
+
Carnivore.configure do
|
14
|
+
source = Carnivore::Source.build(
|
15
|
+
:type => :http, :args => {:port => 8080}
|
16
|
+
)
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
## HTTP with configured end points
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'carnivore'
|
24
|
+
require 'carnivore-http'
|
25
|
+
|
26
|
+
Carnivore.configure do
|
27
|
+
source = Carnivore::Source.build(
|
28
|
+
:type => :http_endpoints, :args => {:auto_respond => false}
|
29
|
+
)
|
30
|
+
end.start!
|
31
|
+
```
|
32
|
+
|
33
|
+
# Info
|
34
|
+
* Carnivore: https://github.com/heavywater/carnivore
|
35
|
+
* Repository: https://github.com/heavywater/carnivore-http
|
36
|
+
* IRC: Freenode @ #heavywater
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'carnivore-http/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'carnivore-http'
|
5
|
+
s.version = Carnivore::Http::VERSION.version
|
6
|
+
s.summary = 'Message processing helper'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'chrisroberts.code@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/heavywater/carnivore-http'
|
10
|
+
s.description = 'Carnivore HTTP source'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.add_dependency 'carnivore', '>= 0.1.8'
|
13
|
+
s.add_dependency 'reel'
|
14
|
+
s.add_dependency 'blockenspiel'
|
15
|
+
s.files = Dir['**/*']
|
16
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'reel'
|
2
|
+
require 'carnivore/source'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
|
7
|
+
class Http < Source
|
8
|
+
|
9
|
+
attr_reader :args
|
10
|
+
|
11
|
+
def setup(args={})
|
12
|
+
@args = default_args(args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_args(args)
|
16
|
+
{
|
17
|
+
:bind => '0.0.0.0',
|
18
|
+
:port => '3000',
|
19
|
+
:auto_respond => true
|
20
|
+
}.merge(args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def transmit(message, orignal_message_or_connection, args={})
|
24
|
+
if(original_message_or_connection.is_a?(Message))
|
25
|
+
con = original_message_or_connection[:connection]
|
26
|
+
else
|
27
|
+
con = original_message_or_connection[:connection]
|
28
|
+
end
|
29
|
+
# TODO: add `args` options for marshaling: json/xml/etc
|
30
|
+
con.respond(args[:code] || :ok, message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def process(*process_args)
|
34
|
+
srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
|
35
|
+
while(req = con.request)
|
36
|
+
begin
|
37
|
+
msg = format(:request => req, :body => req.body, :connection => con)
|
38
|
+
callbacks.each do |name|
|
39
|
+
c_name = callback_name(name)
|
40
|
+
debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{c_name})>"
|
41
|
+
Celluloid::Actor[c_name].async.call(msg)
|
42
|
+
end
|
43
|
+
con.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
|
44
|
+
rescue => e
|
45
|
+
con.respond(:bad_request, 'Failed to process request')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'carnivore-http/http'
|
2
|
+
require 'carnivore-http/point_builder'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
class HttpEndpoints < Http
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def register(args={})
|
11
|
+
args = Hash[*(
|
12
|
+
args.map do |k,v|
|
13
|
+
[k.to_sym, v]
|
14
|
+
end.flatten
|
15
|
+
)]
|
16
|
+
builder = {:name => args[:name], :base_path => args[:base_path]}
|
17
|
+
if(res = builder.find_all{|x,y| y.nil})
|
18
|
+
raise ArgumentError.new("Missing required argument! (#{res.map(&:first).join(',')})")
|
19
|
+
end
|
20
|
+
builders[builder[:name].to_sym] = builder[:base_path]
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def builders
|
25
|
+
@point_builders ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_builder(name)
|
29
|
+
if(builders[name.to_sym])
|
30
|
+
require File.join(builders[name.to_sym], name)
|
31
|
+
else
|
32
|
+
raise NameError.new("Requested end point builder not found (#{name})")
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup!
|
38
|
+
only = Carnivore::Config.get(:http_endpoints, :only)
|
39
|
+
except = Carnivore::Config.get(:http_endpoints, :except)
|
40
|
+
# NOTE: Except has higher precedence than only
|
41
|
+
builders.keys.each do |name|
|
42
|
+
next if only && !only.include?(name.to_s)
|
43
|
+
next if except && except.include?(name.to_s)
|
44
|
+
load_builder(name)
|
45
|
+
end
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :points
|
52
|
+
|
53
|
+
def setup(args={})
|
54
|
+
super
|
55
|
+
@conf_key = (args[:config_key] || :http_endpoints).to_sym
|
56
|
+
set_points
|
57
|
+
end
|
58
|
+
|
59
|
+
def process(*process_args)
|
60
|
+
srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
|
61
|
+
con.each_request do |req|
|
62
|
+
begin
|
63
|
+
msg = format(:request => req, :body => req.body, :connection => con)
|
64
|
+
unless(@points.deliver(msg))
|
65
|
+
con.respond(:ok, 'So long, and thanks for all the fish!')
|
66
|
+
end
|
67
|
+
rescue => e
|
68
|
+
error "Failed to process message: #{e.class} - #{e}"
|
69
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
70
|
+
con.respond(:bad_request, 'Failed to process request')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_points
|
77
|
+
@points = PointBuilder.new(
|
78
|
+
:only => Carnivore::Config.get(@conf_key, :only),
|
79
|
+
:except => Carnivore::Config.get(@conf_key, :except)
|
80
|
+
)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'blockenspiel'
|
2
|
+
require 'singleton'
|
3
|
+
require 'carnivore/utils'
|
4
|
+
require 'celluloid'
|
5
|
+
|
6
|
+
module Carnivore
|
7
|
+
class PointBuilder
|
8
|
+
|
9
|
+
class Endpoint
|
10
|
+
|
11
|
+
include Celluloid
|
12
|
+
include Carnivore::Utils::Logging
|
13
|
+
|
14
|
+
attr_reader :endpoint, :type
|
15
|
+
|
16
|
+
def initialize(type, endpoint, block)
|
17
|
+
@endpoint = endpoint
|
18
|
+
@type = type
|
19
|
+
define_singleton_method(:run, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"<Endpoint[#{endpoint}]>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"<Endpoint[#{endpoint}] type=#{type} objectid=#{self.object_id}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
include Carnivore::Utils::Params
|
33
|
+
include Celluloid::Logger
|
34
|
+
include Blockenspiel::DSL
|
35
|
+
|
36
|
+
attr_reader :static, :regex, :only, :except
|
37
|
+
|
38
|
+
def initialize(args)
|
39
|
+
@only = args[:only]
|
40
|
+
@except = args[:except]
|
41
|
+
@static = {}
|
42
|
+
@regex = {}
|
43
|
+
@callback_names = {}
|
44
|
+
@endpoint_supervisor = Celluloid::SupervisionGroup.run!
|
45
|
+
load_endpoints!
|
46
|
+
end
|
47
|
+
|
48
|
+
[:get, :put, :post, :delete, :head, :options, :trace].each do |name|
|
49
|
+
define_method(name) do |regexp_or_string, args={}, &block|
|
50
|
+
endpoint(name, regexp_or_string, args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
dsl_methods false
|
55
|
+
|
56
|
+
def deliver(msg)
|
57
|
+
type = msg[:message][:request].method.to_s.downcase.to_sym
|
58
|
+
path = msg[:message][:request].url
|
59
|
+
static_points(msg, type, path) || regex_points(msg, type, path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def static_points(msg, type, path)
|
63
|
+
if(static[type])
|
64
|
+
match = static[type].keys.detect do |point|
|
65
|
+
path.sub(%r{/$}, '') == point
|
66
|
+
end
|
67
|
+
if(match)
|
68
|
+
if(static[type][match][:async])
|
69
|
+
Celluloid::Actor[callback_name(match, type)].async.run(msg)
|
70
|
+
false
|
71
|
+
else
|
72
|
+
Celluloid::Actor[callback_name(match, type)].run(msg)
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def regex_points(msg, type, path)
|
80
|
+
if(regex[type])
|
81
|
+
match = regex[type].keys.map do |point|
|
82
|
+
unless((res = path.scan(/^#{point}$/)).empty?)
|
83
|
+
[point, res.first.is_a?(Array) ? res.first : []]
|
84
|
+
end
|
85
|
+
end.compact.first
|
86
|
+
unless(match.empty?)
|
87
|
+
if(regex[type][match.first][:async])
|
88
|
+
Celluloid::Actor[callback_name(match.first, type)].async.run([msg] + match.last)
|
89
|
+
false
|
90
|
+
else
|
91
|
+
Celluloid::Actor[callback_name(match.first, type)].run([msg] + match.last)
|
92
|
+
true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def callback_name(point, type)
|
99
|
+
key = "#{point}_#{type}"
|
100
|
+
unless(@callback_names[key])
|
101
|
+
@callback_names[key] = Digest::SHA256.hexdigest(key)
|
102
|
+
end
|
103
|
+
@callback_names[key]
|
104
|
+
end
|
105
|
+
|
106
|
+
def endpoint(request_type, regexp_or_string, args, &block)
|
107
|
+
request_type = request_type.to_sym
|
108
|
+
if(regexp_or_string.is_a?(Regexp))
|
109
|
+
regex[request_type] ||= {}
|
110
|
+
regex[request_type][regexp_or_string] = args
|
111
|
+
else
|
112
|
+
static[request_type] ||= {}
|
113
|
+
static[request_type][regexp_or_string.sub(%r{/$}, '')] = args
|
114
|
+
end
|
115
|
+
if(args[:workers] && args[:workers].to_i > 1)
|
116
|
+
@endpoint_supervisor.pool(Endpoint,
|
117
|
+
as: callback_name(regexp_or_string, request_type), size: args[:workers.to_i],
|
118
|
+
args: [request_type, regexp_or_string, block]
|
119
|
+
)
|
120
|
+
else
|
121
|
+
@endpoint_supervisor.supervise_as(
|
122
|
+
callback_name(regexp_or_string, request_type), Endpoint, request_type, regexp_or_string, block
|
123
|
+
)
|
124
|
+
end
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
def endpoints
|
129
|
+
[static, regex]
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def load_endpoints!
|
135
|
+
self.class.storage.each do |name, block|
|
136
|
+
next if only && !only.include?(name.to_s)
|
137
|
+
next if except && except.include?(name.to_s)
|
138
|
+
Blockenspiel.invoke(block, self)
|
139
|
+
end
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
class << self
|
144
|
+
def define(&block)
|
145
|
+
name = File.basename(
|
146
|
+
caller.first.match(%r{.*?:}).to_s.sub(':', '')
|
147
|
+
).sub('.rb', '')
|
148
|
+
store(name, block)
|
149
|
+
end
|
150
|
+
|
151
|
+
def store(name, block)
|
152
|
+
storage[name.to_sym] = block
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
def storage
|
157
|
+
@storage ||= {}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: carnivore-http
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Roberts
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: carnivore
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: reel
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: blockenspiel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Carnivore HTTP source
|
63
|
+
email: chrisroberts.code@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- lib/carnivore-http/point_builder.rb
|
69
|
+
- lib/carnivore-http/version.rb
|
70
|
+
- lib/carnivore-http/http.rb
|
71
|
+
- lib/carnivore-http/http_endpoints.rb
|
72
|
+
- lib/carnivore-http.rb
|
73
|
+
- README.md
|
74
|
+
- carnivore-http.gemspec
|
75
|
+
- CHANGELOG.md
|
76
|
+
homepage: https://github.com/heavywater/carnivore-http
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.24
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: Message processing helper
|
100
|
+
test_files: []
|
101
|
+
has_rdoc:
|