class EventMachine::HttpResponse

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.

Attributes

chunks[RW]
content[RW]
headers[RW]
multiparts[RW]
status[RW]
status_string[RW]

Public Class Methods

concoct_multipart_boundary() click to toggle source

TODO, this is going to be way too slow. Cache up the uuidgens.

# File lib/event_machine/http_server/response.rb, line 277
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
new() click to toggle source
# File lib/event_machine/http_server/response.rb, line 54
def initialize
        @headers = {}
        @keep_connection_open = false
end

Public Instance Methods

chunk(text) click to toggle source

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).

# File lib/event_machine/http_server/response.rb, line 217
def chunk text
        @chunks ||= []
        @chunks << text
end
content_type(*mime) click to toggle source

sugarings for headers

# File lib/event_machine/http_server/response.rb, line 64
def content_type *mime
        if mime.length > 0
                @headers["Content-type"] = mime.first.to_s
        else
                @headers["Content-type"]
        end
end
fixup_headers() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 144
def fixup_headers
        if @content
                @headers["Content-length"] = content.to_s.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) click to toggle source
# File lib/event_machine/http_server/response.rb, line 59
def keep_connection_open arg=true
        @keep_connection_open = arg
end
multipart(arg) click to toggle source

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).

# File lib/event_machine/http_server/response.rb, line 248
def multipart arg
        vals = if arg.is_a?(String)
                {:body => arg, :headers => {}}
        else
                arg
        end

        @multiparts ||= []
        @multiparts << vals
end
send_body() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 163
def send_body
        if @content
                send_content
        elsif @chunks
                send_chunks
        elsif @multiparts
                send_multiparts
        else
                @content = ""
                send_content
        end
end
send_chunks() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 234
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() click to toggle source
# File lib/event_machine/http_server/response.rb, line 200
def send_content
        raise "sent content already" if @sent_content
        @sent_content = true
        send_data((@content || "").to_s)
end
send_headers() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 108
def send_headers
        raise "sent headers already" if @sent_headers
        @sent_headers = true

        fixup_headers

        ary = []
        ary << "HTTP/1.1 #{@status || 200} #{@status_string || '...'}\r\n"
        ary += generate_header_lines(@headers)
        ary << "\r\n"

        send_data ary.join
end
send_multiparts() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 265
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) click to toggle source
# File lib/event_machine/http_server/response.rb, line 288
          def send_redirect location
                  @status = 302 # TODO, make 301 available by parameter
@status_string = "Moved Temporarily"
                  @headers["Location"] = location
                  send_response
          end
send_response() click to toggle source

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.

# File lib/event_machine/http_server/response.rb, line 98
def send_response
        send_headers
        send_body
        send_trailer
        close_connection_after_writing unless (@keep_connection_open and (@status || 200) < 500)
end
send_trailer() click to toggle source

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.)

# File lib/event_machine/http_server/response.rb, line 180
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"
        else
                # no-op
        end
end

Private Instance Methods

generate_header_lines(in_hash) click to toggle source
# File lib/event_machine/http_server/response.rb, line 123
def generate_header_lines in_hash
        out_ary = []
        in_hash.keys.sort.each {|k|
                v = in_hash[k]
                if v.is_a?(Array)
                        v.each {|v1| out_ary << "#{k}: #{v1}\r\n" }
                else
                        out_ary << "#{k}: #{v}\r\n"
                end
        }
        out_ary
end