sws 0.4
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.
- 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
|