carnivore-http 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: