Class: Anorexic::HTTPResponse

Inherits:
Object
  • Object
show all
Defined in:
lib/anorexic/server/protocols/http_response.rb

Overview

this class handles HTTP response objects.

learning from rack, the basic response objects imitates the [0, {}, []] structure… with some updates.

the Response's body should respond to each (and optionally to close).

The response can be sent asynchronously, but headers and status cannot be changed once the response started sending data.

Constant Summary collapse

STATUS_CODES =

response status codes, as defined.

{100=>"Continue",
  101=>"Switching Protocols",
  102=>"Processing",
  200=>"OK",
  201=>"Created",
  202=>"Accepted",
  203=>"Non-Authoritative Information",
  204=>"No Content",
  205=>"Reset Content",
  206=>"Partial Content",
  207=>"Multi-Status",
  208=>"Already Reported",
  226=>"IM Used",
  300=>"Multiple Choices",
  301=>"Moved Permanently",
  302=>"Found",
  303=>"See Other",
  304=>"Not Modified",
  305=>"Use Proxy",
  306=>"(Unused)",
  307=>"Temporary Redirect",
  308=>"Permanent Redirect",
  400=>"Bad Request",
  401=>"Unauthorized",
  402=>"Payment Required",
  403=>"Forbidden",
  404=>"Not Found",
  405=>"Method Not Allowed",
  406=>"Not Acceptable",
  407=>"Proxy Authentication Required",
  408=>"Request Timeout",
  409=>"Conflict",
  410=>"Gone",
  411=>"Length Required",
  412=>"Precondition Failed",
  413=>"Payload Too Large",
  414=>"URI Too Long",
  415=>"Unsupported Media Type",
  416=>"Range Not Satisfiable",
  417=>"Expectation Failed",
  422=>"Unprocessable Entity",
  423=>"Locked",
  424=>"Failed Dependency",
  426=>"Upgrade Required",
  428=>"Precondition Required",
  429=>"Too Many Requests",
  431=>"Request Header Fields Too Large",
  500=>"Internal Server Error",
  501=>"Not Implemented",
  502=>"Bad Gateway",
  503=>"Service Unavailable",
  504=>"Gateway Timeout",
  505=>"HTTP Version Not Supported",
  506=>"Variant Also Negotiates",
  507=>"Insufficient Storage",
  508=>"Loop Detected",
  510=>"Not Extended",
  511=>"Network Authentication Required"
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request, status = 200, headers = {}, body = []) ⇒ HTTPResponse

the response object responds to a specific request on a specific service. hence, to initialize a response object, a request must be set.

use, at the very least `HTTPResponse.new request`


35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/anorexic/server/protocols/http_response.rb', line 35

def initialize request, status = 200, headers = {}, body = []
  @request, @status, @headers, @body, @service = request, status, headers, body, (defined?(ANOREXIC_ON_RACK) ? false : request.service)
  @http_version = 'HTTP/1.1' # request.version
  @bytes_sent = 0
  @finished = false
  @cookies = {}
  # propegate flash object
  @flash = Hash.new do |hs,k|
    hs["anorexic_flash_#{k.to_s}"] if hs.has_key? "anorexic_flash_#{k.to_s}"
  end
  request.cookies.each do |k,v|
    @flash[k] = v if k.to_s.start_with? "anorexic_flash_"
  end
end

Instance Attribute Details

#bodyObject

the response's body container (defaults to an array, but can be replaces by any obect that supports `each` - `close` is NOT supported - call `close` as a callback block after `send` if you need to close the object).


19
20
21
# File 'lib/anorexic/server/protocols/http_response.rb', line 19

def body
  @body
end

#bytes_sentObject (readonly)

bytes sent to the asynchronous que so far - excluding headers (only the body object).


21
22
23
# File 'lib/anorexic/server/protocols/http_response.rb', line 21

def bytes_sent
  @bytes_sent
end

#cookiesObject (readonly)

Danger Zone! direct access to cookie headers - don't use this unless you know what you're doing!


29
30
31
# File 'lib/anorexic/server/protocols/http_response.rb', line 29

def cookies
  @cookies
end

#flashObject (readonly)

the flash cookie-jar (single-use cookies, that survive only one request)


17
18
19
# File 'lib/anorexic/server/protocols/http_response.rb', line 17

def flash
  @flash
end

#headersObject (readonly)

the response's headers


15
16
17
# File 'lib/anorexic/server/protocols/http_response.rb', line 15

def headers
  @headers
end

#http_versionObject

the http version header


27
28
29
# File 'lib/anorexic/server/protocols/http_response.rb', line 27

def http_version
  @http_version
end

#requestObject

the request.


25
26
27
# File 'lib/anorexic/server/protocols/http_response.rb', line 25

def request
  @request
end

#serviceObject (readonly)

the service through which the response will be sent.


23
24
25
# File 'lib/anorexic/server/protocols/http_response.rb', line 23

def service
  @service
end

#statusObject

the response's status code


13
14
15
# File 'lib/anorexic/server/protocols/http_response.rb', line 13

def status
  @status
end

Instance Method Details

#<<(str) ⇒ Object

pushes data to the body of the response. this is the preffered way to add data to the response.


61
62
63
# File 'lib/anorexic/server/protocols/http_response.rb', line 61

def << str
  body.push str  
end

#[](header) ⇒ Object

returns a response header, if set.


