carnivore-http 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.1.6
2
+ * Add support for point builder subclassing
3
+ * DRY out message generation to be consistent between http and endpoint
4
+ * Set max size on body and store in temp file if > max size
5
+
1
6
  # v0.1.4
2
7
  * Include custom `connect` to start source
3
8
  * Pull query from body if found
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,25 @@
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.lock CHANGED
@@ -1,48 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- carnivore-http (0.1.3)
4
+ carnivore-http (0.1.5)
5
5
  blockenspiel
6
6
  carnivore (>= 0.1.8)
7
- reel
8
-
9
- PATH
10
- remote: ../carnivore
11
- specs:
12
- carnivore (0.1.11)
13
- celluloid
14
- mixlib-config
15
- multi_json
7
+ reel (~> 0.5.0)
16
8
 
17
9
  GEM
18
10
  remote: https://rubygems.org/
19
11
  specs:
20
12
  blockenspiel (0.4.5)
13
+ carnivore (0.2.2)
14
+ celluloid
15
+ hashie
16
+ mixlib-config
17
+ multi_json
21
18
  celluloid (0.15.2)
22
19
  timers (~> 1.1.0)
23
20
  celluloid-io (0.15.0)
24
21
  celluloid (>= 0.15.0)
25
22
  nio4r (>= 0.5.0)
26
- http (0.5.0)
27
- http_parser.rb
28
- http_parser.rb (0.6.0.beta.2)
29
- mixlib-config (2.0.0)
30
- multi_json (1.8.2)
31
- nio4r (0.5.0)
32
- reel (0.4.0)
23
+ hashie (2.1.1)
24
+ http (0.6.0)
25
+ http_parser.rb (~> 0.6.0)
26
+ http_parser.rb (0.6.0)
27
+ mixlib-config (2.1.0)
28
+ multi_json (1.10.0)
29
+ nio4r (1.0.0)
30
+ reel (0.5.0)
33
31
  celluloid (>= 0.15.1)
34
32
  celluloid-io (>= 0.15.0)
35
- http (>= 0.5.0)
36
- http_parser.rb (>= 0.6.0.beta.2)
37
- websocket_parser (>= 0.1.4)
33
+ http (>= 0.6.0.pre)
34
+ http_parser.rb (>= 0.6.0)
35
+ websocket_parser (>= 0.1.6)
38
36
  timers (1.1.0)
39
- websocket_parser (0.1.4)
40
- http
37
+ websocket_parser (0.1.6)
41
38
 
42
39
  PLATFORMS
43
40
  java
44
41
  ruby
45
42
 
46
43
  DEPENDENCIES
47
- carnivore!
48
44
  carnivore-http!
data/LICENSE ADDED
@@ -0,0 +1,13 @@
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.
@@ -8,9 +8,10 @@ Gem::Specification.new do |s|
8
8
  s.email = 'chrisroberts.code@gmail.com'
9
9
  s.homepage = 'https://github.com/carnivore-rb/carnivore-http'
10
10
  s.description = 'Carnivore HTTP source'
11
+ s.license = 'Apache 2.0'
11
12
  s.require_path = 'lib'
12
13
  s.add_dependency 'carnivore', '>= 0.1.8'
13
- s.add_dependency 'reel'
14
+ s.add_dependency 'reel', '~> 0.5.0'
14
15
  s.add_dependency 'blockenspiel'
15
16
  s.files = Dir['**/*']
16
17
  end
@@ -1,5 +1,23 @@
1
1
  require 'carnivore-http/version'
2
2
  require 'carnivore'
3
+ require 'multi_json'
4
+
5
+ module Carnivore
6
+ # HTTP namespace
7
+ module Http
8
+ autoload :PointBuilder, 'carnivore-http/point_builder'
9
+ end
10
+
11
+ class Source
12
+ autoload :Http, 'carnivore-http/http'
13
+ autoload :HttpEndpoints, 'carnivore-http/http_endpoints'
14
+ end
15
+
16
+ autoload :Utils, 'carnivore-http/utils'
17
+
18
+ # Compat for old naming
19
+ autoload :PointBuilder, 'carnivore-http/point_builder'
20
+ end
3
21
 
