Class: EventMachine::HttpResponse
- Inherits:
-
Object
- Object
- EventMachine::HttpResponse
- Defined in:
- lib/evma_httpserver/response.rb
Overview
This class provides a wide variety of features for generating and dispatching HTTP responses. It allows you to conveniently generate headers and content (including chunks and multiparts), and dispatch responses (including deferred or partially-complete responses).
Although HttpResponse is coded as a class, it’s not complete as it stands. It assumes that it has certain of the behaviors of EventMachine::Connection. You must add these behaviors, either by subclassing HttpResponse, or using the alternate version of this class, DelegatedHttpResponse. See the test cases for current information on which behaviors you have to add.
TODO, someday it would be nice to provide a version of this functionality that is coded as a Module, so it can simply be mixed into an instance of EventMachine::Connection.
Direct Known Subclasses
Constant Summary collapse
- STATUS_CODES =
{ 100 => "100 Continue", 101 => "101 Switching Protocols", 200 => "200 OK", 201 => "201 Created", 202 => "202 Accepted", 203 => "203 Non-Authoritative Information", 204 => "204 No Content", 205 => "205 Reset Content", 206 => "206 Partial Content", 300 => "300 Multiple Choices", 301 => "301 Moved Permanently", 302 => "302 Found", 303 => "303 See Other", 304 => "304 Not Modified", 305 => "305 Use Proxy", 307 => "307 Temporary Redirect", 400 => "400 Bad Request", 401 => "401 Unauthorized", 402 => "402 Payment Required", 403 => "403 Forbidden", 404 => "404 Not Found", 405 => "405 Method Not Allowed", 406 => "406 Not Acceptable", 407 => "407 Proxy Authentication Required", 408 => "408 Request Timeout", 409 => "409 Conflict", 410 => "410 Gone", 411 => "411 Length Required", 412 => "412 Precondition Failed", 413 => "413 Request Entity Too Large", 414 => "414 Request-URI Too Long", 415 => "415 Unsupported Media Type", 416 => "416 Requested Range Not Satisfiable", 417 => "417 Expectation Failed", 500 => "500 Internal Server Error", 501 => "501 Not Implemented", 502 => "502 Bad Gateway", 503 => "503 Service Unavailable", 504 => "504 Gateway Timeout", 505 => "505 HTTP Version Not Supported" }
Instance Attribute Summary collapse
-
#chunks ⇒ Object
Returns the value of attribute chunks.
-
#headers ⇒ Object
Returns the value of attribute headers.
-
#multiparts ⇒ Object
Returns the value of attribute multiparts.
-
#status ⇒ Object
Returns the value of attribute status.
Class Method Summary collapse
-
.concoct_multipart_boundary ⇒ Object
TODO, this is going to be way too slow.
Instance Method Summary collapse
-
#add_set_cookie(*ck) ⇒ Object
Sugaring for Set-Cookie headers.
-
#chunk(text) ⇒ Object
add a chunk to go to the output.
- #content ⇒ Object
- #content=(value) ⇒ Object
- #content? ⇒ Boolean
-
#content_type(*mime) ⇒ Object
sugarings for headers.
-
#fixup_headers ⇒ Object
Examine the content type and data and other things, and perform a final fixup of the header array.
-
#initialize ⇒ HttpResponse
constructor
A new instance of HttpResponse.
- #keep_connection_open(arg = true) ⇒ Object
-
#multipart(arg) ⇒ Object
To add a multipart to the outgoing response, specify the headers and the body.
-
#send_body ⇒ Object
we send either content, chunks, or multiparts.
-
#send_chunks ⇒ Object
send the contents of the chunk list and clear it out.
- #send_content ⇒ Object
-
#send_headers ⇒ Object
Send the headers out in alpha-sorted order.
-
#send_multiparts ⇒ Object
Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq.
- #send_redirect(location) ⇒ Object
-
#send_response ⇒ Object
This is intended to send a complete HTTP response, including closing the connection if appropriate at the end of the transmission.
-
#send_trailer ⇒ Object
send a trailer which depends on the type of body we’re dealing with.
- #set_cookie(*ck) ⇒ Object
Constructor Details
#initialize ⇒ HttpResponse
Returns a new instance of HttpResponse.
97 98 99 |
# File 'lib/evma_httpserver/response.rb', line 97 def initialize @headers = {} end |
Instance Attribute Details
#chunks ⇒ Object
Returns the value of attribute chunks.
95 96 97 |
# File 'lib/evma_httpserver/response.rb', line 95 def chunks @chunks end |
#headers ⇒ Object
Returns the value of attribute headers.
95 96 97 |
# File 'lib/evma_httpserver/response.rb', line 95 def headers @headers end |
#multiparts ⇒ Object
Returns the value of attribute multiparts.
95 96 97 |
# File 'lib/evma_httpserver/response.rb', line 95 def multiparts @multiparts end |
#status ⇒ Object
Returns the value of attribute status.
95 96 97 |
# File 'lib/evma_httpserver/response.rb', line 95 def status @status end |
Class Method Details
.concoct_multipart_boundary ⇒ Object
TODO, this is going to be way too slow. Cache up the uuidgens.
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/evma_httpserver/response.rb', line 333 def self.concoct_multipart_boundary @multipart_index ||= 0 @multipart_index += 1 if @multipart_index >= 1000 @multipart_index = 0 @multipart_guid = nil end @multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"") "#{@multipart_guid}#{@multipart_index}" end |
Instance Method Details
#add_set_cookie(*ck) ⇒ Object
Sugaring for Set-Cookie headers. These are a pain because there can easily and legitimately be more than one. So we use an ugly verb to signify that. #add_set_cookies does NOT disturb the Set-Cookie headers which may have been added on a prior call. #set_cookie clears them out first.
134 135 136 137 138 139 |
# File 'lib/evma_httpserver/response.rb', line 134 def *ck if ck.length > 0 h = (@headers["Set-Cookie"] ||= []) ck.each {|c| h << c} end end |
#chunk(text) ⇒ Object
add a chunk to go to the output. Will cause the headers to pick up “content-transfer-encoding” Add the chunk to a list. Calling #send_chunks will send out the available chunks and clear the chunk list WITHOUT closing the connection, so it can be called any number of times. TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client. Raise an exception here if our user tries to do so. Chunked transfer coding is defined in RFC2616 pgh 3.6.1. The argument can be a string or a hash. The latter allows for sending chunks with extensions (someday).
273 274 275 276 |
# File 'lib/evma_httpserver/response.rb', line 273 def chunk text @chunks ||= [] @chunks << text end |
#content ⇒ Object
105 106 107 |
# File 'lib/evma_httpserver/response.rb', line 105 def content @content || '' end |
#content=(value) ⇒ Object
101 102 103 |
# File 'lib/evma_httpserver/response.rb', line 101 def content= value @content = value.to_s end |
#content? ⇒ Boolean
109 110 111 |
# File 'lib/evma_httpserver/response.rb', line 109 def content? !!@content end |
#content_type(*mime) ⇒ Object
sugarings for headers
122 123 124 125 126 127 128 |
# File 'lib/evma_httpserver/response.rb', line 122 def content_type *mime if mime.length > 0 @headers["Content-Type"] = mime.first.to_s else @headers["Content-Type"] end end |
#fixup_headers ⇒ Object
Examine the content type and data and other things, and perform a final fixup of the header array. We expect this to be called just before sending headers to the remote peer. In the case of multiparts, we ASSUME we will get called before any content gets sent out, because the multipart boundary is created here.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/evma_httpserver/response.rb', line 202 def fixup_headers if @content @headers["Content-Length"] = @content.bytesize elsif @chunks @headers["Transfer-Encoding"] = "chunked" # Might be nice to ENSURE there is no content-length header, # but how to detect all the possible permutations of upper/lower case? elsif @multiparts @multipart_boundary = self.class.concoct_multipart_boundary @headers["Content-Type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\"" else @headers["Content-Length"] = 0 end end |
#keep_connection_open(arg = true) ⇒ Object
113 114 115 |
# File 'lib/evma_httpserver/response.rb', line 113 def keep_connection_open arg=true @keep_connection_open = arg end |
#multipart(arg) ⇒ Object
To add a multipart to the outgoing response, specify the headers and the body. If only a string is given, it’s treated as the body (in this case, the header is assumed to be empty).
304 305 306 307 308 309 310 311 312 313 |
# File 'lib/evma_httpserver/response.rb', line 304 def multipart arg vals = if arg.is_a?(String) {:body => arg, :headers => {}} else arg end @multiparts ||= [] @multiparts << vals end |
#send_body ⇒ Object
we send either content, chunks, or multiparts. Content can only be sent once. Chunks and multiparts can be sent any number of times. DO NOT close the connection or send any goodbye kisses. This method can be called multiple times to send out chunks or multiparts.
221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/evma_httpserver/response.rb', line 221 def send_body if @content send_content elsif @chunks send_chunks elsif @multiparts send_multiparts else @content = "" send_content end end |
#send_chunks ⇒ Object
send the contents of the chunk list and clear it out. ASSUMES that headers have been sent. Does NOT close the connection. Can be called multiple times. According to RFC2616, phg 3.6.1, the last chunk will be zero length. But some caller could accidentally set a zero-length chunk in the middle of the stream. If that should happen, raise an exception. The reason for supporting chunks that are hashes instead of just strings is to enable someday supporting chunk-extension codes (cf the RFC). TODO!!! We’re not supporting the final entity-header that may be transmitted after the last (zero-length) chunk.
290 291 292 293 294 295 296 297 298 |
# File 'lib/evma_httpserver/response.rb', line 290 def send_chunks send_headers unless @sent_headers while chunk = @chunks.shift raise "last chunk already sent" if @last_chunk_sent text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n" @last_chunk_sent = true if text.length == 0 end end |
#send_content ⇒ Object
256 257 258 259 260 |
# File 'lib/evma_httpserver/response.rb', line 256 def send_content raise "sent content already" if @sent_content @sent_content = true send_data(content) end |
#send_headers ⇒ Object
Send the headers out in alpha-sorted order. This will degrade performance to some degree, and is intended only to simplify the construction of unit tests.
166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/evma_httpserver/response.rb', line 166 def send_headers raise "sent headers already" if @sent_headers @sent_headers = true fixup_headers ary = [] ary << "HTTP/1.1 #{@status || '200 OK'}\r\n" ary += generate_header_lines(@headers) ary << "\r\n" send_data ary.join end |
#send_multiparts ⇒ Object
Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq. The CRLF which introduces the boundary line of each part (content entity) is defined as being part of the boundary, not of the preceding part. So we don’t need to mess with interpreting the last bytes of a part to ensure they are CRLF-terminated.
321 322 323 324 325 326 327 328 329 |
# File 'lib/evma_httpserver/response.rb', line 321 def send_multiparts send_headers unless @sent_headers while part = @multiparts.shift send_data "\r\n--#{@multipart_boundary}\r\n" send_data( generate_header_lines( part[:headers] || {} ).join) send_data "\r\n" send_data part[:body].to_s end end |
#send_redirect(location) ⇒ Object
344 345 346 347 348 |
# File 'lib/evma_httpserver/response.rb', line 344 def send_redirect location @status = 302 # TODO, make 301 available by parameter @headers["Location"] = location send_response end |
#send_response ⇒ Object
This is intended to send a complete HTTP response, including closing the connection if appropriate at the end of the transmission. Don’t use this method to send partial or iterated responses. This method will send chunks and multiparts provided they are all available when we get here. Note that the default @status is 200 if the value doesn’t exist.
156 157 158 159 160 161 |
# File 'lib/evma_httpserver/response.rb', line 156 def send_response send_headers send_body send_trailer close_connection_after_writing unless (@keep_connection_open and (@status || "200 OK") == "200 OK") end |
#send_trailer ⇒ Object
send a trailer which depends on the type of body we’re dealing with. The assumption is that we’re about to end the transmission of this particular HTTP response. (A connection-close may or may not follow.)
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/evma_httpserver/response.rb', line 238 def send_trailer send_headers unless @sent_headers if @content #no-op elsif @chunks unless @last_chunk_sent chunk '' send_chunks end elsif @multiparts # in the lingo of RFC 2046/5.1.1, we're sending an "epilog" # consisting of a blank line. I really don't know how that is # supposed to interact with the case where we leave the connection # open after transmitting the multipart response. send_data "\r\n--#{@multipart_boundary}--\r\n\r\n" end end |
#set_cookie(*ck) ⇒ Object
140 141 142 143 144 145 146 147 148 |
# File 'lib/evma_httpserver/response.rb', line 140 def *ck h = (@headers["Set-Cookie"] ||= []) if ck.length > 0 h.clear *ck else h end end |