66
67
68
# File 'lib/anorexic/server/protocols/http_response.rb', line 66

def [] header
  headers[header] # || @cookies[header]
end

#[]=(header, value) ⇒ Object

sets a response header. response headers should be a down-case String or Symbol.

this is the prefered to set a header.

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


77
78
79
80
# File 'lib/anorexic/server/protocols/http_response.rb', line 77

def []= header, value
  header.is_a?(String) ? header.downcase! : (header.is_a?(Symbol) ? (header = header.to_s.downcase.to_sym) : (return false))
  headers[header]  = value
end

#clearObject

clears the response object, unless headers were already sent (use `response.body.clear` to clear only the unsent body).

returns false if the response was already sent.


124
125
126
127
128
# File 'lib/anorexic/server/protocols/http_response.rb', line 124

def clear
  return false if headers.frozen? || @finished
  @status, @body, @headers, @cookies = 200, [], {}, {}
  true
end

deletes a cookie (actually calls `set_cookie name, nil`)


117
118
119
# File 'lib/anorexic/server/protocols/http_response.rb', line 117

def delete_cookie name
  set_cookie name, nil
end

#finishObject

sends the response and flags the response as complete. future data should not be sent. the flag will only be enforced be the Anorexic router. your code might attempt sending data (which would probbaly be ignored by the client or raise an exception).


154
155
156
157
158
159
160
161
162
163
# File 'lib/anorexic/server/protocols/http_response.rb', line 154

def finish
  @headers['content-length'] ||= body[0].bytesize if !headers_sent? && body.is_a?(Array) && body.length == 1
  return self if defined?(ANOREXIC_ON_RACK)
  raise 'HTTPResponse SERVICE MISSING: cannot send http response without a service.' unless service
  self.send
  service.send( (headers["transfer-encoding"] == "chunked") ? "0\r\n\r\n" : nil)
  @finished = true
  # log
  Anorexic.log_raw "#{request[:client_ip]} [#{Time.now.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:requested_protocol]}\/#{request[:version]}\" #{status} #{bytes_sent.to_s} #{"%0.3f" % ((Time.now - request[:time_recieved])*1000)}ms\n"
end

#finished?Boolean

returns true if the response is already finished (the client isn't expecting any more data).

Returns:

  • (Boolean)

56
57
58
# File 'lib/anorexic/server/protocols/http_response.rb', line 56

def finished?
  @finished
end

#fix_headersObject

Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/anorexic/server/protocols/http_response.rb', line 171

def fix_headers
  # headers['Connection'] ||= "Keep-Alive"
  headers['Date'] = Time.now.httpdate
  headers['Transfer-Encoding'] ||= 'chunked' if !headers['content-length']
  headers['cache-control'] ||= 'no-cache'
  # remove old flash cookies
  request.cookies.keys.each do |k|
    if k.to_s.start_with? "anorexic_flash_"
      set_cookie k, nil
      flash.delete k
    end
  end
  #set new flash cookies
  @flash.each do |k,v|
    set_cookie "anorexic_flash_#{k.to_s}", v
  end
end

#headers_sent?Boolean

returns true if headers were already sent

Returns:

  • (Boolean)

51
52
53
# File 'lib/anorexic/server/protocols/http_response.rb', line 51

def headers_sent?
  @headers.frozen?
end

#sendObject

sends the response object. headers will be frozen (they can only be sent at the head of the response).

the response will remain open for more data to be sent through (using `response << data` and `response.send`).


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/anorexic/server/protocols/http_response.rb', line 133

def send
  raise 'HTTPResponse SERVICE MISSING: cannot send http response without a service.' unless service
  send_headers
  return if request.head?
  if headers["transfer-encoding"] == "chunked"
    body.each do |s|
      service.send "#{s.bytesize.to_s(16)}\r\n"
      service.send s
      service.send "\r\n"
      @bytes_sent += s.bytesize
    end
  else
    body.each do |s|
      service.send s
      @bytes_sent += s.bytesize
    end
  end
  @body.is_a?(Array) ? @body.clear : ( @body = [] )
end

#send_headersObject

Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).


189
190
191
192
193
194
195
196
197
198
# File 'lib/anorexic/server/protocols/http_response.rb', line 189

def send_headers
  return false if @headers.frozen?
  fix_headers
  service.send "#{@http_version} #{status} #{STATUS_CODES[status] || 'unknown'}\r\n"
  headers.each {|k,v| service.send "#{k.to_s}: #{v}\r\n"}
  @cookies.each {|k,v| service.send "Set-Cookie: #{k.to_s}=#{v.to_s}\r\n"}
  service.send "\r\n"
  @headers.freeze
  # @cookies.freeze
end

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.


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/anorexic/server/protocols/http_response.rb', line 98

def set_cookie name, value, params = {}
  params[:expires] = (Time.now - 315360000) unless value
  value ||= 'deleted'
  params[:expires] ||= (Time.now + 315360000) unless params[:max_age]
  params[:path] ||= '/'
  value = HTTP.encode(value.to_s)
  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" if params[:secure]
  value << "; HttpOnly" if params[:http_only]
  @cookies[HTTP.encode(name.to_s).to_sym] = value
end

#try_finishObject

attempts to finish the response - if it was not flaged as completed.


166
167
168
# File 'lib/anorexic/server/protocols/http_response.rb', line 166

def try_finish
  finish unless @finished
end