4
22
  Carnivore::Source.provide(:http, 'carnivore-http/http')
5
23
  Carnivore::Source.provide(:http_endpoints, 'carnivore-http/http_endpoints')
@@ -1,31 +1,45 @@
1
1
  require 'reel'
2
+ require 'tempfile'
2
3
  require 'carnivore/source'
3
4
  require 'carnivore-http/utils'
4
5
 
5
6
  module Carnivore
6
7
  class Source
7
8
 
9
+ # Carnivore HTTP source
8
10
  class Http < Source
9
11
 
10
12
  include Carnivore::Http::Utils::Params
11
13
 
14
+ # @return [Hash] source arguments
12
15
  attr_reader :args
13
16
 
17
+ # Setup the source
18
+ #
19
+ # @params args [Hash] setup arguments
14
20
  def setup(args={})
15
21
  @args = default_args(args)
16
22
  end
17
23
 
18
- def default_args(args)
19
- {
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(
20
31
  :bind => '0.0.0.0',
21
32
  :port => '3000',
22
33
  :auto_respond => true
23
- }.merge(args)
34
+ ).merge(args)
24
35
  end
25
36
 
26
- # Transmit can be one of two things:
27
- # 1. Response back to an open connection
28
- # 2. Request to a remote source
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
29
43
  def transmit(message, *extra)
30
44
  options = extra.detect{|x| x.is_a?(Hash)} || {}
31
45
  orig = extra.detect{|x| x.is_a?(Carnivore::Message)}
@@ -52,40 +66,74 @@ module Carnivore
52
66
  end
53
67
  end
54
68
 
69
+ # Confirm processing of message
70
+ #
71
+ # @param message [Carnivore::Message]
72
+ # @param args [Hash]
73
+ # @option args [Symbol] :code return code
55
74
  def confirm(message, args={})
56
- if(args[:response_body] || args[:code])
57
- debug "Confirming #{message} with: #{args.inspect}"
58
- message[:message][:connection].respond(
59
- args[:code] || :ok, args[:response_body] || 'Thanks!'
60
- )
61
- else
62
- debug "Confirming #{message}. No arguments provided so no response taken."
63
- end
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)
64
79
  end
65
80
 
81
+ # Process requests
66
82
  def process(*process_args)
67
- srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
68
- while(req = con.request)
83
+ srv = Reel::Server::HTTP.supervise(args[:bind], args[:port]) do |con|
84
+ con.each_request do |req|
69
85
  begin
70
- msg = format(
71
- :request => req,
72
- :body => req.body.to_s,
73
- :connection => con,
74
- :query => parse_query_string(req.query_string)
75
- )
86
+ msg = build_message(con, req)
76
87
  callbacks.each do |name|
77
88
  c_name = callback_name(name)
78
89
  debug "Dispatching #{msg} to callback<#{name} (#{c_name})>"
79
- callback_supervisor[c_name].async.call(msg)
90
+ callback_supervisor[c_name].call(msg)
80
91
  end
81
- con.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
92
+ req.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
82
93
  rescue => e
83
- con.respond(:bad_request, 'Failed to process request')
94
+ req.respond(:bad_request, "Failed to process request -> #{e}")
84
95
  end
85
96
  end
86
97
  end
87
98
  end
88
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 => req.headers,
115
+ :connection => con,
116
+ :query => parse_query_string(req.query_string)
117
+ )
118
+ if(req.headers['Content-Type'] == 'application/json')
119
+ msg[:query].merge(
120
+ parse_query_string(
121
+ req.body.to_s
122
+ )
123
+ )
124
+ msg[:body] = req.body.to_s
125
+ elsif(req.headers['Content-Length'].to_i > BODY_TO_FILE_SIZE)
126
+ msg[:body] = Tempfile.new('carnivore-http')
127
+ while((chunk = req.body.readpartial(2048)))
128
+ msg[:body] << chunk
129
+ end
130
+ msg[:body].rewind
131
+ else
132
+ msg[:body] = req.body.to_s
133
+ end
134
+ format(msg)
135
+ end
136
+
89
137
  end
