iodine 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +86 -8
- data/lib/iodine.rb +45 -2
- data/lib/iodine/core.rb +9 -37
- data/lib/iodine/http.rb +135 -0
- data/lib/iodine/http/hpack.rb +543 -0
- data/lib/iodine/http/http1.rb +217 -0
- data/lib/iodine/http/http2.rb +465 -0
- data/lib/iodine/http/rack_support.rb +105 -0
- data/lib/iodine/http/request.rb +413 -0
- data/lib/iodine/http/response.rb +355 -0
- data/lib/iodine/http/session.rb +110 -0
- data/lib/iodine/http/websocket_client.rb +224 -0
- data/lib/iodine/http/websocket_handler.rb +40 -0
- data/lib/iodine/http/websockets.rb +319 -0
- data/lib/iodine/io.rb +71 -28
- data/lib/iodine/protocol.rb +74 -25
- data/lib/iodine/settings.rb +44 -3
- data/lib/iodine/ssl_connector.rb +47 -0
- data/lib/iodine/timers.rb +5 -21
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +3 -0
- data/{bin/http_test → manual tests/core_http_test } +6 -2
- data/bin/echo b/data/manual → tests/echo +0 -0
- data/bin/em playground b/data/manual tests/em → playground +0 -0
- data/manual tests/hello_world +56 -0
- metadata +20 -6
- data/lib/iodine/ssl_protocol.rb +0 -108
@@ -0,0 +1,355 @@
|
|
1
|
+
module Iodine
|
2
|
+
|
3
|
+
class Http < ::Iodine::Protocol
|
4
|
+
|
5
|
+
# this class handles Http responses.
|
6
|
+
#
|
7
|
+
# The response can be sent in stages but should complete within the scope of the connecton's message. Please notice that headers and status cannot be changed once the response started sending data.
|
8
|
+
class Response
|
9
|
+
# the response's status code
|
10
|
+
attr_accessor :status
|
11
|
+
# the response's headers
|
12
|
+
attr_reader :headers
|
13
|
+
# the flash cookie-jar (single-use cookies, that survive only one request).
|
14
|
+
attr_reader :flash
|
15
|
+
# the response's body buffer container (an array). This object is removed once the headers are sent and all write operations hang after that point.
|
16
|
+
attr_accessor :body
|
17
|
+
# the io through which the response will be sent.
|
18
|
+
attr_reader :io
|
19
|
+
# the request.
|
20
|
+
attr_accessor :request
|
21
|
+
# Logs the number of bytes written.
|
22
|
+
attr_accessor :bytes_written
|
23
|
+
# forces the connection to remain alive if this flag is set to `true` (otherwise follows Http and optimization guidelines).
|
24
|
+
attr_accessor :keep_alive
|
25
|
+
|
26
|
+
# the response object responds to a specific request on a specific io.
|
27
|
+
# hence, to initialize a response object, a request must be set.
|
28
|
+
#
|
29
|
+
# use, at the very least `HTTPResponse.new request`
|
30
|
+
def initialize request, status = 200, headers = {}, content = nil
|
31
|
+
@request = request
|
32
|
+
@status = status
|
33
|
+
@headers = headers
|
34
|
+
@body = content || []
|
35
|
+
@request.cookies.set_response self
|
36
|
+
@cookies = {}
|
37
|
+
@io = request.io
|
38
|
+
@bytes_written = 0
|
39
|
+
@keep_alive = @http_sblocks_count = false
|
40
|
+
# propegate flash object
|
41
|
+
@flash = Hash.new do |hs,k|
|
42
|
+
hs["magic_flash_#{k.to_s}".to_sym] if hs.has_key? "magic_flash_#{k.to_s}".to_sym
|
43
|
+
end
|
44
|
+
request.cookies.each do |k,v|
|
45
|
+
@flash[k] = v if k.to_s.start_with? 'magic_flash_'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns true if headers were already sent
|
50
|
+
def headers_sent?
|
51
|
+
@headers.frozen?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates a streaming block. Once all streaming blocks are done, the response will automatically finish.
|
55
|
+
#
|
56
|
+
# This avoids manualy handling {#start_streaming}, {#finish_streaming} and asynchronously tasking.
|
57
|
+
#
|
58
|
+
# Every time data is sent the timout is reset. Responses longer than timeout will not be sent (but they will be processed).
|
59
|
+
#
|
60
|
+
# Since Iodine is likely to be multi-threading (depending on your settings and architecture), it is important that
|
61
|
+
# streaming blocks are nested rather than chained. Chained streaming blocks might be executed in parallel and
|
62
|
+
# suffer frome race conditions that might lead to the response being corrupted.
|
63
|
+
#
|
64
|
+
# Accepts a required block. i.e.
|
65
|
+
#
|
66
|
+
# response.stream_async {sleep 1; response << "Hello Streaming"}
|
67
|
+
# # OR, you can nest (but not chain) the streaming calls
|
68
|
+
# response.stream_async do
|
69
|
+
# sleep 1
|
70
|
+
# response << "Hello Streaming"
|
71
|
+
# response.stream_async do
|
72
|
+
# sleep 1
|
73
|
+
# response << "\r\nGoodbye Streaming"
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# @return [true, Exception] The method returns immidiatly with a value of true unless it is impossible to stream the response (an exception will be raised) or a block wasn't supplied.
|
78
|
+
def stream_async &block
|
79
|
+
raise "Block required." unless block
|
80
|
+
start_streaming unless @http_sblocks_count
|
81
|
+
@http_sblocks_count += 1
|
82
|
+
@stream_proc ||= Proc.new { |block| raise "IO closed. Streaming failed." if io.io.closed?; block.call; @http_sblocks_count -= 1; finish_streaming }
|
83
|
+
Iodine.run block, &@stream_proc
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates nested streaming blocks for an Array or another Enumerable object. Once all streaming blocks are done, the response will automatically finish.
|
87
|
+
#
|
88
|
+
# Since streaming blocks might run in parallel, nesting the streaming blocks is important...
|
89
|
+
#
|
90
|
+
# However, manually nesting hundreds of nesting blocks is time consuming and error prone.
|
91
|
+
#
|
92
|
+
# {.sream_enum} allows you to stream an enumerable knowing that Plezi will nest the streaming blocks dynamically.
|
93
|
+
#
|
94
|
+
# Accepts:
|
95
|
+
# enum:: an Enumerable or an object that answers to the `to_a` method (the array will be used to stream the )
|
96
|
+
#
|
97
|
+
# If an Array is passed to the enumerable, it will be changed and emptied as the streaming progresses.
|
98
|
+
# So, if preserving the array is important, please create a shallow copy of the array first using the `.dup` method.
|
99
|
+
#
|
100
|
+
# i.e.:
|
101
|
+
#
|
102
|
+
# data = "Hello world!".chars
|
103
|
+
# response.stream_enum(data.each_with_index) {|c, i| response << c; sleep i/10.0 }
|
104
|
+
#
|
105
|
+
#
|
106
|
+
# @return [true, Exception] The method returns immidiatly with a value of true unless it is impossible to stream the response (an exception will be raised) or a block wasn't supplied.
|
107
|
+
def stream_array enum, &block
|
108
|
+
enum = enum.to_a
|
109
|
+
return if enum.empty?
|
110
|
+
stream_async do
|
111
|
+
args = enum.shift
|
112
|
+
block.call(*args)
|
113
|
+
stream_array enum, &block
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Creates and returns the session storage object.
|
118
|
+
#
|
119
|
+
# By default and for security reasons, session id's created on a secure connection will NOT be available on a non secure connection (SSL/TLS).
|
120
|
+
#
|
121
|
+
# Since this method renews the session_id's cookie's validity (update's it's times-stump), it must be called for the first time BEFORE the headers are sent.
|
122
|
+
#
|
123
|
+
# After the session object was created using this method call, it should be safe to continue updating the session data even after the headers were sent and this method would act as an accessor for the already existing session object.
|
124
|
+
#
|
125
|
+
# @return [Hash like storage] creates and returns the session storage object with all the data from a previous connection.
|
126
|
+
def session
|
127
|
+
return @session if @session
|
128
|
+
id = request.cookies[::Iodine::Http.session_token.to_sym] || SecureRandom.uuid
|
129
|
+
set_cookie ::Iodine::Http.session_token, id, expires: (Time.now+86_400), secure: @request.ssl?
|
130
|
+
@request[:session] = @session = ::Iodine::Http::SessionManager.get(id)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns a writable combined hash of the request's cookies and the response cookie values.
|
134
|
+
#
|
135
|
+
# Any cookies writen to this hash (`response.cookies[:name] = value` will be set using default values).
|
136
|
+
#
|
137
|
+
# It's also possible to use this combined hash to delete cookies, using: response.cookies[:name] = nil
|
138
|
+
def cookies
|
139
|
+
@request.cookies
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the response's encoded cookie hash.
|
143
|
+
#
|
144
|
+
# This method allows direct editing of the cookies about to be set.
|
145
|
+
def raw_cookies
|
146
|
+
@cookies
|
147
|
+
end
|
148
|
+
|
149
|
+
# pushes data to the buffer of the response. this is the preferred way to add data to the response.
|
150
|
+
#
|
151
|
+
# If the headers were already sent, this will also send the data and hang until the data was sent.
|
152
|
+
def << str
|
153
|
+
( @body ? @body.push(str) : ( (@body = str.dup) && @io.stream_response(self) ) ) if str
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
# returns a response header, if set.
|
158
|
+
def [] header
|
159
|
+
header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false))
|
160
|
+
headers[header]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Sets a response header. response headers should be a **downcase** String (not a symbol or any other object).
|
164
|
+
#
|
165
|
+
# this is the prefered to set a header.
|
166
|
+
#
|
167
|
+
# Be aware that HTTP/2 will treat a header name with an upper-case letter as an Error! (while HTTP/1.1 ignores the letter case)
|
168
|
+
#
|
169
|
+
# returns the value set for the header.
|
170
|
+
#
|
171
|
+
# see HTTP response headers for valid headers and values: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
|
172
|
+
def []= header, value
|
173
|
+
raise 'Cannot set headers after the headers had been sent.' if headers_sent?
|
174
|
+
return (@headers.delete(header) && nil) if header.nil?
|
175
|
+
header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false))
|
176
|
+
headers[header] = value
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
COOKIE_NAME_REGEXP = /[\x00-\x20\(\)\<\>@,;:\\\"\/\[\]\?\=\{\}\s]/
|
181
|
+
|
182
|
+
# Sets/deletes cookies when headers are sent.
|
183
|
+
#
|
184
|
+
# Accepts:
|
185
|
+
# name:: the cookie's name
|
186
|
+
# value:: the cookie's value
|
187
|
+
# parameters:: a parameters Hash for cookie creation.
|
188
|
+
#
|
189
|
+
# Parameters accept any of the following Hash keys and values:
|
190
|
+
#
|
191
|
+
# expires:: a Time object with the expiration date. defaults to 10 years in the future.
|
192
|
+
# max_age:: a Max-Age HTTP cookie string.
|
193
|
+
# path:: the path from which the cookie is acessible. defaults to '/'.
|
194
|
+
# domain:: the domain for the cookie (best used to manage subdomains). defaults to the active domain (sub-domain limitations might apply).
|
195
|
+
# secure:: if set to `true`, the cookie will only be available over secure connections. defaults to false.
|
196
|
+
# http_only:: if true, the HttpOnly flag will be set (not accessible to javascript). defaults to false.
|
197
|
+
#
|
198
|
+
# Setting the request's coockies (`request.cookies[:name] = value`) will automatically call this method with default parameters.
|
199
|
+
#
|
200
|
+
def set_cookie name, value, params = {}
|
201
|
+
raise 'Cannot set cookies after the headers had been sent.' if headers_sent?
|
202
|
+
name = name.to_s
|
203
|
+
raise 'Illegal cookie name' if name =~ COOKIE_NAME_REGEXP
|
204
|
+
params[:expires] = (Time.now - 315360000) unless value
|
205
|
+
value ||= 'deleted'.freeze
|
206
|
+
params[:expires] ||= (Time.now + 315360000) unless params[:max_age]
|
207
|
+
params[:path] ||= '/'.freeze
|
208
|
+
value = Iodine::Http::Request.encode_url(value)
|
209
|
+
if params[:max_age]
|
210
|
+
value << ('; Max-Age=%s' % params[:max_age])
|
211
|
+
else
|
212
|
+
value << ('; Expires=%s' % params[:expires].httpdate)
|
213
|
+
end
|
214
|
+
value << "; Path=#{params[:path]}"
|
215
|
+
value << "; Domain=#{params[:domain]}" if params[:domain]
|
216
|
+
value << '; Secure'.freeze if params[:secure]
|
217
|
+
value << '; HttpOnly'.freeze if params[:http_only]
|
218
|
+
@cookies[name.to_sym] = value
|
219
|
+
end
|
220
|
+
|
221
|
+
# deletes a cookie (actually calls `set_cookie name, nil`)
|
222
|
+
def delete_cookie name
|
223
|
+
set_cookie name, nil
|
224
|
+
end
|
225
|
+
|
226
|
+
# clears the response object, unless headers were already sent (the response is already on it's way, at least in part).
|
227
|
+
#
|
228
|
+
# returns false if the response was already sent.
|
229
|
+
def clear
|
230
|
+
return false if @headers.frozen?
|
231
|
+
@status, @body, @headers, @cookies = 200, [], {}, {}
|
232
|
+
self
|
233
|
+
end
|
234
|
+
|
235
|
+
# attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
|
236
|
+
def finish
|
237
|
+
@io.send_response self
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns the connection's UUID.
|
241
|
+
def uuid
|
242
|
+
io.id
|
243
|
+
end
|
244
|
+
|
245
|
+
# response status codes, as defined.
|
246
|
+
STATUS_CODES = {100=>"Continue".freeze,
|
247
|
+
101=>"Switching Protocols".freeze,
|
248
|
+
102=>"Processing".freeze,
|
249
|
+
200=>"OK".freeze,
|
250
|
+
201=>"Created".freeze,
|
251
|
+
202=>"Accepted".freeze,
|
252
|
+
203=>"Non-Authoritative Information".freeze,
|
253
|
+
204=>"No Content".freeze,
|
254
|
+
205=>"Reset Content".freeze,
|
255
|
+
206=>"Partial Content".freeze,
|
256
|
+
207=>"Multi-Status".freeze,
|
257
|
+
208=>"Already Reported".freeze,
|
258
|
+
226=>"IM Used".freeze,
|
259
|
+
300=>"Multiple Choices".freeze,
|
260
|
+
301=>"Moved Permanently".freeze,
|
261
|
+
302=>"Found".freeze,
|
262
|
+
303=>"See Other".freeze,
|
263
|
+
304=>"Not Modified".freeze,
|
264
|
+
305=>"Use Proxy".freeze,
|
265
|
+
306=>"(Unused)".freeze,
|
266
|
+
307=>"Temporary Redirect".freeze,
|
267
|
+
308=>"Permanent Redirect".freeze,
|
268
|
+
400=>"Bad Request".freeze,
|
269
|
+
401=>"Unauthorized".freeze,
|
270
|
+
402=>"Payment Required".freeze,
|
271
|
+
403=>"Forbidden".freeze,
|
272
|
+
404=>"Not Found".freeze,
|
273
|
+
405=>"Method Not Allowed".freeze,
|
274
|
+
406=>"Not Acceptable".freeze,
|
275
|
+
407=>"Proxy Authentication Required".freeze,
|
276
|
+
408=>"Request Timeout".freeze,
|
277
|
+
409=>"Conflict".freeze,
|
278
|
+
410=>"Gone".freeze,
|
279
|
+
411=>"Length Required".freeze,
|
280
|
+
412=>"Precondition Failed".freeze,
|
281
|
+
413=>"Payload Too Large".freeze,
|
282
|
+
414=>"URI Too Long".freeze,
|
283
|
+
415=>"Unsupported Media Type".freeze,
|
284
|
+
416=>"Range Not Satisfiable".freeze,
|
285
|
+
417=>"Expectation Failed".freeze,
|
286
|
+
422=>"Unprocessable Entity".freeze,
|
287
|
+
423=>"Locked".freeze,
|
288
|
+
424=>"Failed Dependency".freeze,
|
289
|
+
426=>"Upgrade Required".freeze,
|
290
|
+
428=>"Precondition Required".freeze,
|
291
|
+
429=>"Too Many Requests".freeze,
|
292
|
+
431=>"Request Header Fields Too Large".freeze,
|
293
|
+
500=>"Internal Server Error".freeze,
|
294
|
+
501=>"Not Implemented".freeze,
|
295
|
+
502=>"Bad Gateway".freeze,
|
296
|
+
503=>"Service Unavailable".freeze,
|
297
|
+
504=>"Gateway Timeout".freeze,
|
298
|
+
505=>"HTTP Version Not Supported".freeze,
|
299
|
+
506=>"Variant Also Negotiates".freeze,
|
300
|
+
507=>"Insufficient Storage".freeze,
|
301
|
+
508=>"Loop Detected".freeze,
|
302
|
+
510=>"Not Extended".freeze,
|
303
|
+
511=>"Network Authentication Required".freeze
|
304
|
+
}
|
305
|
+
|
306
|
+
# This will return the Body object as a String... And set the body to `nil` (seeing as it was extracted from the response).
|
307
|
+
def extract_body
|
308
|
+
if @body.is_a?(Array)
|
309
|
+
return (@body = nil) if body.empty?
|
310
|
+
@body = @body.join
|
311
|
+
extract_body
|
312
|
+
elsif @body.is_a?(String)
|
313
|
+
return (@body = nil) if body.empty?
|
314
|
+
tmp = @body
|
315
|
+
@body = nil
|
316
|
+
tmp
|
317
|
+
elsif body.nil?
|
318
|
+
nil
|
319
|
+
elsif body.respond_to? :each
|
320
|
+
tmp = ''
|
321
|
+
body.each {|s| tmp << s}
|
322
|
+
body.close if body.respond_to? :close
|
323
|
+
@body = nil
|
324
|
+
return nil if tmp.empty?
|
325
|
+
tmp
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
protected
|
330
|
+
|
331
|
+
# Sets the http streaming flag and sends the responses headers, so that the response could be handled asynchronously.
|
332
|
+
#
|
333
|
+
# if this flag is not set, the response will try to automatically finish its job
|
334
|
+
# (send its data and maybe close the connection).
|
335
|
+
#
|
336
|
+
# NOTICE! :: If HTTP streaming is set, you will need to manually call `response.finish_streaming`
|
337
|
+
# or the connection will not close properly and the client will be left expecting more information.
|
338
|
+
def start_streaming
|
339
|
+
raise "Cannot start streaming after headers were sent!" if headers_sent?
|
340
|
+
@http_sblocks_count ||= 0
|
341
|
+
@io.stream_response self
|
342
|
+
end
|
343
|
+
|
344
|
+
# Sends the complete response signal for a streaming response.
|
345
|
+
#
|
346
|
+
# Careful - sending the completed response signal more than once might case disruption to the HTTP connection.
|
347
|
+
def finish_streaming
|
348
|
+
return unless @http_sblocks_count == 0
|
349
|
+
@io.stream_response self, true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Iodine
|
2
|
+
module Base
|
3
|
+
module MemSessionStorage
|
4
|
+
@mem_storage = {}
|
5
|
+
def self.fetch key
|
6
|
+
@mem_storage[key] ||= {}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
module FileSessionStorage
|
10
|
+
class SessionObject
|
11
|
+
# called by the Plezi framework to initiate a session with the id requested
|
12
|
+
def initialize id
|
13
|
+
@filename = File.join Dir.tmpdir, "iodine_#{Iodine::Http.session_token}_#{id}"
|
14
|
+
@data ||= {}
|
15
|
+
end
|
16
|
+
# Get a key from the session data store.
|
17
|
+
#
|
18
|
+
# Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
|
19
|
+
# If you store two keys that evaluate as the same string, they WILL override each other.
|
20
|
+
def [] key
|
21
|
+
key = key.to_s
|
22
|
+
load
|
23
|
+
@data[key]
|
24
|
+
end
|
25
|
+
alias :fetch :[]
|
26
|
+
|
27
|
+
# Stores a key in the session's data store.
|
28
|
+
#
|
29
|
+
# Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
|
30
|
+
# If you store two keys that evaluate as the same string, they WILL override each other.
|
31
|
+
def []= key, value
|
32
|
+
key = key.to_s
|
33
|
+
load
|
34
|
+
@data[key] = value
|
35
|
+
save
|
36
|
+
value
|
37
|
+
end
|
38
|
+
alias :store :[]=
|
39
|
+
|
40
|
+
# @return [Hash] returns a shallow copy of the current session data as a Hash.
|
41
|
+
def to_h
|
42
|
+
load
|
43
|
+
@data.dup
|
44
|
+
end
|
45
|
+
|
46
|
+
# Removes a key from the session's data store.
|
47
|
+
def delete key
|
48
|
+
load
|
49
|
+
ret = @data.delete key
|
50
|
+
save
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
|
54
|
+
# Clears the session's data.
|
55
|
+
def clear
|
56
|
+
@data.clear
|
57
|
+
save
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
protected
|
61
|
+
def save
|
62
|
+
# save data to tmp-file
|
63
|
+
IO.write @filename, @data.to_yaml
|
64
|
+
end
|
65
|
+
def load
|
66
|
+
@data = YAML.load IO.read(@filename) if File.exists?(@filename)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def self.fetch key
|
70
|
+
SessionObject.new key
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
class Http < Iodine::Protocol
|
75
|
+
module SessionManager
|
76
|
+
module_function
|
77
|
+
# returns a session object
|
78
|
+
def get id
|
79
|
+
storage.fetch(id)
|
80
|
+
end
|
81
|
+
# Sets the session storage system, to allow for different storage systems.
|
82
|
+
#
|
83
|
+
# A Session Storage system must answer only one methods:
|
84
|
+
# fetch(id):: returns a Hash like session object with all the session's data or a fresh session object if the session object did not exist before
|
85
|
+
#
|
86
|
+
# The Session Object should update itself in the storage whenever data is saved to the session Object.
|
87
|
+
# This is important also because websocket 'session' could exist simultaneously with other HTTP requests and the data should be kept updated at all times.
|
88
|
+
# If there are race conditions that apply for multi-threading / multi processing, the Session Object should manage them as well as possible.
|
89
|
+
def storage= session_storage = nil
|
90
|
+
case session_storage
|
91
|
+
when :file, nil
|
92
|
+
@storage = Iodine::Base::FileSessionStorage
|
93
|
+
when :mem
|
94
|
+
@storage = Iodine::Base::MemSessionStorage
|
95
|
+
else
|
96
|
+
@storage = session_storage
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# returns the current session storage system.
|
101
|
+
def storage
|
102
|
+
@storage ||= Iodine::Base::FileSessionStorage
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# A hash like interface for storing request session data.
|
108
|
+
# The store must implement: store(key, value) (aliased as []=);
|
109
|
+
# fetch(key, default = nil) (aliased as []);
|
110
|
+
# delete(key); clear;
|