ocular 0.1.3 → 0.1.4
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.
- checksums.yaml +4 -4
- data/bin/ocular +4 -4
- data/lib/ocular/dsl/fog.rb +13 -0
- data/lib/ocular/dsl/runcontext.rb +12 -0
- data/lib/ocular/event/eventbase.rb +51 -8
- data/lib/ocular/event/eventfactory.rb +10 -5
- data/lib/ocular/inputs/http_input.rb +441 -23
- data/lib/ocular/settings.rb +1 -2
- data/lib/ocular/version.rb +1 -1
- metadata +19 -6
- data/lib/ocular/event/forker.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80d3eab4870432f28e3a67ed391012aa4cbd0d3d
|
4
|
+
data.tar.gz: d899c2f9ec2020c3d18bf82dc61543bbb8e1163c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b51e2835fb8aa855eeb85a342782d6e0d7c2e7a5b8293f0fe4aaae6c135727a82952a8b4fd85296eec3f5f3802175d6db511274097f6eaebc1a063e9d82282a1
|
7
|
+
data.tar.gz: c8a79305a5259ea4d6f782a81c91ea41c918910603105d885db97cc2b39d14f1a74a326d2a8584b1e26254e0fdb58475cdf7088535b6aef10b5a71d95efd22ae
|
data/bin/ocular
CHANGED
@@ -21,7 +21,7 @@ class OptparseExample
|
|
21
21
|
options.transfer_type = :auto
|
22
22
|
options.verbose = false
|
23
23
|
options.server = false
|
24
|
-
options.settings =
|
24
|
+
options.settings = nil
|
25
25
|
options.root = nil
|
26
26
|
|
27
27
|
opt_parser = OptionParser.new do |opts|
|
@@ -55,7 +55,7 @@ class OptparseExample
|
|
55
55
|
options.root = path
|
56
56
|
end
|
57
57
|
|
58
|
-
opts.on("--settings FILE", "Full path and file name to the settings yaml file.
|
58
|
+
opts.on("--settings FILE", "Full path and file name to the settings yaml file.") do |path|
|
59
59
|
options.settings = path
|
60
60
|
end
|
61
61
|
|
@@ -69,7 +69,7 @@ end # class OptparseExample
|
|
69
69
|
|
70
70
|
options = OptparseExample.parse(ARGV)
|
71
71
|
|
72
|
-
Ocular::Settings.load_from_file(
|
72
|
+
Ocular::Settings.load_from_file(Ocular::Settings.find_settings_file_from_system(options.settings))
|
73
73
|
|
74
74
|
if options.server
|
75
75
|
if !options.root && !Ocular::Settings.get(:script_root)
|
@@ -96,7 +96,7 @@ else
|
|
96
96
|
proxy = ef.load_from_file(ARGV.shift)
|
97
97
|
|
98
98
|
context = Ocular::DSL::RunContext.new
|
99
|
-
eventbase = proxy.events[
|
99
|
+
eventbase = proxy.events["onEvent"][nil]
|
100
100
|
eventbase.exec(context)
|
101
101
|
|
102
102
|
end
|
data/lib/ocular/dsl/fog.rb
CHANGED
@@ -27,6 +27,19 @@ class Ocular
|
|
27
27
|
})
|
28
28
|
end
|
29
29
|
|
30
|
+
def find_servers_in_autoscaling_groups(substring)
|
31
|
+
instances = []
|
32
|
+
for group in autoscaling.groups
|
33
|
+
if group.id.include?(substring)
|
34
|
+
for i in group.instances
|
35
|
+
instances << aws.servers.get(i.id)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return instances
|
41
|
+
end
|
42
|
+
|
30
43
|
end
|
31
44
|
|
32
45
|
end
|
@@ -7,6 +7,7 @@ class Ocular
|
|
7
7
|
attr_accessor :run_id
|
8
8
|
attr_accessor :proxy
|
9
9
|
attr_accessor :class_name
|
10
|
+
attr_accessor :event_signature
|
10
11
|
|
11
12
|
include Ocular::DSL::Logging
|
12
13
|
include Ocular::DSL::SSH
|
@@ -14,6 +15,7 @@ class Ocular
|
|
14
15
|
def initialize
|
15
16
|
@run_id = SecureRandom.uuid()
|
16
17
|
@logger = Ocular::DSL::Logger.new
|
18
|
+
@cleanups = []
|
17
19
|
end
|
18
20
|
|
19
21
|
def method_missing(method_sym, *arguments, &block)
|
@@ -23,6 +25,16 @@ class Ocular
|
|
23
25
|
raise NoMethodError("undefined method `#{method_sym}` in event #{self.class_name}")
|
24
26
|
end
|
25
27
|
end
|
28
|
+
|
29
|
+
def register_cleanup(&block)
|
30
|
+
@cleanups << block
|
31
|
+
end
|
32
|
+
|
33
|
+
def cleanup()
|
34
|
+
for i in @cleanups
|
35
|
+
i.call()
|
36
|
+
end
|
37
|
+
end
|
26
38
|
end
|
27
39
|
end
|
28
40
|
end
|
@@ -7,20 +7,63 @@ class Ocular
|
|
7
7
|
module DSL
|
8
8
|
|
9
9
|
class EventBase
|
10
|
-
include Ocular::DSL::SSH
|
11
|
-
include Ocular::DSL::Fog
|
12
|
-
include Ocular::DSL::Logging
|
13
10
|
|
14
|
-
|
11
|
+
class Results
|
12
|
+
attr_accessor :response
|
13
|
+
attr_accessor :error
|
14
|
+
end
|
15
15
|
|
16
|
+
attr_accessor :proxy
|
16
17
|
|
17
18
|
def initialize(&block)
|
18
|
-
|
19
|
+
@callback = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def exec(context, do_fork = self.proxy.do_fork)
|
23
|
+
context.proxy = self.proxy
|
24
|
+
if do_fork
|
25
|
+
return exec_fork(context)
|
26
|
+
else
|
27
|
+
return exec_nofork(context)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def exec_fork(context)
|
32
|
+
reader, writer = IO::pipe
|
33
|
+
child_pid = fork do
|
34
|
+
reader.close
|
35
|
+
r = Results.new
|
36
|
+
|
37
|
+
begin
|
38
|
+
retval = context.instance_eval(&@callback)
|
39
|
+
|
40
|
+
# This check is to make sure that whatever we return that it can be serialised
|
41
|
+
if String === retval or Array === retval
|
42
|
+
r.response = retval
|
43
|
+
end
|
44
|
+
|
45
|
+
rescue Exception => error
|
46
|
+
r.error = error
|
47
|
+
end
|
48
|
+
|
49
|
+
response_data = Marshal.dump(r)
|
50
|
+
writer.puts(response_data)
|
51
|
+
writer.close
|
52
|
+
end
|
53
|
+
writer.close
|
54
|
+
|
55
|
+
Process.wait(child_pid)
|
56
|
+
r = Marshal.load(reader.read)
|
57
|
+
reader.close
|
58
|
+
|
59
|
+
if r.error
|
60
|
+
raise r.error
|
61
|
+
end
|
62
|
+
return r.response
|
19
63
|
end
|
20
64
|
|
21
|
-
def
|
22
|
-
|
23
|
-
run_context.instance_eval(&@callback)
|
65
|
+
def exec_nofork(context)
|
66
|
+
return context.instance_eval(&@callback)
|
24
67
|
end
|
25
68
|
|
26
69
|
end
|
@@ -6,14 +6,15 @@ class Ocular
|
|
6
6
|
module Event
|
7
7
|
class DefinitionProxy
|
8
8
|
attr_accessor :events
|
9
|
-
attr_reader :script_name
|
10
|
-
attr_accessor :handlers
|
9
|
+
attr_reader :script_name, :do_fork
|
10
|
+
attr_accessor :handlers, :events
|
11
11
|
|
12
12
|
def initialize(script_name, handlers)
|
13
13
|
@script_name = script_name
|
14
|
-
@events =
|
14
|
+
@events = {}
|
15
15
|
@logger = Ocular::DSL::Logger.new
|
16
16
|
@handlers = handlers
|
17
|
+
@do_fork = true
|
17
18
|
end
|
18
19
|
|
19
20
|
include Ocular::Mixin::FromFile
|
@@ -22,10 +23,14 @@ class Ocular
|
|
22
23
|
include Ocular::DSL::Fog
|
23
24
|
include Ocular::Inputs::HTTP::DSL
|
24
25
|
|
25
|
-
def
|
26
|
+
def fork(value)
|
27
|
+
@do_fork = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def onEvent(type, &block)
|
26
31
|
eventbase = Ocular::DSL::EventBase.new(&block)
|
27
32
|
eventbase.proxy = self
|
28
|
-
@events
|
33
|
+
(@events["onEvent"] ||= {})[type] = eventbase
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
@@ -1,8 +1,14 @@
|
|
1
|
-
require 'sinatra/base'
|
2
1
|
require 'puma'
|
3
2
|
require 'rack'
|
4
3
|
require 'rack/server'
|
4
|
+
require 'rack/protection'
|
5
|
+
require 'uri'
|
6
|
+
|
5
7
|
require 'ocular/inputs/base.rb'
|
8
|
+
require 'ocular/dsl/runcontext.rb'
|
9
|
+
|
10
|
+
# Some of this code is copied from the excellent Sinatra Ruby web library by
|
11
|
+
# Blake Mizerany and Konstantin Haase.
|
6
12
|
|
7
13
|
class Ocular
|
8
14
|
module Inputs
|
@@ -11,19 +17,34 @@ class Ocular
|
|
11
17
|
|
12
18
|
module DSL
|
13
19
|
|
14
|
-
|
20
|
+
|
21
|
+
def onGET(path, opts = {}, &block)
|
15
22
|
handler = handlers.get(::Ocular::Inputs::HTTP::Input)
|
16
|
-
handler.add_get(script_name, path, opts, &block)
|
23
|
+
handler.add_get(script_name, path, opts, self, &block)
|
17
24
|
end
|
18
25
|
|
19
26
|
def onPOST(path, opts = {}, &block)
|
20
27
|
handler = handlers.get(::Ocular::Inputs::HTTP::Input)
|
21
|
-
handler.add_post(script_name, path, opts, &block)
|
28
|
+
handler.add_post(script_name, path, opts, self, &block)
|
22
29
|
end
|
23
30
|
|
31
|
+
def onDELETE(path, opts = {}, &block)
|
32
|
+
handler = handlers.get(::Ocular::Inputs::HTTP::Input)
|
33
|
+
handler.add_delete(script_name, path, opts, self, &block)
|
34
|
+
end
|
24
35
|
end
|
25
36
|
|
26
37
|
class Input < ::Ocular::Inputs::Base
|
38
|
+
|
39
|
+
attr_reader :routes
|
40
|
+
|
41
|
+
class WebRunContext < ::Ocular::DSL::RunContext
|
42
|
+
attr_accessor :request, :response, :params, :env
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
URI_INSTANCE = URI::Parser.new
|
47
|
+
|
27
48
|
DEFAULT_SETTINGS = {
|
28
49
|
:host => '0.0.0.0',
|
29
50
|
:port => 8080,
|
@@ -31,19 +52,167 @@ class Ocular
|
|
31
52
|
:silent => false
|
32
53
|
}
|
33
54
|
|
34
|
-
class
|
35
|
-
|
36
|
-
|
55
|
+
class Request < Rack::Request
|
56
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
|
57
|
+
HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
|
58
|
+
|
59
|
+
# Returns an array of acceptable media types for the response
|
60
|
+
def accept
|
61
|
+
@env['sinatra.accept'] ||= begin
|
62
|
+
if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != ''
|
63
|
+
@env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS).
|
64
|
+
map! { |e| AcceptEntry.new(e) }.sort
|
65
|
+
else
|
66
|
+
[AcceptEntry.new('*/*')]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def accept?(type)
|
72
|
+
preferred_type(type).to_s.include?(type)
|
73
|
+
end
|
74
|
+
|
75
|
+
def preferred_type(*types)
|
76
|
+
accepts = accept # just evaluate once
|
77
|
+
return accepts.first if types.empty?
|
78
|
+
types.flatten!
|
79
|
+
return types.first if accepts.empty?
|
80
|
+
accepts.detect do |pattern|
|
81
|
+
type = types.detect { |t| File.fnmatch(pattern, t) }
|
82
|
+
return type if type
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
alias secure? ssl?
|
87
|
+
|
88
|
+
def forwarded?
|
89
|
+
@env.include? "HTTP_X_FORWARDED_HOST"
|
90
|
+
end
|
91
|
+
|
92
|
+
def safe?
|
93
|
+
get? or head? or options? or trace?
|
94
|
+
end
|
95
|
+
|
96
|
+
def idempotent?
|
97
|
+
safe? or put? or delete? or link? or unlink?
|
98
|
+
end
|
99
|
+
|
100
|
+
def link?
|
101
|
+
request_method == "LINK"
|
102
|
+
end
|
103
|
+
|
104
|
+
def unlink?
|
105
|
+
request_method == "UNLINK"
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
class AcceptEntry
|
111
|
+
attr_accessor :params
|
112
|
+
attr_reader :entry
|
113
|
+
|
114
|
+
def initialize(entry)
|
115
|
+
params = entry.scan(HEADER_PARAM).map! do |s|
|
116
|
+
key, value = s.strip.split('=', 2)
|
117
|
+
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
|
118
|
+
[key, value]
|
119
|
+
end
|
120
|
+
|
121
|
+
@entry = entry
|
122
|
+
@type = entry[/[^;]+/].delete(' ')
|
123
|
+
@params = Hash[params]
|
124
|
+
@q = @params.delete('q') { 1.0 }.to_f
|
125
|
+
end
|
126
|
+
|
127
|
+
def <=>(other)
|
128
|
+
other.priority <=> self.priority
|
129
|
+
end
|
130
|
+
|
131
|
+
def priority
|
132
|
+
# We sort in descending order; better matches should be higher.
|
133
|
+
[ @q, -@type.count('*'), @params.size ]
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_str
|
137
|
+
@type
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s(full = false)
|
141
|
+
full ? entry : to_str
|
142
|
+
end
|
143
|
+
|
144
|
+
def respond_to?(*args)
|
145
|
+
super or to_str.respond_to?(*args)
|
146
|
+
end
|
147
|
+
|
148
|
+
def method_missing(*args, &block)
|
149
|
+
to_str.send(*args, &block)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class NotFound < NameError #:nodoc:
|
155
|
+
def http_status; 404 end
|
156
|
+
end
|
157
|
+
|
158
|
+
# The response object. See Rack::Response and Rack::Response::Helpers for
|
159
|
+
# more info:
|
160
|
+
# http://rubydoc.info/github/rack/rack/master/Rack/Response
|
161
|
+
# http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
|
162
|
+
class Response < Rack::Response
|
163
|
+
DROP_BODY_RESPONSES = [204, 205, 304]
|
164
|
+
def initialize(*)
|
165
|
+
super
|
166
|
+
headers['Content-Type'] ||= 'text/html'
|
167
|
+
end
|
168
|
+
|
169
|
+
def body=(value)
|
170
|
+
value = value.body while Rack::Response === value
|
171
|
+
@body = String === value ? [value.to_str] : value
|
37
172
|
end
|
38
173
|
|
39
|
-
|
40
|
-
|
41
|
-
|
174
|
+
def each
|
175
|
+
block_given? ? super : enum_for(:each)
|
176
|
+
end
|
177
|
+
|
178
|
+
def finish
|
179
|
+
result = body
|
180
|
+
|
181
|
+
if drop_content_info?
|
182
|
+
headers.delete "Content-Length"
|
183
|
+
headers.delete "Content-Type"
|
184
|
+
end
|
185
|
+
|
186
|
+
if drop_body?
|
187
|
+
close
|
188
|
+
result = []
|
189
|
+
end
|
190
|
+
|
191
|
+
if calculate_content_length?
|
192
|
+
# if some other code has already set Content-Length, don't muck with it
|
193
|
+
# currently, this would be the static file-handler
|
194
|
+
headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
|
195
|
+
end
|
196
|
+
|
197
|
+
[status.to_i, headers, result]
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def calculate_content_length?
|
203
|
+
headers["Content-Type"] and not headers["Content-Length"] and Array === body
|
204
|
+
end
|
205
|
+
|
206
|
+
def drop_content_info?
|
207
|
+
status.to_i / 100 == 1 or drop_body?
|
208
|
+
end
|
209
|
+
|
210
|
+
def drop_body?
|
211
|
+
DROP_BODY_RESPONSES.include?(status.to_i)
|
42
212
|
end
|
43
213
|
end
|
44
214
|
|
45
215
|
def generate_uri_from_names(script_name, path)
|
46
|
-
puts "generate_uri_from_names: #{script_name}, #{path}"
|
47
216
|
if path[0] == "/"
|
48
217
|
path = path[1..-1]
|
49
218
|
end
|
@@ -57,37 +226,286 @@ class Ocular
|
|
57
226
|
return "/" + name
|
58
227
|
end
|
59
228
|
|
60
|
-
def add_get(script_name, path, options
|
229
|
+
def add_get(script_name, path, options, proxy, &block)
|
61
230
|
name = generate_uri_from_names(script_name, path)
|
62
|
-
|
63
|
-
@app_class.get(name, options, &block)
|
231
|
+
route('GET', name, options, proxy, &block)
|
64
232
|
end
|
65
233
|
|
66
|
-
def add_post(script_name, path, options
|
67
|
-
name = generate_uri_from_names(script_name, path)
|
68
|
-
|
234
|
+
def add_post(script_name, path, options, proxy, &block)
|
235
|
+
name = generate_uri_from_names(script_name, path)
|
236
|
+
route('POST', name, options, proxy, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
def add_delete(script_name, path, options, proxy, &block)
|
240
|
+
name = generate_uri_from_names(script_name, path)
|
241
|
+
route('DELETE', name, options, proxy, &block)
|
242
|
+
end
|
243
|
+
|
244
|
+
def build_signature(pattern, keys, &block)
|
245
|
+
return [pattern, keys, block]
|
246
|
+
end
|
247
|
+
|
248
|
+
def route(verb, path, options, proxy, &block)
|
249
|
+
eventbase = Ocular::DSL::EventBase.new(&block)
|
250
|
+
eventbase.proxy = proxy
|
251
|
+
(proxy.events[verb] ||= {})[path] = eventbase
|
252
|
+
|
253
|
+
pattern, keys = compile(path)
|
254
|
+
|
255
|
+
(@routes[verb] ||= []) << build_signature(pattern, keys) do |context|
|
256
|
+
context.event_signature = [verb, path]
|
257
|
+
eventbase.exec(context)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
def safe_ignore(ignore)
|
263
|
+
unsafe_ignore = []
|
264
|
+
ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
|
265
|
+
unsafe_ignore << hex[1..2]
|
266
|
+
''
|
267
|
+
end
|
268
|
+
unsafe_patterns = unsafe_ignore.map! do |unsafe|
|
269
|
+
chars = unsafe.split(//).map! do |char|
|
270
|
+
char == char.downcase ? char : char + char.downcase
|
271
|
+
end
|
272
|
+
|
273
|
+
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
274
|
+
end
|
275
|
+
if unsafe_patterns.length > 0
|
276
|
+
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
277
|
+
else
|
278
|
+
"([^#{ignore}/?#]+)"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
def compile(path)
|
284
|
+
if path.respond_to? :to_str
|
285
|
+
keys = []
|
286
|
+
|
287
|
+
# Split the path into pieces in between forward slashes.
|
288
|
+
# A negative number is given as the second argument of path.split
|
289
|
+
# because with this number, the method does not ignore / at the end
|
290
|
+
# and appends an empty string at the end of the return value.
|
291
|
+
#
|
292
|
+
segments = path.split('/', -1).map! do |segment|
|
293
|
+
ignore = []
|
294
|
+
|
295
|
+
# Special character handling.
|
296
|
+
#
|
297
|
+
pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c|
|
298
|
+
ignore << escaped(c).join if c.match(/[\.@]/)
|
299
|
+
patt = encoded(c)
|
300
|
+
patt.gsub(/%[\da-fA-F]{2}/) do |match|
|
301
|
+
match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
ignore = ignore.uniq.join
|
306
|
+
|
307
|
+
# Key handling.
|
308
|
+
#
|
309
|
+
pattern.gsub(/((:\w+)|\*)/) do |match|
|
310
|
+
if match == "*"
|
311
|
+
keys << 'splat'
|
312
|
+
"(.*?)"
|
313
|
+
else
|
314
|
+
keys << $2[1..-1]
|
315
|
+
ignore_pattern = safe_ignore(ignore)
|
316
|
+
|
317
|
+
ignore_pattern
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Special case handling.
|
323
|
+
#
|
324
|
+
if last_segment = segments[-1] and last_segment.match(/\[\^\\\./)
|
325
|
+
parts = last_segment.rpartition(/\[\^\\\./)
|
326
|
+
parts[1] = '[^'
|
327
|
+
segments[-1] = parts.join
|
328
|
+
end
|
329
|
+
[/\A#{segments.join('/')}\z/, keys]
|
330
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
331
|
+
[path, path.keys]
|
332
|
+
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
333
|
+
[path, path.names]
|
334
|
+
elsif path.respond_to? :match
|
335
|
+
[path, []]
|
336
|
+
else
|
337
|
+
raise TypeError, path
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def call(env)
|
342
|
+
dup.call!(env)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Set or retrieve the response body. When a block is given,
|
346
|
+
# evaluation is deferred until the body is read with #each.
|
347
|
+
def body(context, value = nil, &block)
|
348
|
+
if block_given?
|
349
|
+
def block.each; yield(call) end
|
350
|
+
context.response.body = block
|
351
|
+
elsif value
|
352
|
+
# Rack 2.0 returns a Rack::File::Iterator here instead of
|
353
|
+
# Rack::File as it was in the previous API.
|
354
|
+
unless context.request.head?
|
355
|
+
headers(context).delete 'Content-Length'
|
356
|
+
end
|
357
|
+
context.response.body = value
|
358
|
+
else
|
359
|
+
context.response.body
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def call!(env)
|
364
|
+
context = WebRunContext.new
|
365
|
+
|
366
|
+
context.request = Request.new(env)
|
367
|
+
context.response = Response.new
|
368
|
+
context.env = env
|
369
|
+
context.params = indifferent_params(context.request.params)
|
370
|
+
|
371
|
+
context.response['Content-Type'] = nil
|
372
|
+
invoke(context) { |context| dispatch(context) }
|
373
|
+
|
374
|
+
unless context.response['Content-Type']
|
375
|
+
context.response['Content-Type'] = "text/html"
|
376
|
+
end
|
377
|
+
|
378
|
+
context.response.finish
|
379
|
+
end
|
380
|
+
|
381
|
+
def dispatch(context)
|
382
|
+
invoke(context) do |context|
|
383
|
+
route!(context)
|
384
|
+
end
|
385
|
+
rescue ::Exception => error
|
386
|
+
invoke(context) do |context|
|
387
|
+
handle_exception!(context, error)
|
388
|
+
end
|
389
|
+
ensure
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
def handle_exception!(context, error)
|
394
|
+
context.env['error'] = error
|
395
|
+
|
396
|
+
if error.respond_to? :http_status
|
397
|
+
context.response.status = error.http_status
|
398
|
+
else
|
399
|
+
context.response.status = 500
|
400
|
+
puts "Internal Server Error: #{error}"
|
401
|
+
puts error.backtrace
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
def call_block(context)
|
407
|
+
yield(context)
|
408
|
+
end
|
409
|
+
|
410
|
+
def route!(context)
|
411
|
+
if routes = @routes[context.request.request_method]
|
412
|
+
routes.each do |pattern, keys, block|
|
413
|
+
process_route(context, pattern, keys) do |*args|
|
414
|
+
#env['route'] = block.instance_variable_get(:@route_name)
|
415
|
+
|
416
|
+
#throw :halt, context.exec(&block)
|
417
|
+
throw :halt, call_block(context, &block)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
puts "Route missing"
|
423
|
+
raise NotFound
|
424
|
+
end
|
425
|
+
|
426
|
+
def process_route(context, pattern, keys, values = [])
|
427
|
+
route = context.request.path_info
|
428
|
+
route = '/' if route.empty?
|
429
|
+
return unless match = pattern.match(route)
|
430
|
+
values += match.captures.map! { |v| URI_INSTANCE.unescape(v) if v }
|
431
|
+
|
432
|
+
if values.any?
|
433
|
+
original, @params = context.params, context.params.merge('splat' => [], 'captures' => values)
|
434
|
+
keys.zip(values) { |k,v| Array === context.params[k] ? context.params[k] << v : context.params[k] = v if v }
|
435
|
+
end
|
436
|
+
|
437
|
+
yield(self, values)
|
438
|
+
|
439
|
+
rescue
|
440
|
+
context.env['error.params'] = context.params
|
441
|
+
raise
|
442
|
+
ensure
|
443
|
+
@params = original if original
|
444
|
+
end
|
445
|
+
|
446
|
+
def invoke(context)
|
447
|
+
res = catch(:halt) { yield(context) }
|
448
|
+
|
449
|
+
res = [res] if Fixnum === res or String === res
|
450
|
+
if Array === res and Fixnum === res.first
|
451
|
+
res = res.dup
|
452
|
+
status(context, res.shift)
|
453
|
+
body(context, res.pop)
|
454
|
+
headers(context, *res)
|
455
|
+
elsif res.respond_to? :each
|
456
|
+
body(context, res)
|
457
|
+
end
|
458
|
+
nil # avoid double setting the same response tuple twice
|
459
|
+
end
|
460
|
+
|
461
|
+
# Creates a Hash with indifferent access.
|
462
|
+
def indifferent_hash
|
463
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
def indifferent_params(object)
|
468
|
+
case object
|
469
|
+
when Hash
|
470
|
+
new_hash = indifferent_hash
|
471
|
+
object.each { |key, value| new_hash[key] = indifferent_params(value) }
|
472
|
+
new_hash
|
473
|
+
when Array
|
474
|
+
object.map { |item| indifferent_params(item) }
|
475
|
+
else
|
476
|
+
object
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def status(context, value = nil)
|
481
|
+
context.response.status = value if value
|
482
|
+
context.response.status
|
483
|
+
end
|
484
|
+
|
485
|
+
def headers(context, hash = nil)
|
486
|
+
context.response.headers.merge! hash if hash
|
487
|
+
context.response.headers
|
69
488
|
end
|
70
489
|
|
71
490
|
def initialize(settings_factory)
|
72
491
|
settings = settings_factory[:http]
|
73
492
|
|
493
|
+
@routes = {}
|
74
494
|
@settings = DEFAULT_SETTINGS.merge(settings)
|
75
495
|
@stopsignal = Queue.new()
|
76
496
|
@thread = nil
|
77
497
|
|
78
|
-
@app_class = Class.new(SinatraApp)
|
79
|
-
|
80
498
|
end
|
81
499
|
|
82
500
|
def start()
|
83
|
-
|
84
|
-
if @settings[:
|
501
|
+
|
502
|
+
if @settings[:verbose]
|
85
503
|
@app = Rack::CommonLogger.new(@app, STDOUT)
|
86
504
|
end
|
87
505
|
|
88
506
|
@thread = Thread.new do
|
89
|
-
events_hander = @settings[:
|
90
|
-
server = ::Puma::Server.new(
|
507
|
+
events_hander = @settings[:silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
|
508
|
+
server = ::Puma::Server.new(self, events_hander)
|
91
509
|
|
92
510
|
server.add_tcp_listener @settings[:host], @settings[:port]
|
93
511
|
server.min_threads = 0
|
data/lib/ocular/settings.rb
CHANGED
data/lib/ocular/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocular
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juho Mäkinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rye
|
@@ -53,19 +53,33 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 2.16.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rack
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.4
|
61
|
+
version: 1.6.4
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.4
|
68
|
+
version: 1.6.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-protection
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.5.3
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.5.3
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: faraday
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,7 +134,6 @@ files:
|
|
120
134
|
- lib/ocular/dsl/ssh.rb
|
121
135
|
- lib/ocular/event/eventbase.rb
|
122
136
|
- lib/ocular/event/eventfactory.rb
|
123
|
-
- lib/ocular/event/forker.rb
|
124
137
|
- lib/ocular/inputs/base.rb
|
125
138
|
- lib/ocular/inputs/handlers.rb
|
126
139
|
- lib/ocular/inputs/http_input.rb
|
data/lib/ocular/event/forker.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'ocular/mixin/from_file'
|
2
|
-
require 'ocular/dsl/fog'
|
3
|
-
require 'ocular/dsl/ssh'
|
4
|
-
require 'ocular/dsl/logging'
|
5
|
-
|
6
|
-
class Ocular
|
7
|
-
module Event
|
8
|
-
|
9
|
-
class Forker
|
10
|
-
|
11
|
-
def initialize(&block)
|
12
|
-
puts "Forker created with block #{block}"
|
13
|
-
@callback = block
|
14
|
-
end
|
15
|
-
|
16
|
-
def get_proc()
|
17
|
-
cb = @callback
|
18
|
-
|
19
|
-
def proxy(&block)
|
20
|
-
return block
|
21
|
-
end
|
22
|
-
|
23
|
-
p = proxy do |*args|
|
24
|
-
puts "Calling proc callback: #{cb}"
|
25
|
-
cb.call(*args)
|
26
|
-
end
|
27
|
-
|
28
|
-
puts "returning p: #{p}"
|
29
|
-
|
30
|
-
return p
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|