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 +5 -0
- data/CONTRIBUTING.md +25 -0
- data/Gemfile.lock +19 -23
- data/LICENSE +13 -0
- data/carnivore-http.gemspec +2 -1
- data/lib/carnivore-http.rb +18 -0
- data/lib/carnivore-http/http.rb +73 -25
- data/lib/carnivore-http/http_endpoints.rb +29 -9
- data/lib/carnivore-http/point_builder.rb +234 -121
- data/lib/carnivore-http/utils.rb +19 -6
- data/lib/carnivore-http/version.rb +3 -1
- metadata +10 -7
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.
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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.
|
36
|
-
http_parser.rb (>= 0.6.0
|
37
|
-
websocket_parser (>= 0.1.
|
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.
|
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.
|
data/carnivore-http.gemspec
CHANGED
@@ -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
|
data/lib/carnivore-http.rb
CHANGED
@@ -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')
|
data/lib/carnivore-http/http.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
34
|
+
).merge(args)
|
24
35
|
end
|
25
36
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
83
|
+
srv = Reel::Server::HTTP.supervise(args[:bind], args[:port]) do |con|
|
84
|
+
con.each_request do |req|
|
69
85
|
begin
|
70
|
-
msg =
|
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].
|
90
|
+
callback_supervisor[c_name].call(msg)
|
80
91
|
end
|
81
|
-
|
92
|
+
req.respond(:ok, 'So long, and thanks for all the fish!') if args[:auto_respond]
|
82
93
|
rescue => e
|
83
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
7
|
+
module Http
|
8
8
|
|
9
|
-
|
9
|
+
# End point builder
|
10
|
+
class PointBuilder
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
include Carnivore::Utils::Logging
|
12
|
+
# End point
|
13
|
+
class Endpoint
|
14
14
|
|
15
|
-
|
15
|
+
include Celluloid
|
16
|
+
include Carnivore::Utils::Params
|
17
|
+
include Carnivore::Utils::Logging
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
@type
|
20
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
48
|
+
# @return [String] stringify
|
49
|
+
def to_s
|
50
|
+
"<Endpoint[#{endpoint}]>"
|
51
|
+
end
|
32
52
|
|
33
|
-
|
34
|
-
|
35
|
-
|
53
|
+
# @return [String] instance inspection
|
54
|
+
def inspect
|
55
|
+
"<Endpoint[#{endpoint}] type=#{type} objectid=#{self.object_id}>"
|
56
|
+
end
|
36
57
|
|
37
|
-
|
58
|
+
end
|
38
59
|
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
202
|
+
# @return [Array] all endpoints
|
203
|
+
def endpoints
|
204
|
+
[static, regex]
|
205
|
+
end
|
134
206
|
|
135
|
-
|
207
|
+
private
|
136
208
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
data/lib/carnivore-http/utils.rb
CHANGED
@@ -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
|
-
#
|
15
|
-
#
|
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
|
-
#
|
26
|
-
#
|
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
|
-
#
|
45
|
-
#
|
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'
|
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
|
+
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-
|
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:
|
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:
|
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:
|