iodine 0.1.21 → 0.2.0

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.

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
@@ -1,474 +0,0 @@
1
- module Iodine
2
- module Http
3
- # this class handles Http responses.
4
- #
5
- # 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.
6
- class Response
7
- # Makes sure that the `flash` cookie-jar doesn't have Symbols and Strings overlapping.
8
- class Flash < ::Hash
9
- # overrides the []= method to set the cookie for the response (by encoding it and preparing it to be sent), as well as to save the cookie in the combined cookie jar (unencoded and available).
10
- def []= key, val
11
- if key.is_a?(Symbol) && self.has_key?( key.to_s)
12
- key = key.to_s
13
- elsif self.has_key?( key.to_s.to_sym)
14
- key = key.to_s.to_sym
15
- end
16
- super
17
- end
18
- # overrides th [] method to allow Symbols and Strings to mix and match
19
- def [] key
20
- if key.is_a?(Symbol) && self.has_key?( key.to_s)
21
- key = key.to_s
22
- elsif self.has_key?( key.to_s.to_sym)
23
- key = key.to_s.to_sym
24
- elsif self.has_key? "magic_flash_#{key.to_s}".freeze.to_sym
25
- key = "magic_flash_#{key.to_s}".freeze.to_sym
26
- end
27
- super
28
- end
29
- end
30
-
31
- # the response's status code
32
- attr_accessor :status
33
- # the response's headers
34
- attr_reader :headers
35
- # the flash cookie-jar (single-use cookies, that survive only one request).
36
- attr_reader :flash
37
- # 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.
38
- attr_accessor :body
39
- # the request.
40
- attr_accessor :request
41
- # Logs the number of bytes written.
42
- attr_accessor :bytes_written
43
- # forces the connection to remain alive if this flag is set to `true` (otherwise follows Http and optimization guidelines).
44
- attr_accessor :keep_alive
45
-
46
- # the response object responds to a specific request on a specific io.
47
- # hence, to initialize a response object, a request must be set.
48
- #
49
- # use, at the very least `HTTPResponse.new request`
50
- def initialize request, status = 200, headers = {}, content = nil
51
- @request = request
52
- @status = status
53
- @headers = headers
54
- @body = content || []
55
- @request.cookies.set_response self
56
- @cookies = {}
57
- @bytes_written = 0
58
- @keep_alive = @http_sblocks_count = false
59
- # propegate flash object
60
- @flash = ::Iodine::Http::Response::Flash.new
61
- request.cookies.each do |k,v|
62
- @flash[k] = v if k.to_s.start_with? 'magic_flash_'.freeze
63
- end
64
- end
65
-
66
- # returns the active protocol for the request.
67
- def io
68
- @request[:io]
69
- end
70
-
71
- # returns true if headers were already sent
72
- def headers_sent?
73
- @headers.frozen?
74
- end
75
-
76
- # Creates a streaming block. Once all streaming blocks are done, the response will automatically finish.
77
- #
78
- # This avoids manualy handling {#start_streaming}, {#finish_streaming} and asynchronously tasking.
79
- #
80
- # Every time data is sent the timout is reset. Responses longer than timeout will not be sent (but they will be processed).
81
- #
82
- # Since Iodine is likely to be multi-threading (depending on your settings and architecture), it is important that
83
- # streaming blocks are nested rather than chained. Chained streaming blocks might be executed in parallel and
84
- # suffer frome race conditions that might lead to the response being corrupted.
85
- #
86
- # Accepts a required block. i.e.
87
- #
88
- # response.stream_async {sleep 1; response << "Hello Streaming"}
89
- # # OR, you can nest (but not chain) the streaming calls
90
- # response.stream_async do
91
- # sleep 1
92
- # response << "Hello Streaming"
93
- # response.stream_async do
94
- # sleep 1
95
- # response << "\r\nGoodbye Streaming"
96
- # end
97
- # end
98
- #
99
- # @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.
100
- def stream_async &block
101
- raise "Block required." unless block
102
- start_streaming unless @http_sblocks_count
103
- @http_sblocks_count += 1
104
- @stream_proc ||= Proc.new { |block| raise "IO closed. Streaming failed." if request[:io].io.closed?; block.call; @http_sblocks_count -= 1; finish_streaming }
105
- Iodine.run block, &@stream_proc
106
- end
107
-
108
- # Creates nested streaming blocks for an Array object (an object answering `#shift`). Once all streaming blocks are done, the response will automatically finish.
109
- #
110
- # Since streaming blocks might run in parallel, nesting the streaming blocks is important...
111
- #
112
- # However, manually nesting hundreds of nesting blocks is time consuming and error prone.
113
- #
114
- # {.sream_enum} allows you to stream an enumerable knowing that Plezi will nest the streaming blocks dynamically.
115
- #
116
- # Accepts:
117
- # enum:: an Enumerable or an object that answers to the `to_a` method (the array will be used to stream the )
118
- #
119
- # If an Array is passed to the enumerable, it will be changed and emptied as the streaming progresses.
120
- # So, if preserving the array is important, please create a shallow copy of the array first using the `.dup` method.
121
- #
122
- # i.e.:
123
- #
124
- # data = "Hello world!".chars
125
- # response.stream_enum(data.each_with_index) {|c, i| response << c; sleep i/10.0 }
126
- #
127
- #
128
- # @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.
129
- def stream_array enum, &block
130
- enum = enum.to_a
131
- return if enum.empty?
132
- stream_async do
133
- args = enum.shift
134
- block.call(*args)
135
- stream_array enum, &block
136
- end
137
- end
138
-
139
- # Creates and returns the session storage object.
140
- #
141
- # 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).
142
- #
143
- # 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.
144
- #
145
- # 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.
146
- #
147
- # @return [Hash like storage] creates and returns the session storage object with all the data from a previous connection.
148
- def session
149
- return @session if instance_variable_defined?(:@session) && @session
150
- if @request.ssl?
151
- @@sec_session_token ||= "#{::Iodine::Http.session_token}_enc".freeze
152
- id = @request.cookies[@@sec_session_token.to_sym] || SecureRandom.uuid
153
- set_cookie @@sec_session_token, id, expires: :session, secure: true, http_only: true
154
- else
155
- id = @request.cookies[::Iodine::Http.session_token.to_sym] || SecureRandom.uuid
156
- set_cookie ::Iodine::Http.session_token, id, expires: :session, http_only: true
157
- end
158
- @request[:session] = @session = ::Iodine::Http::SessionManager.get(id)
159
- end
160
-
161
- # Returns the OLD session storage object when the connection was upgraded to SSL.
162
- #
163
- # 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).
164
- # However, while upgrading to the encrypted connection, the non_encrypted session storage is still available for review.
165
- #
166
- # @return [nil, "Hash like storage"] returns the non-encypeted connection's session storage object, if it exists. This method will NOT create a new sesssion if it didn't exist.
167
- def session_old
168
- ::Iodine::Http::SessionManager.get(@request.cookies[::Iodine::Http.session_token.to_sym]) if @request.cookies[::Iodine::Http.session_token.to_sym]
169
- end
170
-
171
- # Returns a writable combined hash of the request's cookies and the response cookie values.
172
- #
173
- # Any cookies writen to this hash (`response.cookies[:name] = value` will be set using default values).
174
- #
175
- # It's also possible to use this combined hash to delete cookies, using: response.cookies[:name] = nil
176
- def cookies
177
- @request.cookies
178
- end
179
-
180
- # Returns the response's encoded cookie hash.
181
- #
182
- # This method allows direct editing of the cookies about to be set.
183
- def raw_cookies
184
- @cookies
185
- end
186
-
187
- # pushes data to the buffer of the response. this is the preferred way to add data to the response.
188
- #
189
- # If the headers were already sent, this will also send the data and hang until the data was sent.
190
- def << str
191
- ( @body ? @body.push(str) : ( (@body = str.dup) && request[:io].stream_response(self) ) ) if str
192
- self
193
- end
194
-
195
- # returns a response header, if set.
196
- def [] header
197
- header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false))
198
- headers[header]
199
- end
200
-
201
- # Sets a response header. response headers should be a **downcase** String (not a symbol or any other object).
202
- #
203
- # this is the prefered to set a header.
204
- #
205
- # 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)
206
- #
207
- # returns the value set for the header.
208
- #
209
- # see HTTP response headers for valid headers and values: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
210
- def []= header, value
211
- raise 'Cannot set headers after the headers had been sent.' if headers_sent?
212
- return (@headers.delete(header) && nil) if header.nil?
213
- header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false))
214
- headers[header] = value
215
- end
216
-
217
-
218
- COOKIE_NAME_REGEXP = /[\x00-\x20\(\)\<\>@,;:\\\"\/\[\]\?\=\{\}\s]/.freeze
219
-
220
- # Sets/deletes cookies when headers are sent.
221
- #
222
- # Accepts:
223
- # name:: the cookie's name
224
- # value:: the cookie's value
225
- # parameters:: a parameters Hash for cookie creation.
226
- #
227
- # Parameters accept any of the following Hash keys and values:
228
- #
229
- # expires:: a Time object with the expiration date. defaults to 10 years in the future.
230
- # max_age:: a Max-Age HTTP cookie string.
231
- # path:: the path from which the cookie is acessible. defaults to '/'.
232
- # domain:: the domain for the cookie (best used to manage subdomains). defaults to the active domain (sub-domain limitations might apply).
233
- # secure:: if set to `true`, the cookie will only be available over secure connections. defaults to false.
234
- # http_only:: if true, the HttpOnly flag will be set (not accessible to javascript). defaults to false.
235
- #
236
- # Setting the request's coockies (`request.cookies[:name] = value`) will automatically call this method with default parameters.
237
- #
238
- def set_cookie name, value, params = {}
239
- raise 'Cannot set cookies after the headers had been sent.' if headers_sent?
240
- if value.is_a?(Hash) && value.has_key?(:value) && params.empty?
241
- params = value
242
- value = params.delete :value
243
- end
244
- name = name.to_s
245
- raise 'Illegal cookie name' if name =~ COOKIE_NAME_REGEXP
246
- if value.nil?
247
- params[:expires] = (Iodine.time - 315360000)
248
- value = 'deleted'.freeze
249
- else
250
- params[:expires] ||= (Iodine.time + 315360000) unless params[:max_age]
251
- end
252
- params[:path] ||= '/'.freeze
253
- value = Iodine::Http::Request.encode_url(value) # this dups the string
254
- if params[:max_age]
255
- value << ('; Max-Age=%s'.freeze % params[:max_age])
256
- else
257
- value << ('; Expires=%s'.freeze % params[:expires].httpdate) if params[:expires].is_a?(::Time)
258
- end
259
- value << "; Path=#{params[:path]}".freeze
260
- value << "; Domain=#{params[:domain]}".freeze if params[:domain]
261
- value << '; Secure'.freeze if params[:secure]
262
- value << '; HttpOnly'.freeze if params[:http_only]
263
- @cookies[name.to_sym] = value
264
- end
265
-
266
- # deletes a cookie (actually calls `set_cookie name, nil`)
267
- def delete_cookie name
268
- set_cookie name, nil
269
- end
270
-
271
- # clears the response object, unless headers were already sent (the response is already on it's way, at least in part).
272
- #
273
- # returns false if the response was already sent.
274
- def clear
275
- return false if @headers.frozen?
276
- @status, @body, @headers, @cookies = 200, [], {}, {}
277
- self
278
- end
279
-
280
- # attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
281
- def finish
282
- request[:io].send_response self
283
- request.delete(:body).tap {|f| f.close unless f.respond_to?(:close) && f.closed? rescue false } if request[:body] && @http_sblocks_count.to_i == 0
284
- end
285
-
286
- # Returns the connection's LOCAL UUID.
287
- def uuid
288
- request[:io].id
289
- end
290
-
291
- # Sets the response to redirect to a different page.
292
- #
293
- # The method accepts:
294
- # url:: a String containing the URL to which the response forwards. `nil` or an empty string will be replaced with: `request.base_url`.
295
- # options:: an options Hash. Any key-value pairs not supported WILL be used as flash cookie name-value pairs.
296
- #
297
- # The option's hash includes the following options:
298
- # permanent:: a `true`/`false` value stating the redirection type. Defaults to `false` (temporary redirection).
299
- # *:: remember, all other key-value pairs WILL be used as flash cookie name-value pairs.
300
- def redirect_to url, options = {}
301
- raise 'Cannot redirect after headers were sent.' if headers_sent?
302
- url = "#{@request.base_url}/".freeze if url.nil? || (url.is_a?(String) && url.empty?)
303
- raise TypeError, 'URL must be a String.' unless url.is_a?(String)
304
- url = url_for(url) unless url.is_a?(String) || url.nil?
305
- # redirect
306
- @status = options.delete(:permanent) ? 301 : 302
307
- self['location'] = url
308
- @flash.update options
309
- true
310
- end
311
-
312
- # response status codes, as defined.
313
- STATUS_CODES = {100=>"Continue".freeze,
314
- 101=>"Switching Protocols".freeze,
315
- 102=>"Processing".freeze,
316
- 200=>"OK".freeze,
317
- 201=>"Created".freeze,
318
- 202=>"Accepted".freeze,
319
- 203=>"Non-Authoritative Information".freeze,
320
- 204=>"No Content".freeze,
321
- 205=>"Reset Content".freeze,
322
- 206=>"Partial Content".freeze,
323
- 207=>"Multi-Status".freeze,
324
- 208=>"Already Reported".freeze,
325
- 226=>"IM Used".freeze,
326
- 300=>"Multiple Choices".freeze,
327
- 301=>"Moved Permanently".freeze,
328
- 302=>"Found".freeze,
329
- 303=>"See Other".freeze,
330
- 304=>"Not Modified".freeze,
331
- 305=>"Use Proxy".freeze,
332
- 306=>"(Unused)".freeze,
333
- 307=>"Temporary Redirect".freeze,
334
- 308=>"Permanent Redirect".freeze,
335
- 400=>"Bad Request".freeze,
336
- 401=>"Unauthorized".freeze,
337
- 402=>"Payment Required".freeze,
338
- 403=>"Forbidden".freeze,
339
- 404=>"Not Found".freeze,
340
- 405=>"Method Not Allowed".freeze,
341
- 406=>"Not Acceptable".freeze,
342
- 407=>"Proxy Authentication Required".freeze,
343
- 408=>"Request Timeout".freeze,
344
- 409=>"Conflict".freeze,
345
- 410=>"Gone".freeze,
346
- 411=>"Length Required".freeze,
347
- 412=>"Precondition Failed".freeze,
348
- 413=>"Payload Too Large".freeze,
349
- 414=>"URI Too Long".freeze,
350
- 415=>"Unsupported Media Type".freeze,
351
- 416=>"Range Not Satisfiable".freeze,
352
- 417=>"Expectation Failed".freeze,
353
- 422=>"Unprocessable Entity".freeze,
354
- 423=>"Locked".freeze,
355
- 424=>"Failed Dependency".freeze,
356
- 426=>"Upgrade Required".freeze,
357
- 428=>"Precondition Required".freeze,
358
- 429=>"Too Many Requests".freeze,
359
- 431=>"Request Header Fields Too Large".freeze,
360
- 500=>"Internal Server Error".freeze,
361
- 501=>"Not Implemented".freeze,
362
- 502=>"Bad Gateway".freeze,
363
- 503=>"Service Unavailable".freeze,
364
- 504=>"Gateway Timeout".freeze,
365
- 505=>"HTTP Version Not Supported".freeze,
366
- 506=>"Variant Also Negotiates".freeze,
367
- 507=>"Insufficient Storage".freeze,
368
- 508=>"Loop Detected".freeze,
369
- 510=>"Not Extended".freeze,
370
- 511=>"Network Authentication Required".freeze
371
- }
372
-
373
- # This will return the Body object as an IO like object, such as StringIO (or File) and set the body to `nil` (seeing as it was extracted from the response).
374
- #
375
- # This method will also attempts to set headers and update the response status in relation to the body, if applicable. Call this BEFORE getting any final data about the response or sending the headers.
376
- def extract_body
377
- body_io = if @body.is_a?(Array)
378
- return (@body = nil) if body.empty?
379
- StringIO.new @body.join
380
- elsif @body.is_a?(String)
381
- return (@body = nil) if body.empty?
382
- StringIO.new @body
383
- elsif @body.nil?
384
- return nil
385
- nil
386
- elsif @body.is_a?(File) || @body.is_a?(Tempfile) || @body.is_a?(StringIO)
387
- @body
388
- elsif @body.respond_to? :each
389
- tmp = String.new
390
- @body.each {|s| tmp << s}
391
- @body.close if @body.respond_to? :close
392
- @body = nil
393
- return nil if tmp.empty?
394
- StringIO.new tmp
395
- end
396
- @body = nil
397
- body_io.rewind
398
-
399
- if !(@headers.frozen?) && @request['range'.freeze] && @request.get? && @status == 200 && @headers['content-length'.freeze].nil?
400
- r = @request['range'.freeze].match(/^bytes=([\d]+)\-([\d]+)?$/i.freeze)
401
- if r
402
- old_size = body_io.size
403
- start_pos = r[1].to_i
404
- end_pos = (r[2] || (old_size - 1)).to_i
405
- read_length = end_pos-start_pos+ 1
406
- @status = 206 unless old_size == read_length
407
- body_io.pos = start_pos
408
- unless end_pos == old_size-1
409
- new_body = body_io.read(read_length)
410
- body_io.close
411
- body_io = StringIO.new new_body
412
- body_io.rewind
413
- end
414
- @headers['content-range'.freeze] = "bytes #{start_pos}-#{end_pos}/#{old_size}"
415
- @headers['accept-ranges'.freeze] ||= 'bytes'.freeze
416
- else
417
- @headers['accept-ranges'.freeze] ||= 'none'.freeze
418
- end
419
- end
420
-
421
- body_io
422
- end
423
-
424
- # This will return an array of cookie settings to be appended to `set-cookie` headers.
425
- def extract_cookies
426
- unless @cookies.frozen?
427
- # remove old flash cookies
428
- @request.cookies.keys.each do |k|
429
- if k.to_s.start_with? 'magic_flash_'.freeze
430
- set_cookie k, nil
431
- flash.delete k
432
- end
433
- end
434
- #set new flash cookies
435
- @flash.each do |k,v|
436
- set_cookie "magic_flash_#{k.to_s}".freeze, v
437
- end
438
- @cookies.freeze
439
- # response.cookies.set_response nil
440
- @flash.freeze
441
- end
442
- arr = []
443
- @cookies.each {|k, v| arr << "#{k.to_s}=#{v.to_s}"}
444
- arr
445
- end
446
-
447
- protected
448
-
449
- # Sets the http streaming flag and sends the responses headers, so that the response could be handled asynchronously.
450
- #
451
- # if this flag is not set, the response will try to automatically finish its job
452
- # (send its data and maybe close the connection).
453
- #
454
- # NOTICE! :: If HTTP streaming is set, you will need to manually call `response.finish_streaming`
455
- # or the connection will not close properly and the client will be left expecting more information.
456
- def start_streaming
457
- raise "Cannot start streaming after headers were sent!" if headers_sent?
458
- @http_sblocks_count ||= 0
459
- request[:io].stream_response self
460
- end
461
-
462
- # Sends the complete response signal for a streaming response.
463
- #
464
- # Careful - sending the completed response signal more than once might case disruption to the HTTP connection.
465
- def finish_streaming
466
- return unless @http_sblocks_count == 0
467
- request[:io].stream_response self, true
468
- request.delete(:body).tap {|f| f.close unless f.respond_to?(:close) && f.closed? rescue false } if request[:body]
469
- end
470
- end
471
- end
472
- end
473
-
474
-