carnivore-http 0.1.4 → 0.1.6

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 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: