sws 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/DOC.otl +34 -0
- data/doc/Makefile +13 -0
- data/doc/architecture.dia +0 -0
- data/doc/docbook/architecture.png +0 -0
- data/doc/docbook/concepts.docbook +474 -0
- data/doc/docbook/installation.docbook +57 -0
- data/doc/docbook/introduction.docbook +130 -0
- data/doc/docbook/sws_manual.docbook +35 -0
- data/doc/docbook/todo.docbook +38 -0
- data/doc/docbook/tutorial.docbook +594 -0
- data/examples/README +1 -0
- data/examples/addressbook/CardBrowse/CardBrowse.html +43 -0
- data/examples/addressbook/CardBrowse/CardBrowse.rb +65 -0
- data/examples/addressbook/CardBrowse/CardBrowse.sws +92 -0
- data/examples/addressbook/Login/LoginPage.html +12 -0
- data/examples/addressbook/Login/LoginPage.rb +19 -0
- data/examples/addressbook/Login/LoginPage.sws +15 -0
- data/examples/addressbook/README +1 -0
- data/examples/addressbook/addressbook.rb +70 -0
- data/examples/addressbook/application.yaml +8 -0
- data/examples/addressbook/db.yaml +7 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.html +11 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.rb +21 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.sws +25 -0
- data/examples/component_demo/ComponentDemo.rb +21 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.html +18 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.rb +2 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.sws +22 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.html +10 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.rb +9 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.sws +33 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.html +12 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.rb +21 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.sws +40 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.html +11 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.rb +37 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.sws +47 -0
- data/examples/component_demo/GenericDemo/GenericDemo.html +4 -0
- data/examples/component_demo/GenericDemo/GenericDemo.rb +2 -0
- data/examples/component_demo/GenericDemo/GenericDemo.sws +10 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.html +8 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.rb +20 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.sws +19 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.html +11 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.rb +2 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.sws +14 -0
- data/examples/component_demo/PageWrapper/PageWrapper.html +23 -0
- data/examples/component_demo/PageWrapper/PageWrapper.rb +2 -0
- data/examples/component_demo/PageWrapper/PageWrapper.sws +42 -0
- data/examples/component_demo/README +1 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.html +13 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.rb +19 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.sws +20 -0
- data/examples/component_demo/StringDemo/StringDemo.html +5 -0
- data/examples/component_demo/StringDemo/StringDemo.rb +16 -0
- data/examples/component_demo/StringDemo/StringDemo.sws +14 -0
- data/examples/component_demo/application.yaml +28 -0
- data/examples/component_demo/poweredby.jpg +0 -0
- data/examples/component_demo/style.css +1 -0
- data/examples/movies/Menu/Menu.html +3 -0
- data/examples/movies/Menu/Menu.rb +7 -0
- data/examples/movies/Menu/Menu.sws +7 -0
- data/examples/movies/MovieBrowse/MovieBrowse.html +68 -0
- data/examples/movies/MovieBrowse/MovieBrowse.rb +178 -0
- data/examples/movies/MovieBrowse/MovieBrowse.sws +127 -0
- data/examples/movies/README +1 -0
- data/examples/movies/UserBrowse/UserBrowse.html +50 -0
- data/examples/movies/UserBrowse/UserBrowse.rb +69 -0
- data/examples/movies/UserBrowse/UserBrowse.sws +49 -0
- data/examples/movies/application.yaml +11 -0
- data/examples/movies/da.rb +36 -0
- data/examples/movies/dbmovies.rb +44 -0
- data/examples/movies/frameworks/TestFramework/framework.yaml +4 -0
- data/examples/movies/frameworks/TestFramework/resources/im1.jpg +0 -0
- data/examples/movies/images/pbr1b.jpg +0 -0
- data/examples/movies/movies.rb +28 -0
- data/examples/movies/movies.sds +119 -0
- data/examples/movies/movies.sqlite +0 -0
- data/examples/movies/movies_mysql.sql +28 -0
- data/examples/movies/movies_postgres.sql +27 -0
- data/examples/movies/movies_sqlite.sql +28 -0
- data/lib/sws.rb +89 -0
- data/lib/sws/Core/components/CheckBox/CheckBox.api +5 -0
- data/lib/sws/Core/components/CheckBox/CheckBox.rb +45 -0
- data/lib/sws/Core/components/CheckBoxList/CheckBoxList.api +13 -0
- data/lib/sws/Core/components/CheckBoxList/CheckBoxList.rb +54 -0
- data/lib/sws/Core/components/Conditional/Conditional.api +3 -0
- data/lib/sws/Core/components/Conditional/Conditional.html +1 -0
- data/lib/sws/Core/components/Conditional/Conditional.rb +39 -0
- data/lib/sws/Core/components/Conditional/Conditional.sws +2 -0
- data/lib/sws/Core/components/Content/Content.rb +18 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.html +13 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.rb +18 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.sws +16 -0
- data/lib/sws/Core/components/FileUpload/FileUpload.api +16 -0
- data/lib/sws/Core/components/FileUpload/FileUpload.rb +62 -0
- data/lib/sws/Core/components/Form/Form.api +9 -0
- data/lib/sws/Core/components/Form/Form.html +3 -0
- data/lib/sws/Core/components/Form/Form.rb +55 -0
- data/lib/sws/Core/components/Form/Form.sws +12 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.api +10 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.html +1 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.rb +39 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.sws +12 -0
- data/lib/sws/Core/components/GenericElement/GenericElement.api +10 -0
- data/lib/sws/Core/components/GenericElement/GenericElement.rb +34 -0
- data/lib/sws/Core/components/HiddenField/HiddenField.api +7 -0
- data/lib/sws/Core/components/HiddenField/HiddenField.rb +37 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.api +13 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.html +1 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.rb +102 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.sws +12 -0
- data/lib/sws/Core/components/Image/Image.api +11 -0
- data/lib/sws/Core/components/Image/Image.rb +49 -0
- data/lib/sws/Core/components/ImageButton/ImageButton.api +16 -0
- data/lib/sws/Core/components/ImageButton/ImageButton.rb +76 -0
- data/lib/sws/Core/components/Link/Link.api +11 -0
- data/lib/sws/Core/components/Link/Link.rb +39 -0
- data/lib/sws/Core/components/PasswordField/PasswordField.api +7 -0
- data/lib/sws/Core/components/PasswordField/PasswordField.rb +41 -0
- data/lib/sws/Core/components/RadioButton/RadioButton.api +7 -0
- data/lib/sws/Core/components/RadioButton/RadioButton.rb +44 -0
- data/lib/sws/Core/components/RadioButtonList/RadioButtonList.api +20 -0
- data/lib/sws/Core/components/RadioButtonList/RadioButtonList.rb +76 -0
- data/lib/sws/Core/components/Repetition/Repetition.api +10 -0
- data/lib/sws/Core/components/Repetition/Repetition.html +1 -0
- data/lib/sws/Core/components/Repetition/Repetition.rb +137 -0
- data/lib/sws/Core/components/Repetition/Repetition.sws +2 -0
- data/lib/sws/Core/components/ResetButton/ResetButton.api +5 -0
- data/lib/sws/Core/components/ResetButton/ResetButton.rb +28 -0
- data/lib/sws/Core/components/Select/Select.api +24 -0
- data/lib/sws/Core/components/Select/Select.rb +57 -0
- data/lib/sws/Core/components/String/String.api +5 -0
- data/lib/sws/Core/components/String/String.rb +28 -0
- data/lib/sws/Core/components/SubmitButton/SubmitButton.api +6 -0
- data/lib/sws/Core/components/SubmitButton/SubmitButton.rb +53 -0
- data/lib/sws/Core/components/TextArea/TextArea.api +9 -0
- data/lib/sws/Core/components/TextArea/TextArea.rb +28 -0
- data/lib/sws/Core/components/TextField/TextField.api +9 -0
- data/lib/sws/Core/components/TextField/TextField.rb +46 -0
- data/lib/sws/Core/framework.yaml +25 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.api +5 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.html +58 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.rb +34 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.sws +37 -0
- data/lib/sws/JSComponents/framework.yaml +3 -0
- data/lib/sws/adaptor.rb +334 -0
- data/lib/sws/application.rb +604 -0
- data/lib/sws/component.rb +656 -0
- data/lib/sws/cookie.rb +27 -0
- data/lib/sws/direct_action.rb +38 -0
- data/lib/sws/extensions.rb +49 -0
- data/lib/sws/parsers.rb +374 -0
- data/lib/sws/request.rb +308 -0
- data/lib/sws/response.rb +70 -0
- data/lib/sws/session.rb +195 -0
- data/lib/sws/slot.rb +198 -0
- metadata +263 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
contento:
|
2
|
+
_class: SWS::Content
|
3
|
+
|
4
|
+
menu_id_string:
|
5
|
+
_class: SWS::String
|
6
|
+
value: menu_id
|
7
|
+
|
8
|
+
menu_link:
|
9
|
+
_class: SWS::Hyperlink
|
10
|
+
href: "'#'"
|
11
|
+
other_tag_string: menu_link_js_events
|
12
|
+
|
13
|
+
menu_div:
|
14
|
+
_class: SWS::GenericContainer
|
15
|
+
tag_name: "'div'"
|
16
|
+
id: menu_div_id
|
17
|
+
other_tag_string: "'style=\"visibility: hidden; position:absolute; z-index:1; left:0; top:0\"'"
|
18
|
+
|
19
|
+
menu_table:
|
20
|
+
_class: SWS::GenericContainer
|
21
|
+
tag_name: "'table'"
|
22
|
+
class: menu_class
|
23
|
+
|
24
|
+
actions_repetition:
|
25
|
+
_class: SWS::Repetition
|
26
|
+
list: actions
|
27
|
+
item: action_cursor
|
28
|
+
|
29
|
+
action_link:
|
30
|
+
_class: SWS::Hyperlink
|
31
|
+
action: action_for_cursor
|
32
|
+
other_tag_string: "'onmousedown=\"document.onmousedown=null;\"'"
|
33
|
+
|
34
|
+
action_string:
|
35
|
+
_class: SWS::String
|
36
|
+
value: name_for_cursor
|
37
|
+
|
data/lib/sws/adaptor.rb
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module SWS
|
6
|
+
|
7
|
+
|
8
|
+
# Adaptor is a part of application that listens for requests. Currently there
|
9
|
+
# are 3 available adaptors - Standalone, FastCGI and CGI (the last one is
|
10
|
+
# experimental). You can write your own adaptor - it should just inherit from
|
11
|
+
# Generic adaptor and implement run and each_request methods.
|
12
|
+
module Adaptor
|
13
|
+
|
14
|
+
|
15
|
+
# Base class for all adaptors
|
16
|
+
class Generic
|
17
|
+
|
18
|
+
# Header translation hash - defines how the env variables are named in request.headers
|
19
|
+
# TODO: add missing headers
|
20
|
+
HEADER_TRANSLATION = {
|
21
|
+
"HTTP_USER_AGENT" => "user-agent",
|
22
|
+
"HTTP_HOST" => "host",
|
23
|
+
"HTTP_COOKIE" => "cookie",
|
24
|
+
"CONTENT_LENGTH" => "content-length",
|
25
|
+
"CONTENT_TYPE" => "content-type",
|
26
|
+
"REMOTE_ADDR" => "remote-address"
|
27
|
+
}
|
28
|
+
|
29
|
+
# Base path of the adaptor
|
30
|
+
attr_reader :base_path
|
31
|
+
|
32
|
+
def initialize ()
|
33
|
+
raise "Adaptor::Generic cannot be instantiated - override and don't call super"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Start the adaptor. Custom adaptors should override this method and
|
38
|
+
# shouldn't call super
|
39
|
+
def run ()
|
40
|
+
raise "Override and do not call super for Adaptor.run"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Main adaptor method - invokes block for each received request. Custom
|
45
|
+
# adaptors should override this method and shouldn't call super
|
46
|
+
def each_request ()
|
47
|
+
raise "Override and do not call super for Adaptor.each_request"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Adaptor listening on a TCPport
|
54
|
+
class Standalone < Generic
|
55
|
+
|
56
|
+
attr_accessor :port
|
57
|
+
|
58
|
+
# Creates new adaptor listening on a given port
|
59
|
+
def initialize ( port = 1234 )
|
60
|
+
|
61
|
+
@port = port
|
62
|
+
#Always "/" for this class, as the application listens on its own port
|
63
|
+
@base_path = "/"
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Start listening for request
|
69
|
+
def run ()
|
70
|
+
@server = TCPServer.new( @port )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Main adaptor method - invokes block for each received request
|
75
|
+
def each_request ()
|
76
|
+
|
77
|
+
while ( socket = @server.accept() )
|
78
|
+
|
79
|
+
request = create_request( socket )
|
80
|
+
response = yield( request )
|
81
|
+
begin
|
82
|
+
socket.print( "#{response.http_version} #{response.status}\r\n" )
|
83
|
+
socket.print( response.to_s )
|
84
|
+
socket.close()
|
85
|
+
rescue => exception
|
86
|
+
$log_sws_request.warn( "Exception #{exception.class} when writing response: #{exception.message}" )
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Gets the request data from socket and creates the Request object
|
97
|
+
def create_request ( socket )
|
98
|
+
|
99
|
+
lines = Array.new()
|
100
|
+
request_method,whole_path,http_version = socket.gets().split( /\s+/ )
|
101
|
+
$log_sws_request.debug( "New request: #{whole_path}" )
|
102
|
+
path,query_string = whole_path.split( /\?/,2 )
|
103
|
+
request = Request.new( request_method, path, query_string, http_version )
|
104
|
+
# the "sub" is needed to get rid of IPv6 prefix
|
105
|
+
# TODO: make it more elegant and handle IPv6 adresses
|
106
|
+
request.headers["remote-address"] = socket.peeraddr[3].sub( /^.*:/,"" )
|
107
|
+
begin
|
108
|
+
line = socket.gets()
|
109
|
+
# Read all header lines
|
110
|
+
lines << line unless ( line == "\r\n" )
|
111
|
+
end while ( line != "\r\n" )
|
112
|
+
lines.each do |line|
|
113
|
+
key,value = line.strip().split( /:\s*/ )
|
114
|
+
key.downcase!()
|
115
|
+
request.headers[key] = value
|
116
|
+
$log_sws_request.debug( "Key: #{key}, value: #{value}\n" )
|
117
|
+
# read request content
|
118
|
+
if ( key == "content-length" )
|
119
|
+
# Headers has been read, so there is only the content itself left
|
120
|
+
request.content = socket.read( value.to_i )
|
121
|
+
end
|
122
|
+
end
|
123
|
+
request.process_headers()
|
124
|
+
request.process_content() if ( request.has_content? )
|
125
|
+
return request
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# FastCGI adaptor. Note that at the moment it will only run properly if you
|
133
|
+
# start only one instace of the application. It SHOULD work ok if you run
|
134
|
+
# multiple instances and use session affinity patch for mod_fastcgi, but I
|
135
|
+
# didn't test that.
|
136
|
+
class FastCGI < Generic
|
137
|
+
|
138
|
+
def initialize
|
139
|
+
|
140
|
+
#The 'require' clause has been placed within initialize to avoid
|
141
|
+
#exception if the FastCGI adaptor is not used and fcgi.rb is missing
|
142
|
+
require 'fcgi'
|
143
|
+
|
144
|
+
#Base_path will be initialized on first request, because it is not known before
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# Run for FastCGI adaptor is in fact performed in each_request, so this
|
149
|
+
# method is empty
|
150
|
+
def run
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# Main adaptor method - invokes block for each received request
|
156
|
+
def each_request ()
|
157
|
+
|
158
|
+
FCGI.each_request do |fcgi_request|
|
159
|
+
#This is a FCGI request, not a SWS one - we need to transform it
|
160
|
+
sws_request = create_request( fcgi_request )
|
161
|
+
|
162
|
+
# Base path can only be deducted after first request
|
163
|
+
unless ( @base_path )
|
164
|
+
@base_path = fcgi_request.env["SCRIPT_NAME"] + "/"
|
165
|
+
end
|
166
|
+
|
167
|
+
response = yield( sws_request )
|
168
|
+
fcgi_request.out.print( "Status: #{response.status}\r\n" )
|
169
|
+
fcgi_request.out.print( response.to_s )
|
170
|
+
fcgi_request.finish
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# Creates SWS::Request object using FastCGI request object
|
177
|
+
def create_request ( fcgi_request )
|
178
|
+
|
179
|
+
request_method = fcgi_request.env["REQUEST_METHOD"]
|
180
|
+
path = fcgi_request.env["PATH_INFO"] || "/"
|
181
|
+
# This may be invalid - but it isn't propably important
|
182
|
+
http_version = fcgi_request.env["SERVER_PROTOCOL"]
|
183
|
+
query_string = fcgi_request.env["QUERY_STRING"]
|
184
|
+
# We wan't the same behaviour as Standalone adaptor has
|
185
|
+
if query_string == "" then
|
186
|
+
query_string = nil
|
187
|
+
end
|
188
|
+
request = Request.new( request_method, path, query_string, http_version )
|
189
|
+
|
190
|
+
HEADER_TRANSLATION.each do |original_header,request_header|
|
191
|
+
request.headers[request_header] = fcgi_request.env[original_header]
|
192
|
+
end
|
193
|
+
|
194
|
+
content_length = fcgi_request.env["CONTENT_LENGTH"]
|
195
|
+
if ( content_length )
|
196
|
+
request.content = fcgi_request.in.read( content_length.to_i )
|
197
|
+
end
|
198
|
+
|
199
|
+
fcgi_request.env.each do |key,value|
|
200
|
+
unless HEADER_TRANSLATION.has_key?( key )
|
201
|
+
request.headers[key.downcase] = value
|
202
|
+
end
|
203
|
+
end
|
204
|
+
request.process_headers()
|
205
|
+
request.process_content() if ( request.has_content? )
|
206
|
+
return request
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
# CGI Adaptor. CGI scripts are not persitant, so I had to use some ugly
|
214
|
+
# hacks (including drb) to make it work transparently for the user (and
|
215
|
+
# developer). It has a serious bug - if the response is bigger than 8kB
|
216
|
+
# it is not returned to the client correctly. The use of this adaptor
|
217
|
+
# is discouraged and it may be removed in future versions.
|
218
|
+
class CGI < Generic
|
219
|
+
|
220
|
+
|
221
|
+
def initialize ( drb_port = 2345 )
|
222
|
+
|
223
|
+
# 'Require' included here, so that DRb is not loaded if this adaptor is
|
224
|
+
# not used
|
225
|
+
require 'drb'
|
226
|
+
@drb_port = drb_port
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# Setup DRb server. Remember the DRBObject is the adaptor itself.
|
232
|
+
def run ()
|
233
|
+
|
234
|
+
request = create_request()
|
235
|
+
|
236
|
+
# Base_path will be set on every request. This doesn't matter though,
|
237
|
+
# because on the remote adaptor it will be set only once
|
238
|
+
@base_path = ENV["SCRIPT_NAME"] + "/"
|
239
|
+
|
240
|
+
begin
|
241
|
+
|
242
|
+
adaptor = DRb::DRbObject.new_with_uri( "druby://localhost:#{@drb_port}" )
|
243
|
+
# Try to use the remote Adaptor object
|
244
|
+
adaptor.handle_request( request )
|
245
|
+
|
246
|
+
rescue DRb::DRbConnError
|
247
|
+
|
248
|
+
# Attempt to retrieve remote Adaptor object failed - need to start the DRb server
|
249
|
+
|
250
|
+
# Probably not necessary
|
251
|
+
trap( "SIGCLD", "IGNORE" )
|
252
|
+
|
253
|
+
# Terminate parent and run child in daemon mode
|
254
|
+
if ( fork() ) then exit(0) end
|
255
|
+
DRb.start_service( "druby://localhost:#{@drb_port}", self )
|
256
|
+
Process.setsid()
|
257
|
+
|
258
|
+
@each_request_mutex = Mutex.new()
|
259
|
+
@each_request_mutex.lock()
|
260
|
+
@handle_request_mutex = Mutex.new()
|
261
|
+
|
262
|
+
handle_request( request )
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
# Main adaptor methods - invokes block for each received request
|
270
|
+
def each_request ()
|
271
|
+
|
272
|
+
loop do
|
273
|
+
|
274
|
+
@each_request_mutex.lock()
|
275
|
+
response = yield( @current_request )
|
276
|
+
# print( "Status: 200 OK\n" )
|
277
|
+
# print( "Content-type: text/html;charset=iso-8859-2\n")
|
278
|
+
# print( "Pragma: no-cache\n" )
|
279
|
+
# print( "Set-Cookie: session=6aa83079b309dd51d2dee0b1250719ca; path=/\n" )
|
280
|
+
# print( "\n")
|
281
|
+
print( "Status: #{response.status}\r\n" )
|
282
|
+
$stderr.puts( "B1" )
|
283
|
+
$stderr.puts( "BEfore Response: #{response.to_s}" )
|
284
|
+
print( response.to_s )
|
285
|
+
print( "\r\n" )
|
286
|
+
$stderr.puts( "After Response" )
|
287
|
+
@handle_request_mutex.unlock()
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
# Creates SWS::Request object from ENV variables
|
295
|
+
def create_request ()
|
296
|
+
|
297
|
+
request_method = ENV["REQUEST_METHOD"]
|
298
|
+
path = ENV["PATH_INFO"] || "/"
|
299
|
+
$stderr.puts( "Path: #{path}" )
|
300
|
+
# This may be invalid - but it isn't propably important
|
301
|
+
http_version = ENV["SERVER_PROTOCOL"]
|
302
|
+
request = Request.new( request_method, path, http_version )
|
303
|
+
|
304
|
+
HEADER_TRANSLATION.each do |env_variable,header_name|
|
305
|
+
request.headers[header_name] = ENV[env_variable]
|
306
|
+
end
|
307
|
+
|
308
|
+
content_length = ENV["CONTENT_LENGTH"]
|
309
|
+
if ( content_length )
|
310
|
+
request.content = $stdin.read( content_length.to_i )
|
311
|
+
end
|
312
|
+
|
313
|
+
request.process_headers()
|
314
|
+
request.process_content() if ( request.has_content? )
|
315
|
+
return request
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
protected
|
321
|
+
def handle_request( request )
|
322
|
+
|
323
|
+
@handle_request_mutex.lock()
|
324
|
+
@current_request = request
|
325
|
+
@each_request_mutex.unlock()
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
@@ -0,0 +1,604 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module SWS
|
4
|
+
|
5
|
+
APP_CONFIG_FILE = "application.yaml"
|
6
|
+
FRAMEWORK_CONFIG_FILE = "framework.yaml"
|
7
|
+
|
8
|
+
CONFIG_COMPONENTS = "components"
|
9
|
+
CONFIG_RESOURCES = "resources"
|
10
|
+
CONFIG_FRAMEWORKS = "frameworks"
|
11
|
+
CONFIG_LOAD_PATHS = "load_paths"
|
12
|
+
|
13
|
+
# Main class of each SWS application. Can be accessed as a singleton object.
|
14
|
+
# Contains global data (session independent) and perform fundamental
|
15
|
+
# request/response/exception handling. Defines request handlers.
|
16
|
+
|
17
|
+
class Application
|
18
|
+
|
19
|
+
|
20
|
+
# Singleton instance of an application
|
21
|
+
@@instance = nil
|
22
|
+
|
23
|
+
# Default encoding for components
|
24
|
+
attr_accessor :default_encoding
|
25
|
+
|
26
|
+
# Adaptor object - determines the type of interface the application uses
|
27
|
+
# (eg. Adaptor, FastCGIAdaptor etc)
|
28
|
+
attr_reader :adaptor
|
29
|
+
|
30
|
+
# Handlers for different types of requests.
|
31
|
+
# There are following types of requests:
|
32
|
+
# - component request: URL /cp/11324134 - request for existing component registered
|
33
|
+
# in @pages hash - the number is component id, 'cp' - request handler key
|
34
|
+
# - page by name request: URL /pn/132443243/pageName - request for component
|
35
|
+
# by its name - 'pn' is request handler key, number is _previous_ component id
|
36
|
+
# (it is necessary so that one cannot access page directly by just writing
|
37
|
+
# eg. /pn/43532322/pageName)
|
38
|
+
# - direct action: URL /da/className/actionName - like direct action in WO.
|
39
|
+
# Again, 'da' is request handler key, actionName is self-descripting
|
40
|
+
# - resource request: URL /rs/frameworkName/resourceName - request for
|
41
|
+
# application resource, eg. image or CSS file.
|
42
|
+
# You can also provide custom request handlers - just add them to
|
43
|
+
# @request_handlers hash.
|
44
|
+
# Key - request handler key (eg. 'pn', 'da'), value - method handling this
|
45
|
+
# type of request.
|
46
|
+
attr_reader :request_handlers
|
47
|
+
|
48
|
+
# Request handler key for component requests
|
49
|
+
attr_reader :component_request_handler_key
|
50
|
+
|
51
|
+
# Request handler key for page by name requests
|
52
|
+
attr_reader :page_name_request_handler_key
|
53
|
+
|
54
|
+
# Request handler key for direct action requests
|
55
|
+
attr_reader :direct_action_request_handler_key
|
56
|
+
|
57
|
+
# Request handler key for resource requests
|
58
|
+
attr_reader :resource_request_handler_key
|
59
|
+
|
60
|
+
# Paths in which all files of components will be searched for
|
61
|
+
attr_reader :component_paths
|
62
|
+
|
63
|
+
# Paths in which resources will be searched for
|
64
|
+
attr_reader :resource_paths
|
65
|
+
|
66
|
+
# Name of the subclass of Component that will be used as default for serving
|
67
|
+
# requests (eg. for default DirectAction or "/"). Defaults to "Main"
|
68
|
+
attr_reader :default_component_class_name
|
69
|
+
|
70
|
+
# Name of the subclass of DirectAction that will be used to serve
|
71
|
+
# DirectAction requests with no class specified. Defaults to DirectAction
|
72
|
+
attr_reader :default_direct_action_class
|
73
|
+
|
74
|
+
# Name of the subclass of Session that will be used for storing sessions.
|
75
|
+
# Defaults to Session
|
76
|
+
attr_reader :session_class
|
77
|
+
|
78
|
+
# Component class used to generate exception page
|
79
|
+
attr_reader :exception_component
|
80
|
+
|
81
|
+
# Creates new singleton Application object
|
82
|
+
def initialize ()
|
83
|
+
|
84
|
+
if ( @@instance != nil )
|
85
|
+
raise "Cannot create Application instance if one exists!"
|
86
|
+
else
|
87
|
+
@@instance = self
|
88
|
+
end
|
89
|
+
|
90
|
+
@request_count = 0
|
91
|
+
@frameworks = Hash.new
|
92
|
+
|
93
|
+
load_config_file()
|
94
|
+
|
95
|
+
setup_request_handlers()
|
96
|
+
setup_component_cache()
|
97
|
+
setup_session_cleaner()
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Reads the configuration from YAML file
|
105
|
+
def load_config_file()
|
106
|
+
|
107
|
+
@config = YAML::load( File.open( APP_CONFIG_FILE ) )
|
108
|
+
|
109
|
+
# Adaptor to use
|
110
|
+
@adaptor = @config["adaptor_class"] ?
|
111
|
+
SWS.get_class( @config["adaptor_class"] ).new : Adaptor::Standalone.new
|
112
|
+
# Class used for session objects
|
113
|
+
@session_class = @config["session_class"] ?
|
114
|
+
SWS.get_class( @config["session_class"] ) : Session
|
115
|
+
# Class used for direct actions (if no class explicitly specified)
|
116
|
+
@default_direct_action_class = @config["default_direct_action_class"] ?
|
117
|
+
SWS.get_class( @config["default_direct_action_class"] ) : DirectAction
|
118
|
+
# Name of component returned for new sessions
|
119
|
+
@default_component_class_name = @config["default_component_class"] || "Main"
|
120
|
+
# Default component encoding
|
121
|
+
@default_encoding = @config["default_encoding"] || "iso-8859-1"
|
122
|
+
# Name of component used to create exception pages
|
123
|
+
@exception_component = @config["exception_component_class"] || "SWS::ExceptionPage"
|
124
|
+
|
125
|
+
# Max number of sessions
|
126
|
+
@max_sessions = @config["max_sessions"] || 100
|
127
|
+
# Man number of top-level components per session
|
128
|
+
@max_components_per_session = @config["max_components_per_session"] || 5
|
129
|
+
# Sessions' inactivity time (in seconds) after which it will be swept out
|
130
|
+
@session_timeout = @config["session_timeout"] || 6*3600
|
131
|
+
# This time (in seconds) specifies how often session cleaning will be performed
|
132
|
+
@session_cleaner_interval = @config["session_cleaner_interval"] || 600
|
133
|
+
# Should the application refuse new sessions or kill Least Recently Used
|
134
|
+
# when limit reached?
|
135
|
+
@refuse_sessions = @config["refuse_sessions"] || false
|
136
|
+
|
137
|
+
@config[CONFIG_FRAMEWORKS].each_pair { |name,location| load_framework( name, location ) }
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# Loads a framework
|
143
|
+
def load_framework( name, location )
|
144
|
+
|
145
|
+
|
146
|
+
config_file = find_framework_config_file( location )
|
147
|
+
unless ( config_file )
|
148
|
+
raise( "Cannot find framework #{name} in location #{location}" )
|
149
|
+
end
|
150
|
+
|
151
|
+
config = YAML::load( File.open( config_file ) )
|
152
|
+
path = File.dirname( config_file )
|
153
|
+
components = config[CONFIG_COMPONENTS] || Hash.new
|
154
|
+
resources = config[CONFIG_RESOURCES] || Hash.new
|
155
|
+
load_paths = config[CONFIG_LOAD_PATHS] || Array.new
|
156
|
+
|
157
|
+
@frameworks[name] = Framework.new( path,components,resources )
|
158
|
+
|
159
|
+
$LOAD_PATH.push( *load_paths )
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Searches for framework config file. Frameworks with location starting with
|
165
|
+
# "SYSTEM" are searched in standard locations ($LOAD_PATH/sws) - otherwise
|
166
|
+
# the location is relative to the application directory
|
167
|
+
# TODO: reconsider what should be added with respect to RubyGems
|
168
|
+
def find_framework_config_file( location )
|
169
|
+
|
170
|
+
if( location =~ /^SYSTEM/ )
|
171
|
+
|
172
|
+
loc = location.sub( /^SYSTEM/,"" )
|
173
|
+
|
174
|
+
$LOAD_PATH.each do |path|
|
175
|
+
config_file = File.join( path, loc, FRAMEWORK_CONFIG_FILE )
|
176
|
+
if ( File.exists?( config_file ) )
|
177
|
+
return config_file
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
else
|
182
|
+
|
183
|
+
config_file = File.join( location, FRAMEWORK_CONFIG_FILE )
|
184
|
+
if ( File.exists?( config_file ) )
|
185
|
+
return config_file
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
return nil
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
# Sets the request handlers up
|
195
|
+
def setup_request_handlers ()
|
196
|
+
|
197
|
+
#default request handlers' keys
|
198
|
+
@component_request_handler_key = "cp"
|
199
|
+
@page_name_request_handler_key = "pn"
|
200
|
+
@direct_action_request_handler_key = "da"
|
201
|
+
@resource_request_handler_key = "res"
|
202
|
+
|
203
|
+
#register default request handlers
|
204
|
+
@request_handlers = Hash.new()
|
205
|
+
@request_handlers [@component_request_handler_key] = :handle_component_request
|
206
|
+
@request_handlers [@page_name_request_handler_key] = :handle_page_name_request
|
207
|
+
@request_handlers [@direct_action_request_handler_key] = :handle_direct_action_request
|
208
|
+
@request_handlers [@resource_request_handler_key] = :handle_resource_request
|
209
|
+
#all other defaults to component requests
|
210
|
+
@request_handlers.default = :handle_component_request
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def setup_component_cache
|
216
|
+
ComponentCache.max_components_per_session = @max_components_per_session
|
217
|
+
end
|
218
|
+
|
219
|
+
def setup_session_cleaner
|
220
|
+
|
221
|
+
@session_cleaner_mutex = Mutex.new
|
222
|
+
@session_cleaner = SessionCleaner.new( @session_class.sessions, @session_cleaner_mutex, @session_timeout, @session_cleaner_interval )
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
public
|
228
|
+
|
229
|
+
# Returns the singleton instance of the application
|
230
|
+
def Application.instance ()
|
231
|
+
return @@instance
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# Starts the application (starts the adaptor and goes into #request_loop)
|
236
|
+
def run ()
|
237
|
+
|
238
|
+
@adaptor.run()
|
239
|
+
request_loop()
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# Main request loop of the application. Reads Request objects from the
|
245
|
+
# adaptor and passes them to #handle_request method
|
246
|
+
def request_loop ()
|
247
|
+
|
248
|
+
@adaptor.each_request do |request|
|
249
|
+
|
250
|
+
@request_count += 1
|
251
|
+
|
252
|
+
# The response has to be returned from the block - handle request returns one
|
253
|
+
response = nil
|
254
|
+
@session_cleaner_mutex.synchronize {
|
255
|
+
response = handle_request( request )
|
256
|
+
}
|
257
|
+
response
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
# Exception handling method. If you want to customize error handling, just
|
265
|
+
# override this one.
|
266
|
+
def handle_exception ( request,exception )
|
267
|
+
|
268
|
+
$log_sws_request.warn( "Exception in application loop: #{exception}" )
|
269
|
+
$log_sws_request.warn( "Backtrace: #{exception.backtrace.join("\n")}" )
|
270
|
+
|
271
|
+
begin
|
272
|
+
|
273
|
+
exception_page = Component.create( @exception_component, request )
|
274
|
+
exception_page.exception = exception
|
275
|
+
component,response = exception_page.process_request( request )
|
276
|
+
|
277
|
+
rescue Exception => exception #just in case there is an error in SWS itself
|
278
|
+
|
279
|
+
$log_sws_component.warn( "Exception in exception component #{@exception_component}: #{exception}" )
|
280
|
+
$log_sws_component.warn( "Backtrace: #{exception.backtrace.join("\n")}" )
|
281
|
+
|
282
|
+
response = Response.new( request,"500" )
|
283
|
+
response.cookies << request.session.to_cookie
|
284
|
+
response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
|
285
|
+
response.headers["Pragma"] = "no-cache"
|
286
|
+
response.headers["Expires"] = "0"
|
287
|
+
response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"
|
288
|
+
|
289
|
+
response << "<HTML><BODY><H1>Exception raised!</H1>\n"
|
290
|
+
response << "<H3>#{exception.class}:#{exception}</H3>\n"
|
291
|
+
response << "Backtrace: <BR> #{exception.backtrace.join("<BR>\n")}"
|
292
|
+
response << "</BODY></HTML>\n"
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
return response
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
def handle_too_far_backtrack ( request )
|
302
|
+
|
303
|
+
# TODO: not sure about the status
|
304
|
+
response = Response.new( request,"200 SWS" )
|
305
|
+
response.cookies << request.session.to_cookie
|
306
|
+
response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
|
307
|
+
response.headers["Pragma"] = "no-cache"
|
308
|
+
response.headers["Expires"] = "0"
|
309
|
+
response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"
|
310
|
+
|
311
|
+
# TODO: allow the user to enter the application again
|
312
|
+
response << "<HTML><BODY><H1>You backtracked too far</H1>\n"
|
313
|
+
response << "</BODY></HTML>\n"
|
314
|
+
return nil,response
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
# Called when new session is to be refused. Default implementation return a
|
319
|
+
# simplistic info page. Note that this method probably shouldn't return a
|
320
|
+
# component, as a component may require session to work properly.
|
321
|
+
def handle_refuse_session ( request )
|
322
|
+
|
323
|
+
response = Response.new( request ,"503" )
|
324
|
+
response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
|
325
|
+
response.headers["Pragma"] = "no-cache"
|
326
|
+
response.headers["Expires"] = "0"
|
327
|
+
response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"
|
328
|
+
|
329
|
+
response << "<HTML><BODY><H1>Session refused!</H1>\n"
|
330
|
+
response << "<H3>Session refused due to application overload. Please try again later.</H3>"
|
331
|
+
response << "</BODY></HTML>\n"
|
332
|
+
|
333
|
+
return response
|
334
|
+
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
# Takes the request and calls one of request_handlers basing on request
|
339
|
+
# handler key contained in URL. Also performs session retrieval/creation.
|
340
|
+
def handle_request ( request )
|
341
|
+
|
342
|
+
# hack for requests for favicon if it doesn't exist
|
343
|
+
# TODO: it blocks ALL favicon.ico requests, but should block only if
|
344
|
+
# favicon.ico is not provided
|
345
|
+
# TODO: or we should omit this issue after implementation of
|
346
|
+
# handling more than one current component
|
347
|
+
if ( request.query_string =~ /favicon.ico$/ || request.path =~ /favicon.ico$/ )
|
348
|
+
return Response.new( request, "404 Not Found" )
|
349
|
+
end
|
350
|
+
|
351
|
+
session = request.session
|
352
|
+
if ( session == nil )
|
353
|
+
|
354
|
+
if ( @session_class.sessions.size < @max_sessions )
|
355
|
+
|
356
|
+
session = @session_class.new
|
357
|
+
request.session = session
|
358
|
+
|
359
|
+
else # Too many sessions
|
360
|
+
|
361
|
+
if ( @refuse_sessions )
|
362
|
+
return handle_refuse_session( request )
|
363
|
+
else
|
364
|
+
# Delete Least Recently Used session
|
365
|
+
session_to_delete = @session_class.sessions.values.inject do |session1,session2|
|
366
|
+
session1.last_access_time < session2.last_access_time ? session1 : session2
|
367
|
+
end
|
368
|
+
|
369
|
+
@session_class.delete_session( session_to_delete )
|
370
|
+
session = @session_class.new
|
371
|
+
request.session = session
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
session.last_access_time = Time.now
|
379
|
+
|
380
|
+
#strip leading /'s
|
381
|
+
path = request.path.sub( /^\/+/,"" )
|
382
|
+
|
383
|
+
#path =~ ^/key/url_content$
|
384
|
+
if ( md = /^([^\/]*)\/+(.*)$/.match( path ) )
|
385
|
+
|
386
|
+
request_handler_key = md[1]
|
387
|
+
#TODO: rename it: its just the url without request handler key
|
388
|
+
#I assume the query string is already stripped
|
389
|
+
url_content = md[2]
|
390
|
+
request_handler = method( @request_handlers[request_handler_key] )
|
391
|
+
else
|
392
|
+
#call default request handler
|
393
|
+
request_handler = method( @request_handlers[path] )
|
394
|
+
url_content = path
|
395
|
+
end
|
396
|
+
|
397
|
+
begin
|
398
|
+
|
399
|
+
component,response = request_handler.call( request,url_content )
|
400
|
+
# The resource request handler does not return component
|
401
|
+
|
402
|
+
# That means that we only want to send response without any component.
|
403
|
+
# Variable component is used to return response. Look at
|
404
|
+
# SWS::Component#process_request for more.
|
405
|
+
if( response == nil )
|
406
|
+
return component
|
407
|
+
end
|
408
|
+
|
409
|
+
if ( component )
|
410
|
+
request.session.add_to_cache( component )
|
411
|
+
end
|
412
|
+
return response
|
413
|
+
|
414
|
+
rescue Exception => exception
|
415
|
+
|
416
|
+
return handle_exception( request,exception )
|
417
|
+
|
418
|
+
end
|
419
|
+
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
# Handles component requests
|
425
|
+
def handle_component_request ( request,url_content )
|
426
|
+
|
427
|
+
# check if url_content =~ /^component_data(/action)
|
428
|
+
if ( md = /^([^\/]*)\/+(.*)$/.match( url_content ) )
|
429
|
+
|
430
|
+
component_data = md[1]
|
431
|
+
action_object_id = md[2].to_i
|
432
|
+
|
433
|
+
else #no "/" after component_id
|
434
|
+
|
435
|
+
component_data = url_content
|
436
|
+
|
437
|
+
end
|
438
|
+
|
439
|
+
component_id, request_number = component_data.split( /\./ ).collect { |el| el.to_i }
|
440
|
+
|
441
|
+
$log_sws_component.debug( "Request number: #{request_number}" )
|
442
|
+
$log_sws_component.debug( "Got component id: #{component_id}" )
|
443
|
+
component = request.session.get_from_cache( component_id )
|
444
|
+
$log_sws_component.debug( "Retrieved component id: #{component.object_id}" )
|
445
|
+
unless ( component && request_number )
|
446
|
+
if request.session.old?( component_id ) # Backtracked too far
|
447
|
+
return handle_too_far_backtrack( request )
|
448
|
+
else # new session or random URL - redirect to default component
|
449
|
+
# We don't care about any parameters
|
450
|
+
request.erase_content()
|
451
|
+
component = Component.create( @default_component_class_name,request )
|
452
|
+
request_number = nil
|
453
|
+
end
|
454
|
+
else
|
455
|
+
|
456
|
+
# TODO: make it possible to present a custom error page on backtrack (it
|
457
|
+
# would require holding ids of all old components in session)
|
458
|
+
if ( action_object_id )
|
459
|
+
#action_object_id contains id of object, whose action should be performed
|
460
|
+
#right now it can only be a Hyperlink or a Form
|
461
|
+
action_object = component.action_components[ action_object_id ]
|
462
|
+
if ( action_object )
|
463
|
+
# We don't call the action immediately - only mark it as enabled and
|
464
|
+
# it will be called during call_action phase of request-response loop
|
465
|
+
action_object.enable_action()
|
466
|
+
else
|
467
|
+
component = Component.create( @default_component_class_name,request )
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
$log_sws_component.debug( "Component id: #{component.object_id}, request_number #{request_number}" )
|
473
|
+
return component.process_request( request, request_number )
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
|
478
|
+
# Handles page name requests
|
479
|
+
def handle_page_name_request ( request,url_content )
|
480
|
+
|
481
|
+
if ( md = /^(-?\d+)\/(.+)/.match( url_content ) )
|
482
|
+
|
483
|
+
previous_component = request.session.get_from_cache( md[1].to_i )
|
484
|
+
unless ( previous_component )
|
485
|
+
|
486
|
+
request.erase_content()
|
487
|
+
component = Component.create( @default_component_class_name,request )
|
488
|
+
|
489
|
+
return component.process_request( request )
|
490
|
+
|
491
|
+
else
|
492
|
+
|
493
|
+
next_component_name = md[2]
|
494
|
+
component = Component.create( next_component_name, request, next_component_name)
|
495
|
+
|
496
|
+
return component.process_request( request )
|
497
|
+
|
498
|
+
end
|
499
|
+
|
500
|
+
else
|
501
|
+
raise( "Malformed url for pn type request" )
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
# Handles direct action requests
|
508
|
+
def handle_direct_action_request ( request,url_content )
|
509
|
+
|
510
|
+
class_name, method_name = url_content.split( /\/+/,2 )
|
511
|
+
if ( method_name )
|
512
|
+
klass = SWS.get_class( class_name + "Action" )
|
513
|
+
else
|
514
|
+
klass = @default_direct_action_class
|
515
|
+
method_name = class_name
|
516
|
+
end
|
517
|
+
unless ( klass && klass.ancestors.include?( DirectAction ) )
|
518
|
+
raise TypeError.new( "Direct action class name for url #{url_content} does not inherit from SWS::DirectAction" )
|
519
|
+
end
|
520
|
+
|
521
|
+
direct_action = klass.new( request )
|
522
|
+
|
523
|
+
new_component, response = direct_action.action_for_name( method_name )
|
524
|
+
|
525
|
+
return [new_component, response]
|
526
|
+
|
527
|
+
end
|
528
|
+
|
529
|
+
|
530
|
+
# Handles resource requests
|
531
|
+
def handle_resource_request ( request,url_content )
|
532
|
+
|
533
|
+
response = Response.new( request )
|
534
|
+
|
535
|
+
framework_name,resource_name = url_content.split( /\/+/,2 )
|
536
|
+
if ( framework_name == 'app' )
|
537
|
+
resource = @config[CONFIG_RESOURCES][resource_name]
|
538
|
+
else
|
539
|
+
framework = @frameworks[framework_name]
|
540
|
+
resource = framework.resources[resource_name]
|
541
|
+
end
|
542
|
+
|
543
|
+
unless ( resource )
|
544
|
+
raise( "Cannot find resource #{resource_name} in framework #{framework}" )
|
545
|
+
end
|
546
|
+
|
547
|
+
response.headers["Content-type"] = resource["mime-type"]
|
548
|
+
if ( framework )
|
549
|
+
path = File.join( framework.path, resource["filename"] )
|
550
|
+
else
|
551
|
+
path = resource["filename"]
|
552
|
+
end
|
553
|
+
unless( File.exists?( path ) )
|
554
|
+
raise( "File #{path} does not exist" )
|
555
|
+
end
|
556
|
+
|
557
|
+
file = File.open( path )
|
558
|
+
response << file.read
|
559
|
+
file.close()
|
560
|
+
|
561
|
+
return nil,response
|
562
|
+
|
563
|
+
end
|
564
|
+
|
565
|
+
|
566
|
+
# Returns ComponentFiles struct for given component name. Usually called
|
567
|
+
# only once per component class, so it is a good place to require its ruby
|
568
|
+
# file.
|
569
|
+
def get_component( component_name )
|
570
|
+
|
571
|
+
# Find component path
|
572
|
+
path = @config[CONFIG_COMPONENTS][component_name]
|
573
|
+
|
574
|
+
unless ( path )
|
575
|
+
framework = @frameworks.values.find { |framework| framework.components[component_name] }
|
576
|
+
if ( framework )
|
577
|
+
path = File.join( framework.path,framework.components[component_name] )
|
578
|
+
else
|
579
|
+
raise( "Component #{component_name} was not found in application or frameworks" )
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
# Retrieve component files. For components in nested namespaces only the
|
584
|
+
# last element of the name is used.
|
585
|
+
file = File.join( path, component_name.split( /::/ ).last )
|
586
|
+
|
587
|
+
if ( FileTest.file?( file+".rb" ) ) then ruby_file = file+".rb" end
|
588
|
+
if ( FileTest.file?( file+".sws" ) ) then sws_file = file+".sws" end
|
589
|
+
if ( FileTest.file?( file+".html" ) ) then html_file = file+".html" end
|
590
|
+
if ( FileTest.file?( file+".api" ) ) then api_file = file+".api" end
|
591
|
+
|
592
|
+
if ( ruby_file )
|
593
|
+
require( ruby_file )
|
594
|
+
klass = SWS.get_class( component_name )
|
595
|
+
return ComponentInfo.new( klass, sws_file, html_file, api_file )
|
596
|
+
end
|
597
|
+
|
598
|
+
raise "Cannot find component files for component #{component_name}"
|
599
|
+
|
600
|
+
end
|
601
|
+
|
602
|
+
end
|
603
|
+
|
604
|
+
end
|