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`
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/iodine/http/response.rb', line 26 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.
14 15 16 |
# File 'lib/iodine/http/response.rb', line 14 def body @body end |
#bytes_written ⇒ Object
Logs the number of bytes written.
18 19 20 |
# File 'lib/iodine/http/response.rb', line 18 def bytes_written @bytes_written end |
#flash ⇒ Object (readonly)
the flash cookie-jar (single-use cookies, that survive only one request).
12 13 14 |
# File 'lib/iodine/http/response.rb', line 12 def flash @flash end |
#headers ⇒ Object (readonly)
the response’s headers
10 11 12 |
# File 'lib/iodine/http/response.rb', line 10 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).
20 21 22 |
# File 'lib/iodine/http/response.rb', line 20 def keep_alive @keep_alive end |
#request ⇒ Object
the request.
16 17 18 |
# File 'lib/iodine/http/response.rb', line 16 def request @request end |
#status ⇒ Object
the response’s status code
8 9 10 |
# File 'lib/iodine/http/response.rb', line 8 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.
152 153 154 155 |
# File 'lib/iodine/http/response.rb', line 152 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.
158 159 160 161 |
# File 'lib/iodine/http/response.rb', line 158 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
172 173 174 175 176 177 |
# File 'lib/iodine/http/response.rb', line 172 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.
232 233 234 235 236 |
# File 'lib/iodine/http/response.rb', line 232 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
138 139 140 |
# File 'lib/iodine/http/response.rb', line 138 def @request. end |
#delete_cookie(name) ⇒ Object
deletes a cookie (actually calls ‘set_cookie name, nil`)
225 226 227 |
# File 'lib/iodine/http/response.rb', line 225 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).
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/iodine/http/response.rb', line 311 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.
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/iodine/http/response.rb', line 334 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 arr = [] @cookies.each {|k, v| arr << "#{k.to_s}=#{v.to_s}"} arr end |
#finish ⇒ Object
attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
239 240 241 242 |
# File 'lib/iodine/http/response.rb', line 239 def finish request[:io].send_response self 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 end |
#headers_sent? ⇒ Boolean
returns true if headers were already sent
50 51 52 |
# File 'lib/iodine/http/response.rb', line 50 def headers_sent? @headers.frozen? end |
#io ⇒ Object
returns the active protocol for the request.
45 46 47 |
# File 'lib/iodine/http/response.rb', line 45 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.
145 146 147 |
# File 'lib/iodine/http/response.rb', line 145 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.
126 127 128 129 130 131 |
# File 'lib/iodine/http/response.rb', line 126 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.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/iodine/http/response.rb', line 200 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 }
107 108 109 110 111 112 113 114 115 |
# File 'lib/iodine/http/response.rb', line 107 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
78 79 80 81 82 83 84 |
# File 'lib/iodine/http/response.rb', line 78 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.
245 246 247 |
# File 'lib/iodine/http/response.rb', line 245 def uuid request[:io].id end |