oflow 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -10
- data/lib/oflow/actors.rb +2 -0
- data/lib/oflow/actors/httpserver.rb +238 -0
- data/lib/oflow/actors/log.rb +5 -0
- data/lib/oflow/actors/trigger.rb +46 -0
- data/lib/oflow/box.rb +10 -6
- data/lib/oflow/errors.rb +7 -0
- data/lib/oflow/haslinks.rb +5 -1
- data/lib/oflow/haslog.rb +7 -0
- data/lib/oflow/task.rb +5 -0
- data/lib/oflow/version.rb +1 -1
- data/test/actors/httpserver_test.rb +57 -0
- data/test/all_tests.rb +1 -0
- data/test/flow_log_test.rb +40 -5
- data/test/flow_nest_test.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d36c10496898f7cb6df17b4be3ba103cb8098169
|
4
|
+
data.tar.gz: 563283b71b4e11bd6b207a464b9204722d472f02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d0572af84b50544594563dd86e625c1b86b444702d793ea1585cc870b31f07194903677e1a65a9c6b0d798b7e7a44031c233ca8e3764f36273c20fdb13ce9ff
|
7
|
+
data.tar.gz: 6ca630368b0c25ee789e761043bbca9dcfb8e11fadf590ef8b19017b0d5ea1811c36135d9c9f2e0ab41b97e8f8776a7d969a88678c5c5a223eea4e2adf203c3c
|
data/README.md
CHANGED
@@ -25,6 +25,10 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
|
|
25
25
|
|
26
26
|
## Release Notes
|
27
27
|
|
28
|
+
### Release 0.6
|
29
|
+
|
30
|
+
- Added HTTP Server Actor that acts as a simple HTTP server.
|
31
|
+
|
28
32
|
### Release 0.5
|
29
33
|
|
30
34
|
- Added Persister Actor that acts as a simple local store database.
|
@@ -119,13 +123,13 @@ Hello World!
|
|
119
123
|
|
120
124
|
## Future Features
|
121
125
|
|
122
|
-
-
|
126
|
+
- HTTP Server Actor
|
123
127
|
|
124
|
-
-
|
128
|
+
- OmniGraffle file input for configuration.
|
125
129
|
|
126
|
-
-
|
130
|
+
- .svg file input for configuration.
|
127
131
|
|
128
|
-
-
|
132
|
+
- Visio file input for configuration.
|
129
133
|
|
130
134
|
- CallOut Actor that uses pipes and fork to use a non-Ruby actor.
|
131
135
|
|
@@ -133,12 +137,6 @@ Hello World!
|
|
133
137
|
|
134
138
|
- Dynamic links to Tasks and Flows.
|
135
139
|
|
136
|
-
- OmniGraffle file input for configuration.
|
137
|
-
|
138
|
-
- .svg file input for configuration.
|
139
|
-
|
140
|
-
- Visio file input for configuration.
|
141
|
-
|
142
140
|
- High performance C version. Proof of concept puts the performance range at
|
143
141
|
around 10M operations per second where an operation is one task execution per
|
144
142
|
thread.
|
data/lib/oflow/actors.rb
CHANGED
@@ -10,6 +10,8 @@ require 'oflow/actors/log'
|
|
10
10
|
require 'oflow/actors/errorhandler'
|
11
11
|
|
12
12
|
require 'oflow/actors/balancer'
|
13
|
+
require 'oflow/actors/trigger'
|
13
14
|
require 'oflow/actors/merger'
|
14
15
|
require 'oflow/actors/persister'
|
15
16
|
require 'oflow/actors/timer'
|
17
|
+
require 'oflow/actors/httpserver'
|
@@ -0,0 +1,238 @@
|
|
1
|
+
|
2
|
+
require 'socket'
|
3
|
+
require 'time'
|
4
|
+
require 'fcntl'
|
5
|
+
|
6
|
+
module OFlow
|
7
|
+
module Actors
|
8
|
+
|
9
|
+
# Provides a simple HTTP server that accepts requests which then trigger a
|
10
|
+
# flow. The execution flow should end back at the server so it can sent a
|
11
|
+
# response to the requester.
|
12
|
+
class HttpServer < Trigger
|
13
|
+
|
14
|
+
def initialize(task, options)
|
15
|
+
super
|
16
|
+
@sessions = { }
|
17
|
+
|
18
|
+
@server = TCPServer.new(@port)
|
19
|
+
@server.fcntl(Fcntl::F_SETFL, @server.fcntl(Fcntl::F_GETFL, 0) | Fcntl::O_NONBLOCK)
|
20
|
+
@server_loop = Thread.start(self) do |me|
|
21
|
+
Thread.current[:name] = me.task.full_name() + '-server'
|
22
|
+
while Task::CLOSING != task.state
|
23
|
+
begin
|
24
|
+
if Task::BLOCKED == task.state || Task::STOPPED == task.state
|
25
|
+
sleep(0.1)
|
26
|
+
next
|
27
|
+
end
|
28
|
+
session = @server.accept_nonblock()
|
29
|
+
session.fcntl(Fcntl::F_SETFL, session.fcntl(Fcntl::F_GETFL, 0) | Fcntl::O_NONBLOCK)
|
30
|
+
@count += 1
|
31
|
+
req = read_req(session, @count)
|
32
|
+
@sessions[@count] = session
|
33
|
+
resp = {
|
34
|
+
status: 200,
|
35
|
+
body: nil,
|
36
|
+
headers: {
|
37
|
+
'Content-Type' => 'text/html',
|
38
|
+
}
|
39
|
+
}
|
40
|
+
box = new_event()
|
41
|
+
box.contents[:request] = req
|
42
|
+
box.contents[:response] = resp
|
43
|
+
task.links.each_key do |key|
|
44
|
+
continue if :success == key || 'success' == key
|
45
|
+
begin
|
46
|
+
task.ship(key, box)
|
47
|
+
rescue BlockedError => e
|
48
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
|
49
|
+
rescue BusyError => e
|
50
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue IO::WaitReadable, Errno::EINTR
|
54
|
+
IO.select([@server], nil, nil, 0.5)
|
55
|
+
rescue Exception => e
|
56
|
+
task.handle_error(e)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform(op, box)
|
63
|
+
case op
|
64
|
+
when :reply
|
65
|
+
req_id = box.get(@req_id_path)
|
66
|
+
if (session = @sessions[req_id]).nil?
|
67
|
+
raise NotFoundError.new(task.full_name, 'session', req_id)
|
68
|
+
end
|
69
|
+
if (resp = box.get(@response_path)).nil?
|
70
|
+
raise NotFoundError.new(task.full_name, 'response', @response_path)
|
71
|
+
end
|
72
|
+
body = resp[:body]
|
73
|
+
body = '' if body.nil?
|
74
|
+
status = resp[:status]
|
75
|
+
headers = ["HTTP/1.1 #{status} {STATUS_MESSAGES[status]}"]
|
76
|
+
resp[:headers].each do |k,v|
|
77
|
+
headers << "#{k}: #{v}"
|
78
|
+
end
|
79
|
+
headers << "Content-Length: #{body.length}\r\n\r\n"
|
80
|
+
session.puts headers.join("\r\n")
|
81
|
+
session.puts body
|
82
|
+
session.close
|
83
|
+
@sessions.delete(req_id)
|
84
|
+
when :skip
|
85
|
+
req_id = box.get(@req_id_path)
|
86
|
+
if (session = @sessions[req_id]).nil?
|
87
|
+
raise NotFoundError.new(task.full_name, 'session', req_id)
|
88
|
+
end
|
89
|
+
@sessions.delete(req_id)
|
90
|
+
else
|
91
|
+
raise OpError.new(task.full_name, op)
|
92
|
+
end
|
93
|
+
task.ship(:success, Box.new(nil, box.tracker)) unless task.links[:success].nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_options(options)
|
97
|
+
super
|
98
|
+
@port = options.fetch(:port, 6060)
|
99
|
+
@req_id_path = options.fetch(:req_id_path, 'request:id')
|
100
|
+
@response_path = options.fetch(:response_path, 'response')
|
101
|
+
@read_timeout = options.fetch(:read_timeout, 1.0)
|
102
|
+
end
|
103
|
+
|
104
|
+
def read_req(session, id)
|
105
|
+
req = {
|
106
|
+
id: id,
|
107
|
+
}
|
108
|
+
line = session.gets()
|
109
|
+
parts = line.split(' ')
|
110
|
+
req[:method] = parts[0]
|
111
|
+
req[:protocol] = parts[2]
|
112
|
+
path, arg_str = parts[1].split('?', 2)
|
113
|
+
req[:path] = path
|
114
|
+
args = nil
|
115
|
+
unless arg_str.nil?
|
116
|
+
args = arg_str.split('&').map { |pair| pair.split('=') }
|
117
|
+
end
|
118
|
+
req[:args] = args
|
119
|
+
|
120
|
+
# Read the rest of the lines and the body if there is one.
|
121
|
+
len = 0
|
122
|
+
while line = session.gets()
|
123
|
+
line.strip!
|
124
|
+
break if 0 == line.size
|
125
|
+
parts = line.split(':', 2)
|
126
|
+
next unless 2 == parts.size
|
127
|
+
key = parts[0]
|
128
|
+
value = parts[1].strip()
|
129
|
+
if 'Content-Length' == key
|
130
|
+
value = value.to_i
|
131
|
+
len = value
|
132
|
+
end
|
133
|
+
req[key] = value
|
134
|
+
end
|
135
|
+
req[:body] = read_timeout(session, len, @read_timeout) if 0 < len
|
136
|
+
req
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_timeout(session, len, timeout)
|
140
|
+
str = ''
|
141
|
+
done = Time.now() + timeout
|
142
|
+
loop do
|
143
|
+
begin
|
144
|
+
str = session.readpartial(len, str)
|
145
|
+
break if str.size == len
|
146
|
+
rescue Errno::EAGAIN => e
|
147
|
+
raise e if IO.select([session], nil, nil, done - Time.now()).nil?
|
148
|
+
retry
|
149
|
+
end
|
150
|
+
end
|
151
|
+
str
|
152
|
+
end
|
153
|
+
|
154
|
+
STATUS_MESSAGES = {
|
155
|
+
100 => 'Continue',
|
156
|
+
101 => 'Switching Protocols',
|
157
|
+
102 => 'Processing',
|
158
|
+
200 => 'OK',
|
159
|
+
201 => 'Created',
|
160
|
+
202 => 'Accepted',
|
161
|
+
203 => 'Non-Authoritative Information',
|
162
|
+
204 => 'No Content',
|
163
|
+
205 => 'Reset Content',
|
164
|
+
206 => 'Partial Content',
|
165
|
+
207 => 'Multi-Status',
|
166
|
+
208 => 'Already Reported',
|
167
|
+
226 => 'IM Used',
|
168
|
+
300 => 'Multiple Choices',
|
169
|
+
301 => 'Moved Permanently',
|
170
|
+
302 => 'Found',
|
171
|
+
303 => 'See Other',
|
172
|
+
304 => 'Not Modified',
|
173
|
+
305 => 'Use Proxy',
|
174
|
+
306 => 'Switch Proxy',
|
175
|
+
307 => 'Temporary Redirect',
|
176
|
+
308 => 'Permanent Redirect',
|
177
|
+
400 => 'Bad Request',
|
178
|
+
401 => 'Unauthorized',
|
179
|
+
402 => 'Payment Required',
|
180
|
+
403 => 'Forbidden',
|
181
|
+
404 => 'Not Found',
|
182
|
+
405 => 'Method Not Allowed',
|
183
|
+
406 => 'Not Acceptable',
|
184
|
+
407 => 'Proxy Authentication Required',
|
185
|
+
408 => 'Request Timeout',
|
186
|
+
409 => 'Conflict',
|
187
|
+
410 => 'Gone',
|
188
|
+
411 => 'Length Required',
|
189
|
+
412 => 'Precondition Failed',
|
190
|
+
413 => 'Request Entity Too Large',
|
191
|
+
414 => 'Request-URI Too Long',
|
192
|
+
415 => 'Unsupported Media Type',
|
193
|
+
416 => 'Requested Range Not Satisfiable',
|
194
|
+
417 => 'Expectation Failed',
|
195
|
+
418 => "I'm a teapot",
|
196
|
+
419 => 'Authentication Timeout',
|
197
|
+
420 => 'Method Failure',
|
198
|
+
422 => 'Unprocessed Entity',
|
199
|
+
423 => 'Locked',
|
200
|
+
424 => 'Failed Dependency',
|
201
|
+
425 => 'Unordered Collection',
|
202
|
+
426 => 'Upgrade Required',
|
203
|
+
428 => 'Precondition Required',
|
204
|
+
429 => 'Too Many Requests',
|
205
|
+
431 => 'Request Header Fields Too Long',
|
206
|
+
440 => 'Login Timeout',
|
207
|
+
444 => 'No Response',
|
208
|
+
449 => 'Retry With',
|
209
|
+
450 => 'Blocked by Windows Parental Controls',
|
210
|
+
451 => 'Unavailable For Legal Reasons',
|
211
|
+
494 => 'Request Header Too Large',
|
212
|
+
495 => 'Cert Error',
|
213
|
+
496 => 'No Cert',
|
214
|
+
497 => 'HTTP tp HTTP',
|
215
|
+
499 => 'Client Closed Request',
|
216
|
+
500 => 'Internal Server Error',
|
217
|
+
501 => 'Not Implemented',
|
218
|
+
502 => 'Bad Gateway',
|
219
|
+
503 => 'Service Unavailable',
|
220
|
+
504 => 'Gateway Timeout',
|
221
|
+
505 => 'HTTP Version Not Supported',
|
222
|
+
506 => 'Variant Also Negotiates',
|
223
|
+
507 => 'Insufficient Storage',
|
224
|
+
508 => 'Loop Detected',
|
225
|
+
509 => 'Bandwidth Limit Exceeded',
|
226
|
+
510 => 'Not Extended',
|
227
|
+
511 => 'Network Authentication Required',
|
228
|
+
520 => 'Original Error',
|
229
|
+
522 => 'Connection timed out',
|
230
|
+
523 => 'Proxy Declined Request',
|
231
|
+
524 => 'A timeout occurred',
|
232
|
+
598 => 'Network read timeout error',
|
233
|
+
599 => 'Network connect timeout error',
|
234
|
+
}
|
235
|
+
|
236
|
+
end # HttpServer
|
237
|
+
end # Actors
|
238
|
+
end # OFlow
|
data/lib/oflow/actors/log.rb
CHANGED
@@ -14,6 +14,11 @@ module OFlow
|
|
14
14
|
:warn => Logger::Severity::WARN,
|
15
15
|
:info => Logger::Severity::INFO,
|
16
16
|
:debug => Logger::Severity::DEBUG,
|
17
|
+
:FATAL => Logger::Severity::FATAL,
|
18
|
+
:ERROR => Logger::Severity::ERROR,
|
19
|
+
:WARN => Logger::Severity::WARN,
|
20
|
+
:INFO => Logger::Severity::INFO,
|
21
|
+
:DEBUG => Logger::Severity::DEBUG,
|
17
22
|
}
|
18
23
|
def initialize(task, options={})
|
19
24
|
@logger = nil
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module OFlow
|
3
|
+
module Actors
|
4
|
+
|
5
|
+
class Trigger < Actor
|
6
|
+
|
7
|
+
# Label for the Tracker is used and for trigger content.
|
8
|
+
attr_reader :label
|
9
|
+
# Boolean flag indicating a tracker should be added to the trigger content
|
10
|
+
# if true.
|
11
|
+
attr_reader :with_tracker
|
12
|
+
# The number of time the timer has fired or shipped.
|
13
|
+
attr_reader :count
|
14
|
+
|
15
|
+
def initialize(task, options)
|
16
|
+
super
|
17
|
+
@count = 0
|
18
|
+
set_options(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_event()
|
22
|
+
tracker = @with_tracker ? Tracker.create(@label) : nil
|
23
|
+
Box.new({ source: task.full_name, label: @label, timestamp: Time.now.utc() }, tracker)
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_options(options)
|
27
|
+
set_with_tracker(options[:with_tracker])
|
28
|
+
@label = options[:label].to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_label(v)
|
32
|
+
v = v.to_s unless v.nil?
|
33
|
+
@label = v
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_with_tracker(v)
|
37
|
+
v = false if v.nil?
|
38
|
+
unless true == v || false == v
|
39
|
+
raise ConfigError.new("Expected with_tracker to be a boolean, not a #{v.class}.")
|
40
|
+
end
|
41
|
+
@with_tracker = v
|
42
|
+
end
|
43
|
+
|
44
|
+
end # Trigger
|
45
|
+
end # Actors
|
46
|
+
end # OFlow
|
data/lib/oflow/box.rb
CHANGED
@@ -74,7 +74,11 @@ module OFlow
|
|
74
74
|
|
75
75
|
# Returns a string representation of the Box and contents.
|
76
76
|
def to_s()
|
77
|
-
|
77
|
+
if @tracker.nil?
|
78
|
+
"Box{#{@contents}}"
|
79
|
+
else
|
80
|
+
"Box{#{@contents}, tracker: #{@tracker}}"
|
81
|
+
end
|
78
82
|
end
|
79
83
|
alias inspect to_s
|
80
84
|
|
@@ -152,14 +156,14 @@ module OFlow
|
|
152
156
|
value[ps] = _aset(path[1..-1], value[ps], rv)
|
153
157
|
end
|
154
158
|
when NilClass
|
155
|
-
|
156
|
-
i = p.to_i
|
157
|
-
value = []
|
158
|
-
value[i] = _aset(path[1..-1], nil, rv)
|
159
|
-
rescue
|
159
|
+
if /^\d+$/.match(p).nil?
|
160
160
|
ps = p.to_sym
|
161
161
|
value = {}
|
162
162
|
value[ps] = _aset(path[1..-1], nil, rv)
|
163
|
+
else
|
164
|
+
i = p.to_i
|
165
|
+
value = []
|
166
|
+
value[i] = _aset(path[1..-1], nil, rv)
|
163
167
|
end
|
164
168
|
else
|
165
169
|
raise FrozenError.new(p, value)
|
data/lib/oflow/errors.rb
CHANGED
@@ -44,6 +44,13 @@ module OFlow
|
|
44
44
|
end
|
45
45
|
end # LinkError
|
46
46
|
|
47
|
+
# An Exception indicating an item was not found.
|
48
|
+
class NotFoundError < Exception
|
49
|
+
def initialize(name, type, id)
|
50
|
+
super("The #{type} identified as '#{id}' was not found in #{name}.")
|
51
|
+
end
|
52
|
+
end # NotFoundError
|
53
|
+
|
47
54
|
# An Exception raised when there are validation errors.
|
48
55
|
class ValidateError < Exception
|
49
56
|
attr_accessor :problems
|
data/lib/oflow/haslinks.rb
CHANGED
data/lib/oflow/haslog.rb
CHANGED
@@ -30,7 +30,14 @@ module OFlow
|
|
30
30
|
# @param msg [String] message to log
|
31
31
|
# @param fn [String] full name of Task or Flow calling the log function
|
32
32
|
def log_msg(level, msg, fn)
|
33
|
+
# Abort early if the log severity/level would not log the message. This
|
34
|
+
# also allows non-Loggers to be used in place of the Log Actor.
|
35
|
+
return if Env.log_level > Actors::Log::SEVERITY_MAP[level]
|
36
|
+
|
33
37
|
lt = log()
|
38
|
+
# To prevent infinite looping, don't allow the logger to log to itself.
|
39
|
+
return if self == lt
|
40
|
+
|
34
41
|
unless lt.nil?
|
35
42
|
lt.receive(level, Box.new([msg, fn]))
|
36
43
|
else
|
data/lib/oflow/task.rb
CHANGED
@@ -52,6 +52,7 @@ module OFlow
|
|
52
52
|
init_links()
|
53
53
|
set_options(options)
|
54
54
|
|
55
|
+
info("Creating actor #{actor_class} with options #{options}.")
|
55
56
|
@actor = actor_class.new(self, options)
|
56
57
|
raise Exception.new("#{actor} does not respond to the perform() method.") unless @actor.respond_to?(:perform)
|
57
58
|
|
@@ -76,6 +77,7 @@ module OFlow
|
|
76
77
|
|
77
78
|
@current_req = req
|
78
79
|
begin
|
80
|
+
info("perform(#{req.op}, #{req.box})")
|
79
81
|
@actor.perform(req.op, req.box) unless req.nil?
|
80
82
|
rescue Exception => e
|
81
83
|
handle_error(e)
|
@@ -279,6 +281,8 @@ module OFlow
|
|
279
281
|
# @param op [Symbol] operation to perform
|
280
282
|
# @param box [Box] contents or data for the request
|
281
283
|
def receive(op, box)
|
284
|
+
info("receive(#{op}, #{box}) #{state_string}")
|
285
|
+
|
282
286
|
return if CLOSING == @state
|
283
287
|
|
284
288
|
raise BlockedError.new() if BLOCKED == @state
|
@@ -313,6 +317,7 @@ module OFlow
|
|
313
317
|
box.freeze() unless box.nil?
|
314
318
|
link = resolve_link(dest)
|
315
319
|
raise LinkError.new(dest) if link.nil? || link.target.nil?
|
320
|
+
info("shipping #{box} to #{link.target_name}:#{link.op}")
|
316
321
|
link.target.receive(link.op, box)
|
317
322
|
link
|
318
323
|
end
|
data/lib/oflow/version.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
[ File.dirname(__FILE__),
|
5
|
+
File.join(File.dirname(__FILE__), "../../lib"),
|
6
|
+
File.join(File.dirname(__FILE__), "..")
|
7
|
+
].each { |path| $: << path unless $:.include?(path) }
|
8
|
+
|
9
|
+
require 'net/http'
|
10
|
+
require 'test/unit'
|
11
|
+
require 'oflow'
|
12
|
+
require 'oflow/test'
|
13
|
+
|
14
|
+
class Reply < ::OFlow::Actor
|
15
|
+
|
16
|
+
def initialize(task, options)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform(op, box)
|
21
|
+
reply = box.get('request')
|
22
|
+
box = box.set('response:body', reply.to_s)
|
23
|
+
task.ship(nil, box)
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Reply
|
27
|
+
|
28
|
+
class HttpServerTest < ::Test::Unit::TestCase
|
29
|
+
|
30
|
+
def test_httpserver
|
31
|
+
::OFlow::Env.flow('http-server', port: 6060) { |f|
|
32
|
+
f.task('server', ::OFlow::Actors::HttpServer) { |t|
|
33
|
+
t.link(nil, :reply, nil)
|
34
|
+
}
|
35
|
+
f.task(:reply, Reply) { |t|
|
36
|
+
t.link(nil, :server, :reply)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
# GET
|
40
|
+
uri = URI('http://localhost:6060/test?a=1&b=two')
|
41
|
+
reply = Net::HTTP.get(uri)
|
42
|
+
assert_equal(%|{:id=>1, :method=>"GET", :protocol=>"HTTP/1.1", :path=>"/test", :args=>[["a", "1"], ["b", "two"]], "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "Accept"=>"*/*", "User-Agent"=>"Ruby", "Host"=>"localhost:6060"}|,
|
43
|
+
reply, 'expected reply from GET')
|
44
|
+
|
45
|
+
# POST
|
46
|
+
uri = URI('http://localhost:6060/test')
|
47
|
+
response = Net::HTTP.post_form(uri, 'a' => '1', 'b' => 'two')
|
48
|
+
reply = response.body
|
49
|
+
|
50
|
+
assert_equal(%|{:id=>2, :method=>\"POST\", :protocol=>\"HTTP/1.1\", :path=>\"/test\", :args=>nil, \"Accept-Encoding\"=>\"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\", \"Accept\"=>\"*/*\", \"User-Agent\"=>\"Ruby\", \"Host\"=>\"localhost:6060\", \"Content-Type\"=>\"application/x-www-form-urlencoded\", \"Content-Length\"=>9, :body=>\"a=1&b=two\"}|,
|
51
|
+
reply, 'expected reply from POST')
|
52
|
+
|
53
|
+
::OFlow::Env.flush()
|
54
|
+
::OFlow::Env.clear()
|
55
|
+
end
|
56
|
+
|
57
|
+
end # HttpServerTest
|
data/test/all_tests.rb
CHANGED
data/test/flow_log_test.rb
CHANGED
@@ -17,7 +17,8 @@ class Noise < ::OFlow::Actor
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def perform(op, box)
|
20
|
-
task.
|
20
|
+
task.warn("op: #{op}, box: #{box.contents}")
|
21
|
+
task.ship(nil, ::OFlow::Box.new([box.contents])) if task.has_links?
|
21
22
|
end
|
22
23
|
|
23
24
|
end # Noise
|
@@ -28,6 +29,7 @@ class FlowLogTest < ::Test::Unit::TestCase
|
|
28
29
|
def test_flow_log_relay
|
29
30
|
trigger = nil
|
30
31
|
collector = nil
|
32
|
+
::OFlow::Env.log_level = Logger::WARN
|
31
33
|
::OFlow::Env.flow('log_relay') { |f|
|
32
34
|
trigger = f.task('noise', Noise)
|
33
35
|
f.task(:log, Collector) { |t|
|
@@ -48,7 +50,8 @@ class FlowLogTest < ::Test::Unit::TestCase
|
|
48
50
|
def test_flow_log_var
|
49
51
|
trigger = nil
|
50
52
|
collector = nil
|
51
|
-
::OFlow::Env.
|
53
|
+
::OFlow::Env.log_level = Logger::WARN
|
54
|
+
::OFlow::Env.flow('log_var') { |f|
|
52
55
|
trigger = f.task('noise', Noise)
|
53
56
|
f.log = f.task(:collector, Collector) { |t|
|
54
57
|
collector = t.actor
|
@@ -59,7 +62,7 @@ class FlowLogTest < ::Test::Unit::TestCase
|
|
59
62
|
|
60
63
|
assert_equal(collector.collection.size, 1)
|
61
64
|
assert_equal(collector.collection[0][0], 'op: speak, box: 7')
|
62
|
-
assert_equal(collector.collection[0][1], ':
|
65
|
+
assert_equal(collector.collection[0][1], ':log_var:noise')
|
63
66
|
|
64
67
|
::OFlow::Env.clear()
|
65
68
|
end
|
@@ -68,7 +71,8 @@ class FlowLogTest < ::Test::Unit::TestCase
|
|
68
71
|
def test_flow_log_env
|
69
72
|
trigger = nil
|
70
73
|
collector = nil
|
71
|
-
::OFlow::Env.
|
74
|
+
::OFlow::Env.log_level = Logger::WARN
|
75
|
+
::OFlow::Env.flow('log_env') { |f|
|
72
76
|
trigger = f.task('noise', Noise)
|
73
77
|
::OFlow::Env.log = f.task(:collector, Collector) { |t|
|
74
78
|
collector = t.actor
|
@@ -79,9 +83,40 @@ class FlowLogTest < ::Test::Unit::TestCase
|
|
79
83
|
|
80
84
|
assert_equal(collector.collection.size, 1)
|
81
85
|
assert_equal(collector.collection[0][0], 'op: speak, box: 7')
|
82
|
-
assert_equal(collector.collection[0][1], ':
|
86
|
+
assert_equal(collector.collection[0][1], ':log_env:noise')
|
87
|
+
|
88
|
+
::OFlow::Env.clear()
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_flow_log_info
|
92
|
+
trigger = nil
|
93
|
+
collector = nil
|
94
|
+
::OFlow::Env.log_level = Logger::WARN
|
95
|
+
::OFlow::Env.flow('log_info') { |f|
|
96
|
+
f.log = f.task(:collector, Collector) { |t|
|
97
|
+
collector = t.actor
|
98
|
+
}
|
99
|
+
# Set after log to avoid race condition with the creation of the collector
|
100
|
+
# and the assignment to f.log. The race is whether a log message is
|
101
|
+
# displayed on the output.
|
102
|
+
::OFlow::Env.log_level = Logger::INFO
|
103
|
+
trigger = f.task('noise', Noise) { |t|
|
104
|
+
t.link(nil, :collector, nil)
|
105
|
+
}
|
106
|
+
}
|
107
|
+
trigger.receive(:speak, ::OFlow::Box.new(7))
|
108
|
+
::OFlow::Env.flush()
|
109
|
+
|
110
|
+
entries = collector.collection.map { |entry| entry[0] }
|
111
|
+
assert_equal(["Creating actor Noise with options {:state=>1}.",
|
112
|
+
"receive(speak, Box{7}) RUNNING",
|
113
|
+
"perform(speak, Box{7})",
|
114
|
+
"op: speak, box: 7",
|
115
|
+
"shipping Box{[7]} to collector:",
|
116
|
+
7], entries)
|
83
117
|
|
84
118
|
::OFlow::Env.clear()
|
119
|
+
::OFlow::Env.log_level = Logger::WARN
|
85
120
|
end
|
86
121
|
|
87
122
|
end # FlowLogTest
|
data/test/flow_nest_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Ohler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Operations Workflow in Ruby. This implements a workflow/process flow
|
14
14
|
using multiple task nodes that each have their own queues and execution thread.
|
@@ -25,12 +25,14 @@ files:
|
|
25
25
|
- lib/oflow/actors.rb
|
26
26
|
- lib/oflow/actors/balancer.rb
|
27
27
|
- lib/oflow/actors/errorhandler.rb
|
28
|
+
- lib/oflow/actors/httpserver.rb
|
28
29
|
- lib/oflow/actors/ignore.rb
|
29
30
|
- lib/oflow/actors/log.rb
|
30
31
|
- lib/oflow/actors/merger.rb
|
31
32
|
- lib/oflow/actors/persister.rb
|
32
33
|
- lib/oflow/actors/relay.rb
|
33
34
|
- lib/oflow/actors/timer.rb
|
35
|
+
- lib/oflow/actors/trigger.rb
|
34
36
|
- lib/oflow/box.rb
|
35
37
|
- lib/oflow/env.rb
|
36
38
|
- lib/oflow/errors.rb
|
@@ -51,6 +53,7 @@ files:
|
|
51
53
|
- lib/oflow/tracker.rb
|
52
54
|
- lib/oflow/version.rb
|
53
55
|
- test/actors/balancer_test.rb
|
56
|
+
- test/actors/httpserver_test.rb
|
54
57
|
- test/actors/log_test.rb
|
55
58
|
- test/actors/merger_test.rb
|
56
59
|
- test/actors/persister_test.rb
|
@@ -91,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
94
|
version: '0'
|
92
95
|
requirements: []
|
93
96
|
rubyforge_project: oflow
|
94
|
-
rubygems_version: 2.2.
|
97
|
+
rubygems_version: 2.2.2
|
95
98
|
signing_key:
|
96
99
|
specification_version: 4
|
97
100
|
summary: Operations Workflow in Ruby
|