pione 0.4.0 → 0.4.1
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 +15 -0
- data/.gitignore +1 -0
- data/History.txt +7 -0
- data/example/DeferredChoice/DeferredChoice.pione +14 -7
- data/{misc → example/DeferredChoice/bin}/ui.xml +11 -6
- data/example/DeferredChoice/pione-package.json +1 -1
- data/example/DeferredChoiceWithPage/DeferredChoiceWithPage.pione +47 -0
- data/example/DeferredChoiceWithPage/etc/index.html +22 -0
- data/example/DeferredChoiceWithPage/pione-package.json +18 -0
- data/example/Interaction/Interaction.pione +27 -0
- data/example/Interaction/bin/show-environment.cgi +45 -0
- data/example/Interaction/etc/.hidden-file.txt +1 -0
- data/example/Interaction/etc/cgi.html +46 -0
- data/example/Interaction/etc/create-files.html +51 -0
- data/example/Interaction/etc/delete-files.html +31 -0
- data/example/Interaction/etc/get-files.html +21 -0
- data/example/Interaction/etc/index.html +18 -0
- data/example/Interaction/etc/list-files.html +36 -0
- data/example/Interaction/pione-package.json +24 -0
- data/lib/pione/agent/job-manager.rb +19 -3
- data/lib/pione/agent/logger.rb +9 -9
- data/lib/pione/agent/messenger.rb +3 -2
- data/lib/pione/agent/task-worker.rb +32 -10
- data/lib/pione/command/option.rb +18 -0
- data/lib/pione/command/pione-client.rb +51 -22
- data/lib/pione/command/pione-interactive.rb +128 -55
- data/lib/pione/command/pione-package-build.rb +3 -3
- data/lib/pione/command/pione-task-worker.rb +23 -10
- data/lib/pione/command/pione-tuple-space-provider.rb +8 -8
- data/lib/pione/command/spawner.rb +28 -1
- data/lib/pione/front/interactive-front.rb +76 -0
- data/lib/pione/global/interactive-variable.rb +26 -0
- data/lib/pione/log/message-log-receiver.rb +4 -2
- data/lib/pione/model/task-worker-broker-model.rb +7 -1
- data/lib/pione/package/package-archiver.rb +16 -9
- data/lib/pione/package/package-handler.rb +6 -0
- data/lib/pione/package/package-info.rb +4 -0
- data/lib/pione/package/package-reader.rb +2 -4
- data/lib/pione/package/package-scanner.rb +23 -3
- data/lib/pione/rule-engine.rb +48 -19
- data/lib/pione/rule-engine/action-handler.rb +29 -5
- data/lib/pione/rule-engine/basic-handler.rb +27 -19
- data/lib/pione/test-helper/command-helper.rb +1 -1
- data/lib/pione/tuple-space/tuple-definition.yml +2 -3
- data/lib/pione/tuple-space/tuple-space-server.rb +8 -0
- data/lib/pione/util/cgi.rb +326 -0
- data/lib/pione/version.rb +1 -1
- data/lib/rootage/normalizer.rb +4 -0
- data/lib/rootage/option.rb +11 -4
- data/test/agent/spec_logger.rb +0 -1
- data/test/agent/spec_messenger.rb +1 -1
- data/test/command/spec_pione-client.rb +4 -4
- data/test/log/spec_message-log.rb +1 -1
- data/test/rule-engine/spec_action-handler.rb +25 -5
- data/test/rule-engine/spec_empty-handler.rb +36 -3
- data/test/rule-engine/spec_flow-handler.rb +90 -7
- metadata +22 -72
- data/misc/test-drb-stop-service.rb +0 -34
- data/misc/test-many-waiters-client.rb +0 -56
- data/misc/test-many-waiters-server.rb +0 -14
data/lib/pione/rule-engine.rb
CHANGED
@@ -1,27 +1,56 @@
|
|
1
1
|
module Pione
|
2
2
|
# RuleEngine is a namespace for rule engine classes.
|
3
3
|
module RuleEngine
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
require 'pione/rule-engine/engine-exception'
|
5
|
+
require 'pione/rule-engine/data-finder'
|
6
|
+
require 'pione/rule-engine/basic-handler'
|
7
|
+
require 'pione/rule-engine/update-criteria'
|
8
|
+
require 'pione/rule-engine/flow-handler'
|
9
|
+
require 'pione/rule-engine/action-handler'
|
10
|
+
require 'pione/rule-engine/root-handler'
|
11
|
+
require 'pione/rule-engine/system-handler'
|
12
|
+
require 'pione/rule-engine/empty-handler'
|
13
|
+
|
14
|
+
# Relation from rule definition to the handler.
|
15
|
+
HANDLER = {
|
16
|
+
Lang::FlowRuleDefinition => FlowHandler,
|
17
|
+
Lang::ActionRuleDefinition => ActionHandler,
|
18
|
+
Lang::EmptyRuleDefinition => EmptyHandler,
|
19
|
+
Lang::RootRuleDefinition => RootHandler
|
20
|
+
}
|
21
|
+
|
22
|
+
# Make a rule handler with target rule's informations.
|
23
|
+
#
|
24
|
+
# @param [Hash.new] param
|
25
|
+
# @option param [String] :package_id
|
26
|
+
# package ID
|
27
|
+
# @option param [String] :rule_name
|
28
|
+
# rule name
|
29
|
+
# @option param [Array] :inputs
|
30
|
+
# input data
|
31
|
+
# @option param [Object] :param_set
|
32
|
+
# parameter set
|
33
|
+
# @option param [String] :domain_id
|
34
|
+
# domain ID
|
35
|
+
# @option param [String] :caller_id
|
36
|
+
# caller ID
|
37
|
+
# @option param [URI] :request_from
|
38
|
+
# URI that the client requested the job from
|
39
|
+
# @option param [String] :session_id
|
40
|
+
# session ID
|
41
|
+
def self.make(param)
|
42
|
+
# check requisite parameters
|
43
|
+
requisites = [:tuple_space, :env, :package_id, :rule_name, :inputs, :param_set, :domain_id, :caller_id]
|
44
|
+
requisites.each do |requisite|
|
45
|
+
unless param.has_key?(requisite)
|
46
|
+
raise ArgumentError.new("parameter '%s' is requisite for rule engine." % requisite)
|
12
47
|
end
|
13
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
# make a rule handler
|
51
|
+
rule_definition = param[:env].rule_get(Lang::RuleExpr.new(param[:rule_name], param[:package_id]))
|
52
|
+
HANDLER[rule_definition.class].new(param.merge(rule_definition: rule_definition))
|
14
53
|
end
|
15
54
|
end
|
16
55
|
end
|
17
56
|
|
18
|
-
require 'pione/rule-engine/engine-exception'
|
19
|
-
require 'pione/rule-engine/data-finder'
|
20
|
-
require 'pione/rule-engine/basic-handler'
|
21
|
-
require 'pione/rule-engine/update-criteria'
|
22
|
-
require 'pione/rule-engine/flow-handler'
|
23
|
-
require 'pione/rule-engine/action-handler'
|
24
|
-
require 'pione/rule-engine/root-handler'
|
25
|
-
require 'pione/rule-engine/system-handler'
|
26
|
-
require 'pione/rule-engine/empty-handler'
|
27
|
-
|
@@ -8,8 +8,8 @@ module Pione
|
|
8
8
|
|
9
9
|
attr_reader :working_directory
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
super(
|
11
|
+
def initialize(param)
|
12
|
+
super(param)
|
13
13
|
@working_directory = Location[Global.working_directory_generator.mkdir]
|
14
14
|
@env.variable_set(
|
15
15
|
Lang::Variable.new("__WORKING_DIRECTORY__"),
|
@@ -22,7 +22,7 @@ module Pione
|
|
22
22
|
# prepare input files
|
23
23
|
setup_working_directory
|
24
24
|
# prepare shell script
|
25
|
-
write_shell_script {|path| call_shell_script(path)
|
25
|
+
write_shell_script {|path| call_shell_script(path)}
|
26
26
|
# collect outputs
|
27
27
|
outputs = collect_outputs
|
28
28
|
# write output data
|
@@ -68,16 +68,30 @@ module Pione
|
|
68
68
|
bin.entries.each do |entry|
|
69
69
|
dest = @working_directory + "bin" + entry.basename
|
70
70
|
unless dest.exist?
|
71
|
+
# copy and set executable flag
|
71
72
|
entry.copy(dest)
|
72
73
|
dest.path.chmod(0700)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
77
|
+
|
78
|
+
# FIXME: should not copy files in the package each time
|
79
|
+
file_dir = @base_location + "package" + "etc"
|
80
|
+
if file_dir.exist?
|
81
|
+
file_dir.entries.each do |entry|
|
82
|
+
dest = @working_directory + "etc" + entry.basename
|
83
|
+
unless dest.exist?
|
84
|
+
# copy and unset executable flag
|
85
|
+
entry.copy(dest)
|
86
|
+
dest.path.chmod(0600)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
76
90
|
end
|
77
91
|
|
78
92
|
# Write the action into a shell script.
|
79
93
|
def write_shell_script(&b)
|
80
|
-
file = @working_directory + "
|
94
|
+
file = @working_directory + "__pione__.sh"
|
81
95
|
content = @rule_definition.action_context.eval(@env).content
|
82
96
|
sh = Util::EmbededExprExpander.expand(@env, content)
|
83
97
|
|
@@ -113,7 +127,17 @@ module Pione
|
|
113
127
|
err = ".stderr"
|
114
128
|
|
115
129
|
# execute command
|
116
|
-
`cd #{
|
130
|
+
# `cd #{}; PATH=#{(@working_directory + "bin").path}:$PATH; ./#{scriptname} > #{out} 2> #{err}`
|
131
|
+
callee_env = {
|
132
|
+
"PATH" => (@working_directory + "bin").path.to_s + ";" + ENV["PATH"],
|
133
|
+
"PIONE_SESSION_ID" => @session_id,
|
134
|
+
"PIONE_REQUEST_FROM" => @request_from.to_s,
|
135
|
+
"PIONE_CLIENT_UI" => @client_ui.to_s
|
136
|
+
}
|
137
|
+
command = "./#{scriptname} > #{out} 2> #{err}"
|
138
|
+
options = {:chdir => @working_directory.path.to_s}
|
139
|
+
|
140
|
+
system(callee_env, command, options)
|
117
141
|
|
118
142
|
# the case the script has errored
|
119
143
|
unless $?.success?
|
@@ -22,26 +22,32 @@ module Pione
|
|
22
22
|
attr_reader :caller_id # from domain
|
23
23
|
|
24
24
|
# Create a new handler for rule.
|
25
|
-
|
25
|
+
#
|
26
|
+
# @param [Hash] param
|
27
|
+
# see `RuleEngine.make`
|
28
|
+
def initialize(param)
|
26
29
|
### set tuple space server
|
27
|
-
set_tuple_space(
|
30
|
+
set_tuple_space(param[:tuple_space])
|
28
31
|
|
29
32
|
### set informations
|
30
|
-
@plain_env = env
|
31
|
-
@env = setup_env(env, param_set)
|
32
|
-
@package_id = package_id
|
33
|
-
@rule_name = rule_name
|
34
|
-
@rule_definition = rule_definition
|
35
|
-
@rule_condition = rule_definition.rule_condition_context.eval(@env)
|
36
|
-
@inputs = inputs
|
33
|
+
@plain_env = param[:env]
|
34
|
+
@env = setup_env(param[:env], param[:param_set])
|
35
|
+
@package_id = param[:package_id]
|
36
|
+
@rule_name = param[:rule_name]
|
37
|
+
@rule_definition = param[:rule_definition]
|
38
|
+
@rule_condition = @rule_definition.rule_condition_context.eval(@env)
|
39
|
+
@inputs = param[:inputs]
|
37
40
|
@outputs = []
|
38
|
-
@param_set = param_set
|
39
|
-
@digest = Util::TaskDigest.generate(package_id, rule_name, inputs, param_set)
|
41
|
+
@param_set = param[:param_set]
|
42
|
+
@digest = Util::TaskDigest.generate(@package_id, @rule_name, @inputs, @param_set)
|
40
43
|
@base_location = read!(TupleSpace::BaseLocationTuple.any).location
|
41
44
|
@dry_run = begin read!(TupleSpace::DryRunTuple.any).availability rescue false end
|
42
|
-
@domain_id = domain_id
|
45
|
+
@domain_id = param[:domain_id]
|
43
46
|
@domain_location = make_location("", @domain_id)
|
44
|
-
@caller_id = caller_id
|
47
|
+
@caller_id = param[:caller_id]
|
48
|
+
@request_from = param[:request_from]
|
49
|
+
@session_id = param[:session_id]
|
50
|
+
@client_ui = param[:client_ui]
|
45
51
|
end
|
46
52
|
|
47
53
|
# Handle the rule and return the outputs.
|
@@ -54,12 +60,14 @@ module Pione
|
|
54
60
|
user_message(@digest, 0, "==>")
|
55
61
|
debug_message("caller: %s" % @caller_id)
|
56
62
|
|
57
|
-
|
58
|
-
|
63
|
+
unless @domain_id == "root"
|
64
|
+
# save domain log
|
65
|
+
Log::DomainLog.new(self).save
|
59
66
|
|
60
|
-
|
61
|
-
|
62
|
-
|
67
|
+
# save a domain dump file
|
68
|
+
domain_dump_location = @working_directory ? @working_directory :@domain_location
|
69
|
+
System::DomainDump.new(env.dumpable).write(domain_dump_location)
|
70
|
+
end
|
63
71
|
|
64
72
|
# execute the rule
|
65
73
|
outputs = execute
|
@@ -96,7 +104,7 @@ module Pione
|
|
96
104
|
# the location
|
97
105
|
def make_location(name, domain_id)
|
98
106
|
if domain_id == "root"
|
99
|
-
return @base_location + "
|
107
|
+
return @base_location + "./output/%s" % name
|
100
108
|
else
|
101
109
|
# make relative path
|
102
110
|
pakcage_id, rule_name, task_id = domain_id.split(":")
|
@@ -11,7 +11,7 @@ module Pione
|
|
11
11
|
def self.test(context, &b)
|
12
12
|
# with client mode
|
13
13
|
new(context: context).tap do |runner|
|
14
|
-
runner.default_arguments = ["
|
14
|
+
runner.default_arguments = ["--base", runner.base.path.to_s]
|
15
15
|
b.call(runner)
|
16
16
|
end
|
17
17
|
end
|
@@ -191,6 +191,14 @@ module Pione
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
+
def attribute(name)
|
195
|
+
if tuple = read!(TupleSpace::AttributeTuple.new(key: name))
|
196
|
+
return tuple.value
|
197
|
+
else
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
194
202
|
def inspect
|
195
203
|
"#<Pione::TupleSpace::TupleSpaceServer:%s>" % object_id
|
196
204
|
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
module Pione
|
2
|
+
module Util
|
3
|
+
module CGIUtils
|
4
|
+
def self.decode(string)
|
5
|
+
URI.decode_www_form_component(string)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# CGIInfo is a store of CGI meta-variables based on RFC3875.
|
10
|
+
class CGIInfo
|
11
|
+
# CGI meta-variable "AUTH_TYPE"
|
12
|
+
attr_accessor :auth_type
|
13
|
+
|
14
|
+
# CGI meta-variable "CONTENT_LENGTH"
|
15
|
+
attr_accessor :content_length
|
16
|
+
|
17
|
+
# CGI meta-variable "CONTENT_TYPE"
|
18
|
+
attr_accessor :content_type
|
19
|
+
|
20
|
+
# CGI meta-variable "GATEWAY_INTERFACE"
|
21
|
+
attr_accessor :gateway_interface
|
22
|
+
|
23
|
+
# CGI meta-variable "PATH_INFO"
|
24
|
+
attr_accessor :path_info
|
25
|
+
|
26
|
+
# CGI meta-variable "PATH_TRANSLATED"
|
27
|
+
attr_accessor :path_translated
|
28
|
+
|
29
|
+
# CGI meta-variable "QUERY_STRING"
|
30
|
+
attr_accessor :query_string
|
31
|
+
|
32
|
+
# CGI meta-variable "REMOTE_ADDR"
|
33
|
+
attr_accessor :remote_addr
|
34
|
+
|
35
|
+
# CGI meta-variable "REMOTE_HOST"
|
36
|
+
attr_accessor :remote_host
|
37
|
+
|
38
|
+
# CGI meta-variable "REMOTE_IDENT"
|
39
|
+
attr_accessor :remote_ident
|
40
|
+
|
41
|
+
# CGI meta-variable "REMOTE_USER"
|
42
|
+
attr_accessor :remote_user
|
43
|
+
|
44
|
+
# CGI meta-variable "REQUEST_METHOD"
|
45
|
+
attr_accessor :request_method
|
46
|
+
|
47
|
+
# CGI meta-variable "SCRIPT_NAME"
|
48
|
+
attr_accessor :script_name
|
49
|
+
|
50
|
+
# CGI meta-variable "SERVER_NAME"
|
51
|
+
attr_accessor :server_name
|
52
|
+
|
53
|
+
# CGI meta-variable "SERVER_PORT"
|
54
|
+
attr_accessor :server_port
|
55
|
+
|
56
|
+
# CGI meta-variable "SERVER_PROTOCOL"
|
57
|
+
attr_accessor :server_protocol
|
58
|
+
|
59
|
+
# CGI meta-variable "SERVER_SOFTWARE"
|
60
|
+
attr_accessor :server_software
|
61
|
+
|
62
|
+
# HTTP specific variable table
|
63
|
+
attr_accessor :http_header
|
64
|
+
|
65
|
+
# request body
|
66
|
+
attr_accessor :body
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@auth_type = nil
|
70
|
+
@content_length = nil
|
71
|
+
@content_type = nil
|
72
|
+
@gateway_interface = "CGI/1.1"
|
73
|
+
@path_info = nil
|
74
|
+
@path_translated = nil
|
75
|
+
@query_string = nil
|
76
|
+
@remote_addr = nil
|
77
|
+
@remote_host = nil
|
78
|
+
@remote_ident = nil
|
79
|
+
@remote_user = nil
|
80
|
+
@request_method = nil
|
81
|
+
@script_name = nil
|
82
|
+
@server_name = nil
|
83
|
+
@server_port = nil
|
84
|
+
@server_protocol = "HTTP/1.1"
|
85
|
+
@server_software = "PIONE/%s" % Pione::VERSION
|
86
|
+
@body = nil
|
87
|
+
@http_header = Hash.new
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create environment variables.
|
91
|
+
# @return [Hash]
|
92
|
+
# environment variables
|
93
|
+
def create_env
|
94
|
+
env = Hash.new
|
95
|
+
|
96
|
+
# store CGI meta-variables
|
97
|
+
env["AUTH_TYPE"] = @auth_type if @auth_type
|
98
|
+
env["CONTENT_LENGTH"] = @content_length if @content_length
|
99
|
+
env["CONTENT_TYPE"] = @content_type if @content_type
|
100
|
+
env["GATEWAY_INTERFACE"] = @gateway_interface
|
101
|
+
env["PATH_INFO"] = @path_info
|
102
|
+
env["PATH_TRANSLATED"] = @path_translated
|
103
|
+
env["QUERY_STRING"] = @query_string
|
104
|
+
env["REMOTE_ADDR"] = @remote_addr
|
105
|
+
env["REMOTE_HOST"] = @remote_host
|
106
|
+
env["REMOTE_IDENT"] = @remote_ident if @remote_ident
|
107
|
+
env["REMOTE_USER"] = @remote_user if @remote_user
|
108
|
+
env["REQUEST_METHOD"] = @request_method.to_s
|
109
|
+
env["SCRIPT_NAME"] = @script_name
|
110
|
+
env["SERVER_NAME"] = @server_name
|
111
|
+
env["SERVER_PORT"] = @server_port
|
112
|
+
env["SERVER_PROTOCOL"] = @server_protocol
|
113
|
+
env["SERVER_SOFTWARE"] = @server_software
|
114
|
+
|
115
|
+
# store HTTP specific variables
|
116
|
+
@http_header.each do |key, val|
|
117
|
+
env["HTTP_%s" % key] = val
|
118
|
+
end
|
119
|
+
|
120
|
+
return env
|
121
|
+
end
|
122
|
+
|
123
|
+
def create_arguments
|
124
|
+
unless @query_string.include?("=")
|
125
|
+
return @query_string.split("+").map do |arg|
|
126
|
+
begin
|
127
|
+
CGIUtils.decode(arg)
|
128
|
+
rescue
|
129
|
+
raise CGIError.failed_to_decode(@query_string)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
return []
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# CGIExecutor is a execution helper for CGI programs.
|
139
|
+
class CGIExecutor
|
140
|
+
# @param [Pathname] cgi_path
|
141
|
+
# path of the CGI program
|
142
|
+
# @param [CGIInfo] cgi_info
|
143
|
+
# various informations for CGI program
|
144
|
+
def initialize(cgi_path, cgi_info, chdir, timeout)
|
145
|
+
@cgi_path = cgi_path
|
146
|
+
@cgi_info = cgi_info
|
147
|
+
@chdir = chdir
|
148
|
+
@timeout = timeout
|
149
|
+
@umask = 077
|
150
|
+
@cgi_stdin = Temppath.create
|
151
|
+
@cgi_stdout = Temppath.create
|
152
|
+
@pid = nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# Execute the CGI program.
|
156
|
+
def exec
|
157
|
+
unless @cgi_path.exist?
|
158
|
+
raise CGIError.not_exist(@cgi_path)
|
159
|
+
end
|
160
|
+
|
161
|
+
env = @cgi_info.create_env
|
162
|
+
options = create_options
|
163
|
+
args = @cgi_info.create_arguments
|
164
|
+
|
165
|
+
Timeout.timeout(@timeout) do
|
166
|
+
@pid = Kernel.spawn(env, @cgi_path.to_s, *args, options)
|
167
|
+
Process.waitpid(@pid)
|
168
|
+
if @cgi_stdout.exist?
|
169
|
+
return analyze_response(Location[@cgi_stdout].read)
|
170
|
+
else
|
171
|
+
raise CGIError.response_not_found
|
172
|
+
end
|
173
|
+
end
|
174
|
+
rescue Timeout::Error
|
175
|
+
if @pid
|
176
|
+
begin
|
177
|
+
Process.kill(15, @pid)
|
178
|
+
rescue
|
179
|
+
ensure
|
180
|
+
CGIError.timeouted
|
181
|
+
end
|
182
|
+
end
|
183
|
+
rescue Errno::EACCES => e
|
184
|
+
CGIError.cannot_execute_cgi(@cgi_path)
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def nph?
|
190
|
+
Pathname.new(@cgi_path).basename.to_s.start_with?("nph-")
|
191
|
+
end
|
192
|
+
|
193
|
+
def create_options
|
194
|
+
options = Hash.new
|
195
|
+
options[:chdir] = @chdir.path.to_s
|
196
|
+
options[:umask] = @umask
|
197
|
+
if @cgi_info.body
|
198
|
+
Location[@cgi_stdin].write(@cgi_info.body)
|
199
|
+
options[:in] = @cgi_stdin.to_s
|
200
|
+
end
|
201
|
+
options[:out] = @cgi_stdout.to_s
|
202
|
+
return options
|
203
|
+
end
|
204
|
+
|
205
|
+
def analyze_response(stdout)
|
206
|
+
cgi_response = CGIResponse.new
|
207
|
+
|
208
|
+
if nph?
|
209
|
+
cgi_response.nph = true
|
210
|
+
cgi_response.body = stdout
|
211
|
+
else
|
212
|
+
cgi_response.nph = false
|
213
|
+
|
214
|
+
# parse headers
|
215
|
+
headers, body = stdout.split(/\r\n\r\n|\r\r|\n\n/, 2)
|
216
|
+
header = headers.split(/\r\n|\r|\n/).each_with_object(Hash.new) do |line, table|
|
217
|
+
name, value = line.split(/:[\s\t]*/, 2)
|
218
|
+
if name.nil? or name.size == 0 or /\s/.match(name) or value.nil?
|
219
|
+
raise CGIError.invalid_response_header(line)
|
220
|
+
else
|
221
|
+
table[name.downcase] = value
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# content-type
|
226
|
+
if header.has_key?("content-type")
|
227
|
+
cgi_response.content_type = header["content-type"]
|
228
|
+
else
|
229
|
+
raise CGIError.content_type_not_found
|
230
|
+
end
|
231
|
+
|
232
|
+
# location
|
233
|
+
if header["location"]
|
234
|
+
begin
|
235
|
+
uri = URI.parse(header["location"])
|
236
|
+
cgi_response.location = header["location"]
|
237
|
+
rescue
|
238
|
+
raise CGIError.invalid_location(header["location"])
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# status
|
243
|
+
if header["status"]
|
244
|
+
code, reason_phrase = status.split(/\s+/, 2)
|
245
|
+
if /\d\d\d/.match(code)
|
246
|
+
cgi_response.status_code = code
|
247
|
+
cgi_response.reason_phrase = reason_phrase
|
248
|
+
else
|
249
|
+
raise CGIError.invalid_status(code)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# body
|
254
|
+
cgi_response.body = body
|
255
|
+
end
|
256
|
+
|
257
|
+
return cgi_response
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class CGIResponse
|
262
|
+
attr_accessor :nph
|
263
|
+
attr_accessor :content_type
|
264
|
+
attr_accessor :location
|
265
|
+
attr_accessor :status_code
|
266
|
+
attr_accessor :reason_phrase
|
267
|
+
attr_accessor :body
|
268
|
+
|
269
|
+
def initialize
|
270
|
+
@nph = false
|
271
|
+
@content_type = nil
|
272
|
+
@location = nil
|
273
|
+
@status_code = 200
|
274
|
+
@reason_phrase = nil
|
275
|
+
@body = nil
|
276
|
+
end
|
277
|
+
|
278
|
+
def nph?
|
279
|
+
@nph
|
280
|
+
end
|
281
|
+
|
282
|
+
def valid?
|
283
|
+
not(@content_type.nil?)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# CGIError is an error class for occuring errors of CGI execution.
|
288
|
+
class CGIError < StandardError
|
289
|
+
def self.not_exist(path)
|
290
|
+
new("CGI program not exist at %s." % path)
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.failed_to_decode(string)
|
294
|
+
new("Failed to decode the string as URL: %s" % string)
|
295
|
+
end
|
296
|
+
|
297
|
+
def self.invalid_response_header(line)
|
298
|
+
new("Inlivad CGI response header has found: \"%s\"" % line)
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.content_type_not_found
|
302
|
+
new("Requisite CGI response header \"Content-Type\" has not found.")
|
303
|
+
end
|
304
|
+
|
305
|
+
def self.invalid_location(value)
|
306
|
+
new("Invalid location has found: \"%s\"" % value)
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.invalid_status(code)
|
310
|
+
new("Invalid status code has found: \"%s\"" % code)
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.response_not_found
|
314
|
+
"No CGI response."
|
315
|
+
end
|
316
|
+
|
317
|
+
def self.cannot_execute_cgi(cgi_path)
|
318
|
+
"Cannot execute the CGI: %s" % cgi_path.to_s
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.timeouted
|
322
|
+
"CGI exectuion has been timeouted."
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|