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.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
data/lib/iodine/http/response.rb
DELETED
@@ -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
|
-
|