pione 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|