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 ADDED
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
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,5 @@
1
+ require 'carnivore-http/version'
2
+ require 'carnivore'
3
+
4
+ Carnivore::Source.provide(:http, 'carnivore-http/http')
5
+ Carnivore::Source.provide(:http_endpoints, 'carnivore-http/http_endpoints')
@@ -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
@@ -0,0 +1,7 @@
1
+ module Carnivore
2
+ module Http
3
+ class Version < Gem::Version
4
+ end
5
+ VERSION = Version.new('0.1.0')
6
+ end
7
+ 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: