oflow 0.5.0 → 0.6.0
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/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
|