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
data/lib/sws/request.rb
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
module SWS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Struct containing data of a file passed as form-data
|
|
7
|
+
FileData = Struct.new(
|
|
8
|
+
:content_disposition,
|
|
9
|
+
:filename,
|
|
10
|
+
:content_type,
|
|
11
|
+
:charset,
|
|
12
|
+
:content_transfer_encoding,
|
|
13
|
+
:contents
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Class representing HTTP request
|
|
17
|
+
class Request
|
|
18
|
+
|
|
19
|
+
# Method of the request (POST,GET)
|
|
20
|
+
attr_reader :request_method
|
|
21
|
+
|
|
22
|
+
# Path the request was made to
|
|
23
|
+
attr_reader :path
|
|
24
|
+
|
|
25
|
+
# Version of the HTTP protocol
|
|
26
|
+
attr_reader :http_version
|
|
27
|
+
|
|
28
|
+
# Query string (as in CGI). May be used freely, as SWS does not use it
|
|
29
|
+
# internally.
|
|
30
|
+
attr_reader :query_string
|
|
31
|
+
|
|
32
|
+
# Hash: key - header name, value - header value
|
|
33
|
+
attr_reader :headers
|
|
34
|
+
|
|
35
|
+
# Form parameters passed to the request (Hash)
|
|
36
|
+
attr_reader :params
|
|
37
|
+
|
|
38
|
+
# Cookies passed to the request (Hash)
|
|
39
|
+
attr_reader :cookies
|
|
40
|
+
|
|
41
|
+
# User agent string
|
|
42
|
+
attr_reader :user_agent
|
|
43
|
+
|
|
44
|
+
# Name of the server host
|
|
45
|
+
attr_reader :host
|
|
46
|
+
|
|
47
|
+
# IP of the remote host
|
|
48
|
+
attr_reader :remote_address
|
|
49
|
+
|
|
50
|
+
# Content of the request
|
|
51
|
+
attr_accessor :content
|
|
52
|
+
|
|
53
|
+
# Session object connected with this request
|
|
54
|
+
attr_writer :session
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Creates new Request object
|
|
58
|
+
def initialize ( request_method, path, query_string, http_version)
|
|
59
|
+
|
|
60
|
+
@request_method = request_method
|
|
61
|
+
@path = path
|
|
62
|
+
@http_version = http_version
|
|
63
|
+
@query_string = query_string
|
|
64
|
+
@headers = Hash.new
|
|
65
|
+
@params = Hash.new
|
|
66
|
+
@cookies = Hash.new
|
|
67
|
+
@content = nil
|
|
68
|
+
@session = nil
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Request true if content is not empty
|
|
74
|
+
def has_content? ()
|
|
75
|
+
return @content != nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Returns string representation of the receiver
|
|
80
|
+
def to_s ()
|
|
81
|
+
|
|
82
|
+
params = ""
|
|
83
|
+
@params.each { |key,value| params << "Key: #{key}, value: #{value}\n" }
|
|
84
|
+
return "Method: #{request_method}, path: #{path}, params:\n #{params}"
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Retrieves session from cookie (or only returns it if already retrieved)
|
|
90
|
+
def session ()
|
|
91
|
+
#TODO: allow session to be stored in url
|
|
92
|
+
|
|
93
|
+
if ( @session == nil )
|
|
94
|
+
session_id = @cookies["session"]
|
|
95
|
+
@session = Session.sessions[session_id]
|
|
96
|
+
end
|
|
97
|
+
return @session
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Erases receiver's content - useful when new session is created on not refreshed HTML
|
|
103
|
+
def erase_content ()
|
|
104
|
+
#TODO: erase cookies and headers too?
|
|
105
|
+
@content = nil
|
|
106
|
+
@params = Hash.new()
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Parses headers hash and generates a bunch of more useful parameters and
|
|
112
|
+
# cookies
|
|
113
|
+
def process_headers ()
|
|
114
|
+
|
|
115
|
+
if ( @headers[ "cookie" ] )
|
|
116
|
+
@headers["cookie"].split( /;\s+/ ).each do |cookie_pair|
|
|
117
|
+
name, value = cookie_pair.split( /=/ )
|
|
118
|
+
@cookies[name] = value.url_decode
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@user_agent = @headers["user-agent"]
|
|
123
|
+
@host = @headers["host"]
|
|
124
|
+
@remote_address = @headers["remote-address"]
|
|
125
|
+
|
|
126
|
+
#TODO: maybe add the rest of headers?
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Parses request content and extracts form parameters
|
|
132
|
+
def process_content ()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
$log_sws_request.debug( "Content: #{@content}" )
|
|
136
|
+
content_type = @headers["content-type"]
|
|
137
|
+
|
|
138
|
+
if ( content_type =~ /application\/x-www-form-urlencoded/i )
|
|
139
|
+
|
|
140
|
+
process_url_encoded_content()
|
|
141
|
+
|
|
142
|
+
elsif ( content_type =~ /multipart\/form-data/i )
|
|
143
|
+
|
|
144
|
+
process_multipart_form_data_content( content_type )
|
|
145
|
+
|
|
146
|
+
else
|
|
147
|
+
|
|
148
|
+
raise( "Unsupported content type #{content_type}" )
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
protected
|
|
156
|
+
|
|
157
|
+
def process_url_encoded_content ()
|
|
158
|
+
|
|
159
|
+
$log_sws_request.debug( "Content type: x-www-form-urlencoded" )
|
|
160
|
+
@content.split( '&' ).each do |param|
|
|
161
|
+
key,value = param.split( '=' )
|
|
162
|
+
unless ( value == nil ) #can be nil eg. in empty textfield
|
|
163
|
+
value.gsub!( /\+/," " )
|
|
164
|
+
value.url_decode!()
|
|
165
|
+
end
|
|
166
|
+
$log_sws_request.debug( "Param: #{key}, value: #{value}" )
|
|
167
|
+
add_param( key,value )
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def process_multipart_form_data_content ( content_type )
|
|
174
|
+
|
|
175
|
+
$log_sws_request.debug( "Content type: multipart/form-data" )
|
|
176
|
+
boundary = content_type.sub( /^.*boundary=/,"" )
|
|
177
|
+
|
|
178
|
+
$log_sws_request.debug( "Boundary: #{boundary}" )
|
|
179
|
+
|
|
180
|
+
# For some unknown reason (RFC, presumably :) all browsers add '--'
|
|
181
|
+
# before the boundary and at the end of content
|
|
182
|
+
params = @content.split( /--#{boundary}-{0,2}\r\n/m )
|
|
183
|
+
params.delete_if { |param| param == "" }
|
|
184
|
+
#TODO: why the statement below was used? It breaks file uploads
|
|
185
|
+
#|| param =~ /^--/m }
|
|
186
|
+
params.each do |param|
|
|
187
|
+
|
|
188
|
+
$log_sws_request.debug( "Param: #{param}" )
|
|
189
|
+
headers,value = param.split( /\r\n\r\n/,2 )
|
|
190
|
+
|
|
191
|
+
$log_sws_request.debug( "headers: #{headers}" )
|
|
192
|
+
if ( headers =~ /^content-disposition: form-data; name="([^"]+)"$/i )
|
|
193
|
+
|
|
194
|
+
key = $1
|
|
195
|
+
$log_sws_request.debug( "Single value, #{key}: #{value}" )
|
|
196
|
+
# should work properly with form-data content
|
|
197
|
+
# currently other types are unsupported
|
|
198
|
+
|
|
199
|
+
# TODO: refactoring for proper coding/decoding
|
|
200
|
+
# value.gsub!( /\+/," " )
|
|
201
|
+
# value.url_decode!()
|
|
202
|
+
|
|
203
|
+
# Don't know why, but in some strings - like
|
|
204
|
+
# "nazwy_projekt�w_do_um�w\r\n" - using /\r\n/ in regexp doesn't strip
|
|
205
|
+
# these chars properly
|
|
206
|
+
value.sub!( /(\x0D\x0A)+$/, "" )
|
|
207
|
+
add_param( key, value )
|
|
208
|
+
|
|
209
|
+
else
|
|
210
|
+
|
|
211
|
+
$log_sws_request.debug( "File value" )
|
|
212
|
+
file_data = FileData.new
|
|
213
|
+
file_data.contents = value
|
|
214
|
+
headers.each_line do |header|
|
|
215
|
+
header.sub!( /\r\n$/,"" )
|
|
216
|
+
case header
|
|
217
|
+
when /^content-disposition/i
|
|
218
|
+
parse_multipart_form_data_content_disposition( file_data,header )
|
|
219
|
+
when /^content-type/i
|
|
220
|
+
parse_multipart_form_data_content_type( file_data,header )
|
|
221
|
+
when /^content-transfer-encoding/i
|
|
222
|
+
parse_multipart_form_data_content_transfer_encoding( file_data,header )
|
|
223
|
+
else
|
|
224
|
+
raise( "Unknown multipart/form-data header: --#{header}--" )
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def process_multipart_mixed_content ( content_type )
|
|
236
|
+
|
|
237
|
+
raise( "Multipart/mixed content-type is not yet supported" )
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def parse_multipart_form_data_content_disposition ( file_data, header )
|
|
243
|
+
|
|
244
|
+
#TODO: avoid exception on malformed header
|
|
245
|
+
file_data.content_disposition, params =
|
|
246
|
+
/^content-disposition: (.*?);\s*(.*)$/i.match( header ).captures
|
|
247
|
+
params.split( /;\s*/ ).each do |param|
|
|
248
|
+
key, value = /^(\w+)="?(.*?)"?$/.match( param ).captures
|
|
249
|
+
case key
|
|
250
|
+
when "name" then @params[value] = file_data
|
|
251
|
+
when "filename" then file_data.filename = value
|
|
252
|
+
else
|
|
253
|
+
raise( "Unknown content-disposition param in multipart/form-data: #{key}" )
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def parse_multipart_form_data_content_type ( file_data, header )
|
|
261
|
+
|
|
262
|
+
md = /^content-type: (.*?);\s*(.*)$/i.match( header )
|
|
263
|
+
if ( md )
|
|
264
|
+
file_data.content_type,params = md.captures
|
|
265
|
+
params.split( /;\s*/ ).each do |param|
|
|
266
|
+
$log_sws_request.debug( "Param: #{param}" )
|
|
267
|
+
key,value = /^(\w+)="?(.*?)"?$/.match( param ).captures
|
|
268
|
+
case key
|
|
269
|
+
when "charset" then file_data.charset = value
|
|
270
|
+
else
|
|
271
|
+
raise( "Unknown param of content-type in multipart/form-data: #{key}" )
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
else
|
|
275
|
+
file_data.content_type = /^content-type: (.*)$/i.match( header ).captures[0]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def parse_multipart_form_data_content_transfer_encoding ( file_data, header )
|
|
282
|
+
|
|
283
|
+
file_data.content_transfer_encoding =
|
|
284
|
+
/^,*:\s*(.*)$/.match( header ).captures[0]
|
|
285
|
+
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def add_param( key, value )
|
|
290
|
+
|
|
291
|
+
if ( @params[key] ) #that means this param has multiple values
|
|
292
|
+
if ( @params[key].instance_of?( Array ) )
|
|
293
|
+
#param already has multiple values assigned,
|
|
294
|
+
#so just add next value to the array
|
|
295
|
+
@params[key] << value
|
|
296
|
+
else #value in params has must be converted to an Array
|
|
297
|
+
values = [@params[key],value]
|
|
298
|
+
@params[key] = values
|
|
299
|
+
end
|
|
300
|
+
else
|
|
301
|
+
@params[key] = value
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
end
|
data/lib/sws/response.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
module SWS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Represents HTML reponse generated by SWS
|
|
7
|
+
class Response
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Hash of HTTP headers
|
|
11
|
+
attr_reader :headers
|
|
12
|
+
|
|
13
|
+
# Array of Cookie objects
|
|
14
|
+
attr_reader :cookies
|
|
15
|
+
|
|
16
|
+
# The version of HTTP protocol
|
|
17
|
+
attr_accessor :http_version
|
|
18
|
+
|
|
19
|
+
# Status of the response
|
|
20
|
+
attr_accessor :status
|
|
21
|
+
|
|
22
|
+
# The Request the receiver is associated with
|
|
23
|
+
attr_reader :request
|
|
24
|
+
|
|
25
|
+
attr_reader :content
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Creates new empty Response object
|
|
29
|
+
def initialize ( request,status = "200 SWS/OK" )
|
|
30
|
+
|
|
31
|
+
@content = ""
|
|
32
|
+
@headers = Hash.new
|
|
33
|
+
@cookies = Array.new
|
|
34
|
+
@http_version = "HTTP/1.1"
|
|
35
|
+
@status = status
|
|
36
|
+
@request = request
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def Response.redirect( request, url )
|
|
41
|
+
|
|
42
|
+
response = Response.new( request, "302 Found" )
|
|
43
|
+
response.headers["Location"] = url
|
|
44
|
+
return response
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Appends a string to the receiver
|
|
49
|
+
def << ( string )
|
|
50
|
+
@content << string
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Returns string representation of the receiver
|
|
55
|
+
def to_s ()
|
|
56
|
+
|
|
57
|
+
data = ""
|
|
58
|
+
@headers.each_pair { |key, value| data << "#{key}: #{value}\r\n" }
|
|
59
|
+
@cookies.each { |cookie| data << "#{cookie}\r\n" }
|
|
60
|
+
# End headers
|
|
61
|
+
data << "\r\n"
|
|
62
|
+
data << @content
|
|
63
|
+
return data
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
data/lib/sws/session.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require "digest/md5"
|
|
4
|
+
|
|
5
|
+
module SWS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# This is a class representing user sessions. Sessions are based on id,
|
|
9
|
+
# which is passed in cookie
|
|
10
|
+
class Session
|
|
11
|
+
|
|
12
|
+
# Hash for created sessions: key - id, value - Session object (can be subclasses of Session!)
|
|
13
|
+
@@sessions = Hash.new
|
|
14
|
+
|
|
15
|
+
# Session id
|
|
16
|
+
attr_reader :sid
|
|
17
|
+
|
|
18
|
+
# Time when the session was last active
|
|
19
|
+
attr_accessor :last_access_time
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Creates new Session object and registers it
|
|
24
|
+
def initialize ()
|
|
25
|
+
|
|
26
|
+
generate_sid()
|
|
27
|
+
#register session
|
|
28
|
+
@@sessions[@sid] = self
|
|
29
|
+
|
|
30
|
+
# Last top-level components created
|
|
31
|
+
@component_cache = ComponentCache.new()
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Generates session id
|
|
37
|
+
def generate_sid ()
|
|
38
|
+
|
|
39
|
+
md5digest = Digest::MD5.new()
|
|
40
|
+
md5digest.update(Time.now.to_f.to_s + rand.to_s + $$.to_s)
|
|
41
|
+
@sid = (md5digest.hexdigest)
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
public
|
|
47
|
+
|
|
48
|
+
# Returns sessions Hash containing all registered sessions
|
|
49
|
+
def Session.sessions ()
|
|
50
|
+
return @@sessions
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Deletes session from registered ones
|
|
55
|
+
def Session.delete_session ( session )
|
|
56
|
+
@@sessions.delete( session.sid )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Returns session id represented as cookie in order to append it to
|
|
61
|
+
# response.
|
|
62
|
+
# TODO: deal with "path" (set it to base app path)
|
|
63
|
+
def to_cookie ()
|
|
64
|
+
return Cookie.new( "session",@sid )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Returns string representation of the receiver
|
|
69
|
+
def to_s ()
|
|
70
|
+
return "#{self.class}, sid: #{@sid}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_to_cache ( component )
|
|
74
|
+
@component_cache.add_component( component )
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_from_cache ( component_id )
|
|
78
|
+
return @component_cache.component( component_id )
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def old? ( component_id )
|
|
82
|
+
return @component_cache.old?( component_id )
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SessionCleaner < Thread
|
|
90
|
+
|
|
91
|
+
# By default cleaning will be performed every 10 minutes and each session
|
|
92
|
+
# inactive for 6 hours will be deleted
|
|
93
|
+
def initialize ( sessions, mutex, session_timeout = 21600, cleanup_interval = 600 )
|
|
94
|
+
|
|
95
|
+
super do |sessions, mutex, session_timeout, cleanup_interval|
|
|
96
|
+
loop do
|
|
97
|
+
mutex.synchronize {
|
|
98
|
+
sessions.delete_if() {
|
|
99
|
+
|key, value| ( Time.now - value.last_access_time ) > session_timeout
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sleep cleanup_interval
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# TODO: not sure if it works entirely correctly, but for now it works good
|
|
114
|
+
# enough
|
|
115
|
+
class ComponentCache
|
|
116
|
+
|
|
117
|
+
def ComponentCache.max_components_per_session= ( max )
|
|
118
|
+
@@max_components_per_session = max
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def initialize
|
|
123
|
+
@components = Array.new
|
|
124
|
+
# Object_ids of components removed from cache. Useful when handling
|
|
125
|
+
# backtracking.
|
|
126
|
+
@old_component_ids = Array.new
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Adds a component to cache.
|
|
131
|
+
# Returns false if component was nil.
|
|
132
|
+
# Size of component cache can be customized in application.yaml
|
|
133
|
+
def add_component ( component )
|
|
134
|
+
|
|
135
|
+
unless @components.include?( component )
|
|
136
|
+
@components << component
|
|
137
|
+
if @components.length > @@max_components_per_session
|
|
138
|
+
# As checking for only one copy of a component is already done when
|
|
139
|
+
# inserting into @components, we don't need to do this here. We only
|
|
140
|
+
# store object_ids to limit memory footprint.
|
|
141
|
+
@old_component_ids << @components.shift.object_id
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def component ( component_id )
|
|
149
|
+
|
|
150
|
+
if( component_id == @components.last.object_id )
|
|
151
|
+
return @components.last
|
|
152
|
+
else
|
|
153
|
+
component = @components.find { |c| c.object_id == component_id }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
unless component
|
|
157
|
+
return nil
|
|
158
|
+
else
|
|
159
|
+
# Old behaviour (for optimalization)
|
|
160
|
+
# Remove all components that are after found component - they are in the
|
|
161
|
+
# "branch" the client shouldn't have access to. Of course they are added
|
|
162
|
+
# to @old_component_ids for the case user backtracks to them.
|
|
163
|
+
#
|
|
164
|
+
# Accidentally, this works correctly even if component ==
|
|
165
|
+
# @components.last (that means there was no backtracking) - that is, it
|
|
166
|
+
# doesn't remove anything.
|
|
167
|
+
#
|
|
168
|
+
#@old_component_ids.concat(
|
|
169
|
+
# @components.slice!( @components.index( component ) + 1 .. -1 ).collect { |com| com.object_id }
|
|
170
|
+
#)
|
|
171
|
+
|
|
172
|
+
# @components.push( @components.delete( component ) )
|
|
173
|
+
# puts @components.each { |c| print "-- ", c.object_id, "\n" }
|
|
174
|
+
# puts component_id
|
|
175
|
+
return component
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def old? ( component_id )
|
|
182
|
+
return @old_component_ids.include?( component_id )
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# This method returns last component in array - this is used
|
|
186
|
+
# when we need direct access to some common component atributes
|
|
187
|
+
# (i.e. request.headers)
|
|
188
|
+
# TODO: maybe it is unnecessary (i mean: there is better way to do this)
|
|
189
|
+
def last
|
|
190
|
+
return @components.last
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
end
|