Class: Iodine::Http::Response
- Inherits:
-
Object
- Object
- Iodine::Http::Response
- Defined in:
- lib/iodine/http/response.rb
Overview
this class handles Http responses.
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.
Constant Summary collapse
- COOKIE_NAME_REGEXP =
/[\x00-\x20\(\)\<\>@,;:\\\"\/\[\]\?\=\{\}\s]/
- STATUS_CODES =
response status codes, as defined.
{100=>"Continue".freeze, 101=>"Switching Protocols".freeze, 102=>"Processing".freeze, 200=>"OK".freeze, 201=>"Created".freeze, 202=>"Accepted".freeze, 203=>"Non-Authoritative Information".freeze, 204=>"No Content".freeze, 205=>"Reset Content".freeze, 206=>"Partial Content".freeze, 207=>"Multi-Status".freeze, 208=>"Already Reported".freeze, 226=>"IM Used".freeze, 300=>"Multiple Choices".freeze, 301=>"Moved Permanently".freeze, 302=>"Found".freeze, 303=>"See Other".freeze, 304=>"Not Modified".freeze, 305=>"Use Proxy".freeze, 306=>"(Unused)".freeze, 307=>"Temporary Redirect".freeze, 308=>"Permanent Redirect".freeze, 400=>"Bad Request".freeze, 401=>"Unauthorized".freeze, 402=>"Payment Required".freeze, 403=>"Forbidden".freeze, 404=>"Not Found".freeze, 405=>"Method Not Allowed".freeze, 406=>"Not Acceptable".freeze, 407=>"Proxy Authentication Required".freeze, 408=>"Request Timeout".freeze, 409=>"Conflict".freeze, 410=>"Gone".freeze, 411=>"Length Required".freeze, 412=>"Precondition Failed".freeze, 413=>"Payload Too Large".freeze, 414=>"URI Too Long".freeze, 415=>"Unsupported Media Type".freeze, 416=>"Range Not Satisfiable".freeze, 417=>"Expectation Failed".freeze, 422=>"Unprocessable Entity".freeze, 423=>"Locked".freeze, 424=>"Failed Dependency".freeze, 426=>"Upgrade Required".freeze, 428=>"Precondition Required".freeze, 429=>"Too Many Requests".freeze, 431=>"Request Header Fields Too Large".freeze, 500=>"Internal Server Error".freeze, 501=>"Not Implemented".freeze, 502=>"Bad Gateway".freeze, 503=>"Service Unavailable".freeze, 504=>"Gateway Timeout".freeze, 505=>"HTTP Version Not Supported".freeze, 506=>"Variant Also Negotiates".freeze, 507=>"Insufficient Storage".freeze, 508=>"Loop Detected".freeze, 510=>"Not Extended".freeze, 511=>"Network Authentication Required".freeze }
Instance Attribute Summary collapse
-
#body ⇒ Object
the response’s body buffer container (an array).
-
#bytes_written ⇒ Object
Logs the number of bytes written.
-
#flash ⇒ Object
readonly
the flash cookie-jar (single-use cookies, that survive only one request).
-
#headers ⇒ Object
readonly
the response’s headers.
-
#keep_alive ⇒ Object
forces the connection to remain alive if this flag is set to ‘true` (otherwise follows Http and optimization guidelines).
-
#request ⇒ Object
the request.
-
#status ⇒ Object
the response’s status code.
Instance Method Summary collapse
-
#<<(str) ⇒ Object
pushes data to the buffer of the response.
-
#[](header) ⇒ Object
returns a response header, if set.
-
#[]=(header, value) ⇒ Object
Sets a response header.
-
#clear ⇒ Object
clears the response object, unless headers were already sent (the response is already on it’s way, at least in part).
-
#cookies ⇒ Object
Returns a writable combined hash of the request’s cookies and the response cookie values.
-
#delete_cookie(name) ⇒ Object
deletes a cookie (actually calls ‘set_cookie name, nil`).
-
#extract_body ⇒ Object
This will return the Body object as a String…
-
#extract_cookies ⇒ Object
This will return an array of cookie settings to be appended to ‘set-cookie` headers.
-
#finish ⇒ Object
attempts to write a non-streaming response to the IO.
-
#headers_sent? ⇒ Boolean
returns true if headers were already sent.
-
#initialize(request, status = 200, headers = {}, content = nil) ⇒ Response
constructor
the response object responds to a specific request on a specific io.
-
#io ⇒ Object
returns the active protocol for the request.
-
#raw_cookies ⇒ Object
Returns the response’s encoded cookie hash.
-
#session ⇒ Hash like storage
Creates and returns the session storage object.
-
#set_cookie(name, value, params = {}) ⇒ Object
Sets/deletes cookies when headers are sent.
-
#stream_array(enum, &block) ⇒ true, Exception
Creates nested streaming blocks for an Array or another Enumerable object.
-
#stream_async(&block) ⇒ true, Exception
Creates a streaming block.
-
#uuid ⇒ Object
Returns the connection’s UUID.
Constructor Details
#initialize(request, status = 200, headers = {}, content = nil) ⇒ Response
the response object responds to a specific request on a specific io. hence, to initialize a response object, a request must be set.
use, at the very least ‘HTTPResponse.new request`
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/iodine/http/response.rb', line 28 def initialize request, status = 200, headers = {}, content = nil @request = request @status = status @headers = headers @body = content || [] @request..set_response self @cookies = {} @bytes_written = 0 @keep_alive = @http_sblocks_count = false # propegate flash object @flash = Hash.new do |hs,k| hs["magic_flash_#{k.to_s}".to_sym] if hs.has_key? "magic_flash_#{k.to_s}".to_sym end request..each do |k,v| @flash[k] = v if k.to_s.start_with? 'magic_flash_' end end |
Instance Attribute Details
#body ⇒ Object
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 17 18 |
# File 'lib/iodine/http/response.rb', line 16 def body @body end |
#bytes_written ⇒ Object
Logs the number of bytes written.
20 21 22 |
# File 'lib/iodine/http/response.rb', line 20 def bytes_written @bytes_written end |
#flash ⇒ Object (readonly)
the flash cookie-jar (single-use cookies, that survive only one request).
14 15 16 |
# File 'lib/iodine/http/response.rb', line 14 def flash @flash end |
#headers ⇒ Object (readonly)
the response’s headers
12 13 14 |
# File 'lib/iodine/http/response.rb', line 12 def headers @headers end |
#keep_alive ⇒ Object
forces the connection to remain alive if this flag is set to ‘true` (otherwise follows Http and optimization guidelines).
22 23 24 |
# File 'lib/iodine/http/response.rb', line 22 def keep_alive @keep_alive end |
#request ⇒ Object
the request.
18 19 20 |
# File 'lib/iodine/http/response.rb', line 18 def request @request end |
#status ⇒ Object
the response’s status code
10 11 12 |
# File 'lib/iodine/http/response.rb', line 10 def status @status end |
Instance Method Details
#<<(str) ⇒ Object
pushes data to the buffer of the response. this is the preferred way to add data to the response.
If the headers were already sent, this will also send the data and hang until the data was sent.
154 155 156 157 |
# File 'lib/iodine/http/response.rb', line 154 def << str ( @body ? @body.push(str) : ( (@body = str.dup) && request[:io].stream_response(self) ) ) if str self end |
#[](header) ⇒ Object
returns a response header, if set.
160 161 162 163 |
# File 'lib/iodine/http/response.rb', line 160 def [] header header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false)) headers[header] end |
#[]=(header, value) ⇒ Object
Sets a response header. response headers should be a downcase String (not a symbol or any other object).
this is the prefered to set a header.
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)
returns the value set for the header.
see HTTP response headers for valid headers and values: en.wikipedia.org/wiki/List_of_HTTP_header_fields
174 175 176 177 178 179 |
# File 'lib/iodine/http/response.rb', line 174 def []= header, value raise 'Cannot set headers after the headers had been sent.' if headers_sent? return (@headers.delete(header) && nil) if header.nil? header.is_a?(String) ? (header.frozen? ? header : header.downcase!) : (header.is_a?(Symbol) ? (header = header.to_s.downcase) : (return false)) headers[header] = value end |
#clear ⇒ Object
clears the response object, unless headers were already sent (the response is already on it’s way, at least in part).
returns false if the response was already sent.
234 235 236 237 238 |
# File 'lib/iodine/http/response.rb', line 234 def clear return false if @headers.frozen? @status, @body, @headers, @cookies = 200, [], {}, {} self end |
#cookies ⇒ Object
Returns a writable combined hash of the request’s cookies and the response cookie values.
Any cookies writen to this hash (‘response.cookies = value` will be set using default values).
It’s also possible to use this combined hash to delete cookies, using: response.cookies = nil
140 141 142 |
# File 'lib/iodine/http/response.rb', line 140 def @request. end |
#delete_cookie(name) ⇒ Object
deletes a cookie (actually calls ‘set_cookie name, nil`)
227 228 229 |
# File 'lib/iodine/http/response.rb', line 227 def name name, nil end |
#extract_body ⇒ Object
This will return the Body object as a String… And set the body to ‘nil` (seeing as it was extracted from the response).
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/iodine/http/response.rb', line 312 def extract_body if @body.is_a?(Array) return (@body = nil) if body.empty? @body = @body.join extract_body elsif @body.is_a?(String) return (@body = nil) if body.empty? tmp = @body @body = nil tmp elsif body.nil? nil elsif body.respond_to? :each tmp = '' body.each {|s| tmp << s} body.close if body.respond_to? :close @body = nil return nil if tmp.empty? tmp end end |
#extract_cookies ⇒ Object
This will return an array of cookie settings to be appended to ‘set-cookie` headers.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/iodine/http/response.rb', line 335 def unless @cookies.frozen? # remove old flash cookies @request..keys.each do |k| if k.to_s.start_with? 'magic_flash_'.freeze k, nil flash.delete k end end #set new flash cookies @flash.each do |k,v| "magic_flash_#{k.to_s}", v end @cookies.freeze # response.cookies.set_response nil @flash.freeze end [].tap do |arr| @cookies.each {|k, v| arr << "#{k.to_s}=#{v.to_s}"} end end |
#finish ⇒ Object
attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
241 242 243 |
# File 'lib/iodine/http/response.rb', line 241 def finish request[:io].send_response self end |
#headers_sent? ⇒ Boolean
returns true if headers were already sent
52 53 54 |
# File 'lib/iodine/http/response.rb', line 52 def headers_sent? @headers.frozen? end |
#io ⇒ Object
returns the active protocol for the request.
47 48 49 |
# File 'lib/iodine/http/response.rb', line 47 def io @request[:io] end |
#raw_cookies ⇒ Object
Returns the response’s encoded cookie hash.
This method allows direct editing of the cookies about to be set.
147 148 149 |
# File 'lib/iodine/http/response.rb', line 147 def @cookies end |
#session ⇒ Hash like storage
Creates and returns the session storage object.
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).
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.
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.
128 129 130 131 132 133 |
# File 'lib/iodine/http/response.rb', line 128 def session return @session if @session id = request.[::Iodine::Http.session_token.to_sym] || SecureRandom.uuid ::Iodine::Http.session_token, id, expires: (Time.now+86_400), secure: @request.ssl? @request[:session] = @session = ::Iodine::Http::SessionManager.get(id) end |
#set_cookie(name, value, params = {}) ⇒ Object
Sets/deletes cookies when headers are sent.
Accepts:
- name
-
the cookie’s name
- value
-
the cookie’s value
- parameters
-
a parameters Hash for cookie creation.
Parameters accept any of the following Hash keys and values:
- expires
-
a Time object with the expiration date. defaults to 10 years in the future.
- max_age
-
a Max-Age HTTP cookie string.
- path
-
the path from which the cookie is acessible. defaults to ‘/’.
- domain
-
the domain for the cookie (best used to manage subdomains). defaults to the active domain (sub-domain limitations might apply).
- secure
-
if set to ‘true`, the cookie will only be available over secure connections. defaults to false.
- http_only
-
if true, the HttpOnly flag will be set (not accessible to javascript). defaults to false.
Setting the request’s coockies (‘request.cookies = value`) will automatically call this method with default parameters.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/iodine/http/response.rb', line 202 def name, value, params = {} raise 'Cannot set cookies after the headers had been sent.' if headers_sent? name = name.to_s raise 'Illegal cookie name' if name =~ COOKIE_NAME_REGEXP if value.nil? params[:expires] = (Time.now - 315360000) value = 'deleted'.freeze else params[:expires] ||= (Time.now + 315360000) unless params[:max_age] end params[:path] ||= '/'.freeze value = Iodine::Http::Request.encode_url(value) if params[:max_age] value << ('; Max-Age=%s' % params[:max_age]) else value << ('; Expires=%s' % params[:expires].httpdate) end value << "; Path=#{params[:path]}" value << "; Domain=#{params[:domain]}" if params[:domain] value << '; Secure'.freeze if params[:secure] value << '; HttpOnly'.freeze if params[:http_only] @cookies[name.to_sym] = value end |
#stream_array(enum, &block) ⇒ true, Exception
Creates nested streaming blocks for an Array or another Enumerable object. Once all streaming blocks are done, the response will automatically finish.
Since streaming blocks might run in parallel, nesting the streaming blocks is important…
However, manually nesting hundreds of nesting blocks is time consuming and error prone.
sream_enum allows you to stream an enumerable knowing that Plezi will nest the streaming blocks dynamically.
Accepts:
- enum
-
an Enumerable or an object that answers to the ‘to_a` method (the array will be used to stream the )
If an Array is passed to the enumerable, it will be changed and emptied as the streaming progresses. So, if preserving the array is important, please create a shallow copy of the array first using the ‘.dup` method.
i.e.:
data = "Hello world!".chars
response.stream_enum(data.each_with_index) {|c, i| response << c; sleep i/10.0 }
109 110 111 112 113 114 115 116 117 |
# File 'lib/iodine/http/response.rb', line 109 def stream_array enum, &block enum = enum.to_a return if enum.empty? stream_async do args = enum.shift block.call(*args) stream_array enum, &block end end |
#stream_async(&block) ⇒ true, Exception
Creates a streaming block. Once all streaming blocks are done, the response will automatically finish.
This avoids manualy handling #start_streaming, #finish_streaming and asynchronously tasking.
Every time data is sent the timout is reset. Responses longer than timeout will not be sent (but they will be processed).
Since Iodine is likely to be multi-threading (depending on your settings and architecture), it is important that streaming blocks are nested rather than chained. Chained streaming blocks might be executed in parallel and suffer frome race conditions that might lead to the response being corrupted.
Accepts a required block. i.e.
response.stream_async {sleep 1; response << "Hello Streaming"}
# OR, you can nest (but not chain) the streaming calls
response.stream_async do
sleep 1
response << "Hello Streaming"
response.stream_async do
sleep 1
response << "\r\nGoodbye Streaming"
end
end
80 81 82 83 84 85 86 |
# File 'lib/iodine/http/response.rb', line 80 def stream_async &block raise "Block required." unless block start_streaming unless @http_sblocks_count @http_sblocks_count += 1 @stream_proc ||= Proc.new { |block| raise "IO closed. Streaming failed." if request[:io].io.closed?; block.call; @http_sblocks_count -= 1; finish_streaming } Iodine.run block, &@stream_proc end |
#uuid ⇒ Object
Returns the connection’s UUID.
246 247 248 |
# File 'lib/iodine/http/response.rb', line 246 def uuid request[:io].id end |