90
138
 
91
139
  end
@@ -3,10 +3,17 @@ require 'carnivore-http/point_builder'
3
3
 
4
4
  module Carnivore
5
5
  class Source
6
+
7
+ # Carnivore HTTP end points source
6
8
  class HttpEndpoints < Http
7
9
 
8
10
  class << self
9
11
 
12
+ # Register endpoint
13
+ #
14
+ # @param args [Hash]
15
+ # @option args [String] :name
16
+ # @option args [String] :base_path
10
17
  def register(args={})
11
18
  args = Hash[*(
12
19
  args.map do |k,v|
@@ -21,10 +28,15 @@ module Carnivore
21
28
  self
22
29
  end
23
30
 
31
+ # @return [Hash] point builders registered
24
32
  def builders
25
33
  @point_builders ||= {}
26
34
  end
27
35
 
36
+ # Load the named builder
37
+ #
38
+ # @param name [String] name of builder
39
+ # @return [self]
28
40
  def load_builder(name)
29
41
  if(builders[name.to_sym])
30
42
  require File.join(builders[name.to_sym], name)
@@ -34,6 +46,9 @@ module Carnivore
34
46
  self
35
47
  end
36
48
 
49
+ # Setup the builders
50
+ #
51
+ # @return [TrueClass]
37
52
  def setup!
38
53
  only = Carnivore::Config.get(:http_endpoints, :only)
39
54
  except = Carnivore::Config.get(:http_endpoints, :except)
@@ -48,40 +63,45 @@ module Carnivore
48
63
 
49
64
  end
50
65
 
66
+ # @return [Hash] point builders
51
67
  attr_reader :points
52
68
 
69
+ # Setup the registered endpoints
70
+ #
71
+ # @param args [Hash]
72
+ # @option args [String, Symbol] :config_key
53
73
  def setup(args={})
54
74
  super
55
75
  @conf_key = (args[:config_key] || :http_endpoints).to_sym
56
76
  set_points
57
77
  end
58
78
 
79
+ # Start processing
59
80
  def connect
60
81
  async.process
61
82
  end
62
83
 
84
+ # Process requests
63
85
  def process(*process_args)
64
- srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
86
+ srv = Reel::Server::HTTP.supervise(args[:bind], args[:port]) do |con|
65
87
  con.each_request do |req|
66
88
  begin
67
- msg = format(
68
- :request => req,
69
- :body => req.body.to_s,
70
- :connection => con,
71
- :query => parse_query_string(req.query_string).merge(parse_query_string(req.body.to_s))
72
- )
89
+ msg = build_message(con, req)
73
90
  unless(@points.deliver(msg))
74
- con.respond(:ok, 'So long, and thanks for all the fish!')
91
+ req.respond(:ok, 'So long, and thanks for all the fish!')
75
92
  end
76
93
  rescue => e
77
94
  error "Failed to process message: #{e.class} - #{e}"
78
95
  debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
79
- con.respond(:bad_request, 'Failed to process request')
96
+ req.respond(:bad_request, 'Failed to process request')
80
97
  end
81
98
  end
82
99
  end
83
100
  end
84
101
 
102
+ # Build the endpoints and set
103
+ #
104
+ # @return [self]
85
105
  def set_points
86
106
  @points = PointBuilder.new(
87
107
  :only => Carnivore::Config.get(@conf_key, :only),
@@ -4,161 +4,274 @@ require 'carnivore/utils'
4
4
  require 'celluloid'
5
5
 
6
6
  module Carnivore
7
- class PointBuilder
7
+ module Http
8
8
 
9
- class Endpoint
9
+ # End point builder
10
+ class PointBuilder
10
11
 
11
- include Celluloid
12
- include Carnivore::Utils::Params
13
- include Carnivore::Utils::Logging
12
+ # End point
13
+ class Endpoint
14
14
 
15
- attr_reader :endpoint, :type
15
+ include Celluloid
16
+ include Carnivore::Utils::Params
17
+ include Carnivore::Utils::Logging
16
18
 
17
- def initialize(type, endpoint, block)
18
- @endpoint = endpoint
19
- @type = type
20
- define_singleton_method(:execute, &block)
21
- end
19
+ # @return [String, Regexp] request path matcher
20
+ attr_reader :endpoint
21
+ # @return [Symbol] request type (:get, :put, etc.)
22
+ attr_reader :type
22
23
 
23
- def to_s
24
- "<Endpoint[#{endpoint}]>"
25
- end
24
+ # Create new endoint
25
+ #
26
+ # @param type [Symbol] request type (:get, :put, etc.)
27
+ # @param endpoint [String, Regexp] request path matcher
28
+ # @param block [Proc] action to run on match
29
+ def initialize(type, endpoint, block)
30
+ @endpoint = endpoint
31
+ @type = type
32
+ define_singleton_method(:wrapped_execute, &block)
33
+ end
26
34
 
27
- def inspect
28
- "<Endpoint[#{endpoint}] type=#{type} objectid=#{self.object_id}>"
29
- end
35
+ # Execute action on match
36
+ #
37
+ # @param args [Object] argument list
38
+ def execute(*args)
39
+ begin
40
+ wrapped_execute(*args)
41
+ rescue => e
42
+ error "Unexpected error encountered! #{e.class}: #{e}"
43
+ debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
44
+ abort e
45
+ end
46
+ end
30
47
 
31
- end
48
+ # @return [String] stringify
49
+ def to_s
50
+ "<Endpoint[#{endpoint}]>"
51
+ end
32
52
 
33
- include Carnivore::Utils::Params
34
- include Celluloid::Logger
35
- include Blockenspiel::DSL
53
+ # @return [String] instance inspection
54
+ def inspect
55
+ "<Endpoint[#{endpoint}] type=#{type} objectid=#{self.object_id}>"
56
+ end
36
57
 
37
- attr_reader :static, :regex, :only, :except, :endpoint_supervisor
58
+ end
38
59
 
39
- def initialize(args)
40
- @only = args[:only]
41
- @except = args[:except]
42
- @static = {}
43
- @regex = {}
44
- @callback_names = {}
45
- @endpoint_supervisor = Carnivore::Supervisor.create!.last
46
- load_endpoints!
47
- end
60
+ include Carnivore::Utils::Params
61
+ include Celluloid::Logger
62
+ include Blockenspiel::DSL
48
63
 
49
- [:get, :put, :post, :delete, :head, :options, :trace].each do |name|
50
- define_method(name) do |regexp_or_string, args={}, &block|
51
- endpoint(name, regexp_or_string, args, &block)
64
+ # @return [Hash] static path endpoints
65
+ attr_reader :static
66
+ # @return [Hash] regex path endpoints
67
+ attr_reader :regex
68
+ # @return [Array] only enable endpoints
69
+ attr_reader :only
70
+ # @return [Array] do not enable endpoints
71
+ attr_reader :except
72
+ # @return [Carnivore::Supervisor] supervisor
73
+ attr_reader :endpoint_supervisor
74
+
75
+ # Create new instance
76
+ #
77
+ # @param args [Hash]
78
+ # @option args [Array] :only
79
+ # @option args [Array] :except
80
+ def initialize(args={})
81
+ @only = args[:only]
82
+ @except = args[:except]
83
+ @static = {}
84
+ @regex = {}
85
+ @callback_names = {}
86
+ @endpoint_supervisor = Carnivore::Supervisor.create!.last
87
+ load_endpoints!
52
88
  end
53
- end
54
89
 
55
- dsl_methods false
90
+ # Request methods
91
+ # @todo add yardoc method generation
92
+ [:get, :put, :post, :delete, :head, :options, :trace].each do |name|
93
+ define_method(name) do |regexp_or_string, args={}, &block|
94
+ endpoint(name, regexp_or_string, args, &block)
95
+ end
96
+ end
56
97
 
57
- def deliver(msg)
58
- type = msg[:message][:request].method.to_s.downcase.to_sym
59
- path = msg[:message][:request].url
60
- static_points(msg, type, path) || regex_points(msg, type, path)
61
- end
98
+ dsl_methods false
62
99
 
63
- def static_points(msg, type, path)
64
- if(static[type])
65
- match = static[type].keys.detect do |point|
66
- !path.scan(/^#{Regexp.escape(point)}\/?(\?|$)/).empty?
67
- end
68
- if(match)
69
- if(static[type][match][:async])
70
- endpoint_supervisor[callback_name(match, type)].async.execute(msg)
71
- true
72
- else
73
- endpoint_supervisor[callback_name(match, type)].execute(msg)
74
- true
75
- end
76
- end
100
+ # Deliver message to end points
101
+ #
102
+ # @param msg [Carnivore::Message]
103
+ # @return [Truthy, Falsey]
104
+ def deliver(msg)
105
+ type = msg[:message][:request].method.to_s.downcase.to_sym
106
+ path = msg[:message][:request].url
107
+ static_points(msg, type, path) || regex_points(msg, type, path)
77
108
  end
78
- end
79
109
 
80
- def regex_points(msg, type, path)
81
- if(regex[type])
82
- match = regex[type].keys.map do |point|
83
- unless((res = path.scan(/^(#{point})(\?|$)/)).empty?)
84
- res = res.first
85
- res.pop # remove empty EOS match
86
- [point, res]
110
+ # Apply message to static endpoints and execute if matching
111
+ #
112
+ # @param msg [Carnivore::Message]
113
+ # @param type [Symbol] request type
114
+ # @param path [String] request path
115
+ # @param [Truthy, Falsey] match was detected and executed
116
+ def static_points(msg, type, path)
117
+ if(static[type])
118
+ match = static[type].keys.detect do |point|
119
+ !path.scan(/^#{Regexp.escape(point)}\/?(\?|$)/).empty?
87
120
  end
88
- end.compact.first
89
- if(match && !match.empty?)
90
- if(regex[type][match.first][:async])
91
- endpoint_supervisor[callback_name(match.first, type)].async.execute(*([msg] + match.last))
92
- true
93
- else
94
- endpoint_supervisor[callback_name(match.first, type)].execute(*([msg] + match.last))
95
- true
121
+ if(match)
122
+ if(static[type][match][:async])
123
+ endpoint_supervisor[callback_name(match, type)].async.execute(msg)
124
+ true
125
+ else
126
+ endpoint_supervisor[callback_name(match, type)].execute(msg)
127
+ true
128
+ end
96
129
  end
97
130
  end
98
131
  end
99
- end
100
132
 
101
- def callback_name(point, type)
102
- key = "#{point}_#{type}"
103
- unless(@callback_names[key])
104
- @callback_names[key] = Digest::SHA256.hexdigest(key)
133
+ # Apply message to regex endpoints and execute if matching
134
+ #
135
+ # @param msg [Carnivore::Message]
136
+ # @param type [Symbol] request type
137
+ # @param path [String] request path
138
+ # @param [Truthy, Falsey] match was detected and executed
139
+ def regex_points(msg, type, path)
140
+ if(regex[type])
141
+ match = regex[type].keys.map do |point|
142
+ unless((res = path.scan(/^(#{point})(\?|$)/)).empty?)
143
+ res = res.first
144
+ res.pop # remove empty EOS match
145
+ [point, res]
146
+ end
147
+ end.compact.first
148
+ if(match && !match.empty?)
149
+ if(regex[type][match.first][:async])
150
+ endpoint_supervisor[callback_name(match.first, type)].async.execute(*([msg] + match.last))
151
+ true
152
+ else
153
+ endpoint_supervisor[callback_name(match.first, type)].execute(*([msg] + match.last))
154
+ true
155
+ end
156
+ end
157
+ end
105
158
  end
106
- @callback_names[key]
107
- end
108
159
 
109
- def endpoint(request_type, regexp_or_string, args, &block)
110
- request_type = request_type.to_sym
111
- if(regexp_or_string.is_a?(Regexp))
112
- regex[request_type] ||= {}
113
- regex[request_type][regexp_or_string] = args
114
- else
115
- static[request_type] ||= {}
116
- static[request_type][regexp_or_string.sub(%r{/$}, '')] = args
160
+ # Generate internal callback name reference
161
+ #
162
+ # @param point [String, Symbol]
163
+ # @param type [String, Symbol]
164
+ # @return [String]
165
+ def callback_name(point, type)
166
+ key = "#{point}_#{type}"
167
+ unless(@callback_names[key])
168
+ @callback_names[key] = Digest::SHA256.hexdigest(key)
169
+ end
170
+ @callback_names[key]
117
171
  end
118
- if(args[:workers] && args[:workers].to_i > 1)
119
- endpoint_supervisor.pool(Endpoint,
120
- as: callback_name(regexp_or_string, request_type), size: args[:workers].to_i,
121
- args: [request_type, regexp_or_string, block]
122
- )
123
- else
124
- endpoint_supervisor.supervise_as(
125
- callback_name(regexp_or_string, request_type), Endpoint, request_type, regexp_or_string, block
126
- )
172
+
173
+ # Build new endpoint and supervise
174
+ #
175
+ # @param request_type [Symbol, String] request type (:get, :put, etc.)
176
+ # @param regexp_or_string [Regexp, String]
177
+ # @param args [Hash]
178
+ # @option args [Numeric] :workers number of workers to initialize
179
+ # @yield action to execute on match
180
+ def endpoint(request_type, regexp_or_string, args, &block)
181
+ request_type = request_type.to_sym
182
+ if(regexp_or_string.is_a?(Regexp))
183
+ regex[request_type] ||= {}
184
+ regex[request_type][regexp_or_string] = args
185
+ else
186
+ static[request_type] ||= {}
187
+ static[request_type][regexp_or_string.sub(%r{/$}, '')] = args
188
+ end
189
+ if(args[:workers] && args[:workers].to_i > 1)
190
+ endpoint_supervisor.pool(Endpoint,
191
+ as: callback_name(regexp_or_string, request_type), size: args[:workers].to_i,
192
+ args: [request_type, regexp_or_string, block]
193
+ )
194
+ else
195
+ endpoint_supervisor.supervise_as(
196
+ callback_name(regexp_or_string, request_type), Endpoint, request_type, regexp_or_string, block
197
+ )
198
+ end
199
+ true
127
200
  end
128
- true
129
- end
130
201
 
131
- def endpoints
132
- [static, regex]
133
- end
202
+ # @return [Array] all endpoints
203
+ def endpoints
204
+ [static, regex]
205
+ end
134
206
 
135
- private
207
+ private
136
208
 
137
- def load_endpoints!
138
- self.class.storage.each do |name, block|
139
- next if only && !only.include?(name.to_s)
140
- next if except && except.include?(name.to_s)
141
- Blockenspiel.invoke(block, self)
209
+ # Load all available endpoints
210
+ #
211
+ # @note will register all discoverable subclasses
212
+ def load_endpoints!
213
+ self.class.compress_offspring.each do |name, block|
214
+ next if only && !only.include?(name.to_s)
215
+ next if except && except.include?(name.to_s)
216
+ Blockenspiel.invoke(block, self)
217
+ end
218
+ true
142
219
  end
143
- true
144
- end
145
220
 
146
- class << self
147
- def define(&block)
148
- name = File.basename(
149
- caller.first.match(%r{.*?:}).to_s.sub(':', '')
150
- ).sub('.rb', '')
151
- store(name, block)
152
- end
221
+ class << self
153
222
 
154
- def store(name, block)
155
- storage[name.to_sym] = block
156
- self
157
- end
223
+ # Store subclass reference in descendants and
224
+ # setup properly for DSL usage
225
+ #
226
+ # @param klass [Class]
227
+ def inherited(klass)
228
+ descendants.push(klass)
229
+ klass.send(:include, Blockenspiel::DSL)
230
+ end
231
+
232
+ # Define new API block
233
+ #
234
+ # @yield new API block
235
+ def define(&block)
236
+ store(Celluloid.uuid, block)
237
+ end
238
+
239
+ # Store block
240
+ #
241
+ # @param name [String, Symbol]
242
+ # @param block [Proc]
243
+ def store(name, block)
244
+ storage[name.to_sym] = block
245
+ self
246
+ end
247
+
248
+ # @return [Hash] storage for defined blocks
249
+ def storage
250
+ @storage ||= {}
251
+ end
252
+
253
+ # @return [Array<Class>] descendant classes
254
+ def descendants
255
+ @descendants ||= []
256
+ end
257
+
258
+ # @return [Array<Hash>] all descendant storages (full tree)
259
+ def offspring_storage
260
+ descendants.map(&:offspring_storage).flatten.unshift(storage)
261
+ end
262
+
263
+ # @return [Hash] merged storages (full tree)
264
+ def compress_offspring
265
+ stores = offspring_storage
266
+ stores.inject(stores.shift) do |memo, store|
267
+ memo.merge(store)
268
+ end
269
+ end
158
270
 
159
- def storage
160
- @storage ||= {}
161
271
  end
162
272
  end
163
273
  end
164
274
  end
275
+
276
+ # Alias for previous release compatibility
277
+ Carnivore::PointBuilder = Carnivore::Http::PointBuilder
@@ -1,18 +1,27 @@
1
1
  module Carnivore
2
2
  module Http
3
+
4
+ # Helper utilities
3
5
  module Utils
6
+
7
+ # URL parameter helpers
4
8
  module Params
5
9
 
6
10
  class << self
7
11
 
12
+ # Load cgi library on demand
13
+ #
14
+ # @param klass [Class]
8
15
  def included(klass)
9
16
  require 'cgi'
10
17
  end
11
18
 
12
19
  end
13
20
 
14
- # string:: HTTP query string
15
- # Return Hash of parsed query string
21
+ # Generate hash of parsed query String
22
+ #
23
+ # @param string [String] HTTP query string
24
+ # @return [Hash]
16
25
  def parse_query_string(string)
17
26
  unless(string.to_s.empty?)
18
27
  args = CGI.parse(string)
@@ -22,8 +31,10 @@ module Carnivore
22
31
  end
23
32
  end
24
33
 
25
- # args:: HTTP query string Hash
26
- # Return formatted hash with inferred types
34
+ # Cast hash values when possible
35
+ #
36
+ # @param args [Hash]
37
+ # @return [Hash]
27
38
  def format_query_args(args)
28
39
  new_args = {}
29
40
  args.each do |k, v|
@@ -41,8 +52,10 @@ module Carnivore
41
52
  new_args
42
53
  end
43
54
 
44
- # obj:: object
45
- # Attempts to return true type
55
+ # Best attempt to convert to true type
56
+ #
57
+ # @param obj [Object] generally string value
58
+ # @return [Object] result of best attempt
46
59
  def format_query_type(obj)
47
60
  case obj
48
61
  when 'true'
@@ -1,7 +1,9 @@
1
1
  module Carnivore
2
2
  module Http
3
+ # Custom version class
3
4
  class Version < Gem::Version
4
5
  end
5
- VERSION = Version.new('0.1.4')
6
+ # Current library version
7
+ VERSION = Version.new('0.1.6')
6
8
  end
7
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carnivore-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-10 00:00:00.000000000 Z
12
+ date: 2014-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: carnivore
@@ -32,17 +32,17 @@ dependencies:
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  none: false
34
34
  requirements:
35
- - - ! '>='
35
+ - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: '0'
37
+ version: 0.5.0
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
- - - ! '>='
43
+ - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: '0'
45
+ version: 0.5.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: blockenspiel
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -75,11 +75,14 @@ files:
75
75
  - test/specs/http.rb
76
76
  - Gemfile
77
77
  - README.md
78
+ - LICENSE
78
79
  - carnivore-http.gemspec
79
80
  - CHANGELOG.md
81
+ - CONTRIBUTING.md
80
82
  - Gemfile.lock
81
83
  homepage: https://github.com/carnivore-rb/carnivore-http
82
- licenses: []
84
+ licenses:
85
+ - Apache 2.0
83
86
  post_install_message:
84
87
  rdoc_options: []
85
88
  require_paths: