Skip to content
Browse files

Send full HTTP/1.1 headers. Capitalize headers per HTTP/1.1 spec

  • Loading branch information...
1 parent 59e052d commit 83418358fc8becfe96bc8c0f9acdfa94be5d6af9 @cmdrkeene cmdrkeene committed Jun 25, 2011
Showing with 516 additions and 475 deletions.
  1. +11 −12 ext/http.cpp
  2. +4 −2 ext/http.h
  3. +1 −1 ext/rubyhttp.cpp
  4. +318 −272 lib/evma_httpserver/response.rb
  5. +2 −2 test/test_app.rb
  6. +163 −166 test/test_delegated.rb
  7. +17 −20 test/test_response.rb
View
23 ext/http.cpp
@@ -358,7 +358,7 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
if (bContentLengthSeen) {
// TODO, log this. There are some attacks that depend
// on sending more than one content-length header.
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
bContentLengthSeen = true;
@@ -368,7 +368,7 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
ContentLength = atoi (s);
if (ContentLength > MaxContentLength) {
// TODO, log this.
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
}
@@ -407,7 +407,7 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
}
else {
// TODO, log this.
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
@@ -439,7 +439,7 @@ bool HttpConnection_t::_InterpretRequest (const char *header)
const char *blank = strchr (header, ' ');
if (!blank) {
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
@@ -448,17 +448,17 @@ bool HttpConnection_t::_InterpretRequest (const char *header)
blank++;
if (*blank != '/') {
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
const char *blank2 = strchr (blank, ' ');
if (!blank2) {
- _SendError (406);
+ _SendError (RESPONSE_CODE_406);
return false;
}
if (strcasecmp (blank2 + 1, "HTTP/1.0") && strcasecmp (blank2 + 1, "HTTP/1.1")) {
- _SendError (505);
+ _SendError (RESPONSE_CODE_505);
return false;
}
@@ -562,7 +562,7 @@ bool HttpConnection_t::_DetectVerbAndSetEnvString (const char *request, int verb
}
}
- _SendError (405);
+ _SendError (RESPONSE_CODE_406);
return false;
}
@@ -572,14 +572,13 @@ bool HttpConnection_t::_DetectVerbAndSetEnvString (const char *request, int verb
HttpConnection_t::_SendError
****************************/
-void HttpConnection_t::_SendError (int code)
+void HttpConnection_t::_SendError (const char *header)
{
stringstream ss;
- ss << "HTTP/1.1 " << code << " ...\r\n";
+ ss << "HTTP/1.1 " << header << "\r\n";
ss << "Connection: close\r\n";
- ss << "Content-type: text/plain\r\n";
+ ss << "Content-Type: text/plain\r\n";
ss << "\r\n";
- ss << "Detected error: HTTP code " << code;
SendData (ss.str().c_str(), ss.str().length());
}
View
6 ext/http.h
@@ -26,7 +26,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifndef __HttpPersonality__H_
#define __HttpPersonality__H_
-
+#define RESPONSE_CODE_405 "405 Method Not Allowed"
+#define RESPONSE_CODE_406 "406 Not Acceptable"
+#define RESPONSE_CODE_505 "505 HTTP Version Not Supported"
/**********************
class HttpConnection_t
@@ -106,7 +108,7 @@ class HttpConnection_t
bool _InterpretHeaderLine (const char*);
bool _InterpretRequest (const char*);
bool _DetectVerbAndSetEnvString (const char*, int);
- void _SendError (int);
+ void _SendError (const char*);
};
#endif // __HttpPersonality__H_
View
2 ext/rubyhttp.cpp
@@ -256,7 +256,7 @@ t_process_http_request
static VALUE t_process_http_request (VALUE self)
{
// This is a stub in case the caller doesn't define it.
- rb_funcall (self, rb_intern ("send_data"), 1, rb_str_new2 ("HTTP/1.1 200 ...\r\nContent-type: text/plain\r\nContent-length: 8\r\n\r\nMonorail"));
+ rb_funcall (self, rb_intern ("send_data"), 1, rb_str_new2 ("HTTP/1.1 200 OK\r\nContent-type: text/plain\r\nContent-length: 8\r\n\r\nMonorail"));
return Qnil;
}
View
590 lib/evma_httpserver/response.rb
@@ -2,7 +2,7 @@
# HTTP Response-support class
#
# Author:: blackhedd (gmail address: garbagecat10).
-#
+#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
#
# This program is made available under the terms of the GPL version 2.
@@ -32,282 +32,328 @@
module EventMachine
- # 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.
- #
- class HttpResponse
- attr_accessor :status, :content, :headers, :chunks, :multiparts
-
- def initialize
- @headers = {}
- end
-
- def keep_connection_open arg=true
- @keep_connection_open = arg
- end
-
- # sugarings for headers
- def content_type *mime
- if mime.length > 0
- @headers["Content-type"] = mime.first.to_s
- else
- @headers["Content-type"]
- end
- end
-
- # 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.
- def add_set_cookie *ck
- if ck.length > 0
- h = (@headers["Set-cookie"] ||= [])
- ck.each {|c| h << c}
- end
- end
- def set_cookie *ck
- h = (@headers["Set-cookie"] ||= [])
- if ck.length > 0
- h.clear
- add_set_cookie *ck
- else
- h
- end
- end
-
-
- # 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.
- def send_response
- send_headers
- send_body
- send_trailer
- close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200)
- end
-
- # 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.
- #
- def send_headers
- raise "sent headers already" if @sent_headers
- @sent_headers = true
-
- fixup_headers
-
- ary = []
- ary << "HTTP/1.1 #{@status || 200} ...\r\n"
- ary += generate_header_lines(@headers)
- ary << "\r\n"
-
- send_data ary.join
- end
-
-
- 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
- private :generate_header_lines
-
-
- # 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.
- #
- def fixup_headers
- if @content
- @headers["Content-length"] = @content.to_s.length
- 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
-
- # 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.
- def send_body
- if @content
- send_content
- elsif @chunks
- send_chunks
- elsif @multiparts
- send_multiparts
- else
- @content = ""
- send_content
- end
- end
-
- # 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.)
- #
- 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
-
- def send_content
- raise "sent content already" if @sent_content
- @sent_content = true
- send_data((@content || "").to_s)
- end
-
- # 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).
- #
- def chunk text
- @chunks ||= []
- @chunks << text
- end
-
- # 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.
- #
- 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
-
- # 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).
- #
- def multipart arg
- vals = if arg.is_a?(String)
- {:body => arg, :headers => {}}
- else
- arg
- end
-
- @multiparts ||= []
- @multiparts << vals
- end
-
- # 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.
- #
- 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
-
- # TODO, this is going to be way too slow. Cache up the uuidgens.
- #
- 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
-
- def send_redirect location
- @status = 302 # TODO, make 301 available by parameter
- @headers["Location"] = location
- send_response
- end
- end
+ # 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.
+ #
+ class HttpResponse
+ 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"
+ }
+
+ attr_accessor :status, :content, :headers, :chunks, :multiparts
+
+ def initialize
+ @headers = {}
+ end
+
+ def keep_connection_open arg=true
+ @keep_connection_open = arg
+ end
+
+ def status=(status)
+ @status = STATUS_CODES[status.to_i] || status
+ end
+
+ # sugarings for headers
+ def content_type *mime
+ if mime.length > 0
+ @headers["Content-Type"] = mime.first.to_s
+ else
+ @headers["Content-Type"]
+ end
+ end
+
+ # 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.
+ def add_set_cookie *ck
+ if ck.length > 0
+ h = (@headers["Set-Cookie"] ||= [])
+ ck.each {|c| h << c}
+ end
+ end
+ def set_cookie *ck
+ h = (@headers["Set-Cookie"] ||= [])
+ if ck.length > 0
+ h.clear
+ add_set_cookie *ck
+ else
+ h
+ end
+ end
+
+
+ # 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.
+ 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 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.
+ #
+ 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
+
+
+ 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
+ private :generate_header_lines
+
+
+ # 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.
+ #
+ def fixup_headers
+ if @content
+ @headers["Content-Length"] = @content.to_s.length
+ 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
+
+ # 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.
+ def send_body
+ if @content
+ send_content
+ elsif @chunks
+ send_chunks
+ elsif @multiparts
+ send_multiparts
+ else
+ @content = ""
+ send_content
+ end
+ end
+
+ # 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.)
+ #
+ 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
+
+ def send_content
+ raise "sent content already" if @sent_content
+ @sent_content = true
+ send_data((@content || "").to_s)
+ end
+
+ # 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).
+ #
+ def chunk text
+ @chunks ||= []
+ @chunks << text
+ end
+
+ # 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.
+ #
+ 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
+
+ # 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).
+ #
+ def multipart arg
+ vals = if arg.is_a?(String)
+ {:body => arg, :headers => {}}
+ else
+ arg
+ end
+
+ @multiparts ||= []
+ @multiparts << vals
+ end
+
+ # 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.
+ #
+ 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
+
+ # TODO, this is going to be way too slow. Cache up the uuidgens.
+ #
+ 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
+
+ def send_redirect location
+ @status = 302 # TODO, make 301 available by parameter
+ @headers["Location"] = location
+ send_response
+ end
+ end
end
#----------------------------------------------------------------------------
require 'forwardable'
module EventMachine
- class DelegatedHttpResponse < HttpResponse
- extend Forwardable
- def_delegators :@delegate,
- :send_data,
- :close_connection,
- :close_connection_after_writing
-
- def initialize dele
- super()
- @delegate = dele
- end
- end
-end
+ class DelegatedHttpResponse < HttpResponse
+ extend Forwardable
+ def_delegators :@delegate,
+ :send_data,
+ :close_connection,
+ :close_connection_after_writing
+ def initialize dele
+ super()
+ @delegate = dele
+ end
+ end
+end
View
4 test/test_app.rb
@@ -34,7 +34,7 @@ class TestApp < Test::Unit::TestCase
TestPort = 8911
TestResponse_1 = <<EORESP
-HTTP/1.0 200 ...
+HTTP/1.0 200 OK
Content-length: 4
Content-type: text/plain
Connection: close
@@ -236,4 +236,4 @@ def test_post
assert_equal( received_content_type, content_type )
end
-end
+end
View
329 test/test_delegated.rb
@@ -17,171 +17,168 @@
class TestDelegatedHttpResponse < Test::Unit::TestCase
- # This is a delegate class that (trivially) implements the
- # several classes needed to work with HttpResponse.
- #
- class D
- attr_reader :output_data
- attr_reader :closed_after_writing
-
- def send_data data
- @output_data ||= ""
- @output_data << data
- end
- def close_connection_after_writing
- @closed_after_writing = true
- end
- end
-
- def setup
- end
-
-
- def teardown
- end
-
-
- def test_properties
- a = EM::DelegatedHttpResponse.new( D.new )
- a.status = 200
- a.content = "Some content"
- a.headers["Content-type"] = "text/xml"
- end
-
- def test_header_sugarings
- a = EM::DelegatedHttpResponse.new( D.new )
- a.content_type "text/xml"
- a.set_cookie "a=b"
- a.headers["X-bayshore"] = "aaaa"
-
- assert_equal({
- "Content-type" => "text/xml",
- "Set-cookie" => ["a=b"],
- "X-bayshore" => "aaaa"
- }, a.headers)
- end
-
- def test_send_response
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.status = 200
- a.send_response
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 0\r\n",
- "\r\n"
- ].join, d.output_data)
- assert_equal( true, d.closed_after_writing )
- end
-
- def test_send_response_1
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.status = 200
- a.content_type "text/plain"
- a.content = "ABC"
- a.send_response
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 3\r\n",
- "Content-type: text/plain\r\n",
- "\r\n",
- "ABC"
- ].join, d.output_data)
- assert( d.closed_after_writing )
- end
-
- def test_send_response_no_close
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.status = 200
- a.content_type "text/plain"
- a.content = "ABC"
- a.keep_connection_open
- a.send_response
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 3\r\n",
- "Content-type: text/plain\r\n",
- "\r\n",
- "ABC"
- ].join, d.output_data)
- assert( ! d.closed_after_writing )
- end
-
- def test_send_response_multiple_times
- a = EM::DelegatedHttpResponse.new( D.new )
- a.status = 200
- a.send_response
- assert_raise( RuntimeError ) {
- a.send_response
- }
- end
-
- def test_send_headers
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.status = 200
- a.send_headers
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 0\r\n",
- "\r\n"
- ].join, d.output_data)
- assert( ! d.closed_after_writing )
- assert_raise( RuntimeError ) {
- a.send_headers
- }
- end
-
- def test_send_chunks
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.chunk "ABC"
- a.chunk "DEF"
- a.chunk "GHI"
- a.keep_connection_open
- a.send_response
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Transfer-encoding: chunked\r\n",
- "\r\n",
- "3\r\n",
- "ABC\r\n",
- "3\r\n",
- "DEF\r\n",
- "3\r\n",
- "GHI\r\n",
- "0\r\n",
- "\r\n"
- ].join, d.output_data)
- assert( !d.closed_after_writing )
- end
-
- def test_send_chunks_with_close
- d = D.new
- a = EM::DelegatedHttpResponse.new( d )
- a.chunk "ABC"
- a.chunk "DEF"
- a.chunk "GHI"
- a.send_response
- assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Transfer-encoding: chunked\r\n",
- "\r\n",
- "3\r\n",
- "ABC\r\n",
- "3\r\n",
- "DEF\r\n",
- "3\r\n",
- "GHI\r\n",
- "0\r\n",
- "\r\n"
- ].join, d.output_data)
- assert( d.closed_after_writing )
- end
+ # This is a delegate class that (trivially) implements the
+ # several classes needed to work with HttpResponse.
+ #
+ class D
+ attr_reader :output_data
+ attr_reader :closed_after_writing
+
+ def send_data data
+ @output_data ||= ""
+ @output_data << data
+ end
+ def close_connection_after_writing
+ @closed_after_writing = true
+ end
+ end
+
+ def setup
+ end
+
+
+ def teardown
+ end
+
+
+ def test_properties
+ a = EM::DelegatedHttpResponse.new( D.new )
+ a.status = 200
+ a.content = "Some content"
+ a.headers["Content-Type"] = "text/xml"
+ end
+
+ def test_header_sugarings
+ a = EM::DelegatedHttpResponse.new( D.new )
+ a.content_type "text/xml"
+ a.set_cookie "a=b"
+ a.headers["X-bayshore"] = "aaaa"
+
+ assert_equal({
+ "Content-Type" => "text/xml",
+ "Set-Cookie" => ["a=b"],
+ "X-bayshore" => "aaaa"
+ }, a.headers)
+ end
+
+ def test_send_response
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.status = 200
+ a.send_response
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 0\r\n",
+ "\r\n"
+ ].join, d.output_data)
+ assert_equal( true, d.closed_after_writing )
+ end
+
+ def test_send_response_1
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.status = 200
+ a.content_type "text/plain"
+ a.content = "ABC"
+ a.send_response
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 3\r\n",
+ "Content-Type: text/plain\r\n",
+ "\r\n",
+ "ABC"
+ ].join, d.output_data)
+ assert( d.closed_after_writing )
+ end
+
+ def test_send_response_no_close
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.status = 200
+ a.content_type "text/plain"
+ a.content = "ABC"
+ a.keep_connection_open
+ a.send_response
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 3\r\n",
+ "Content-Type: text/plain\r\n",
+ "\r\n",
+ "ABC"
+ ].join, d.output_data)
+ assert( ! d.closed_after_writing )
+ end
+
+ def test_send_response_multiple_times
+ a = EM::DelegatedHttpResponse.new( D.new )
+ a.status = 200
+ a.send_response
+ assert_raise( RuntimeError ) {
+ a.send_response
+ }
+ end
+
+ def test_send_headers
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.status = 200
+ a.send_headers
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 0\r\n",
+ "\r\n"
+ ].join, d.output_data)
+ assert( ! d.closed_after_writing )
+ assert_raise( RuntimeError ) {
+ a.send_headers
+ }
+ end
+
+ def test_send_chunks
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.chunk "ABC"
+ a.chunk "DEF"
+ a.chunk "GHI"
+ a.keep_connection_open
+ a.send_response
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Transfer-Encoding: chunked\r\n",
+ "\r\n",
+ "3\r\n",
+ "ABC\r\n",
+ "3\r\n",
+ "DEF\r\n",
+ "3\r\n",
+ "GHI\r\n",
+ "0\r\n",
+ "\r\n"
+ ].join, d.output_data)
+ assert( !d.closed_after_writing )
+ end
+
+ def test_send_chunks_with_close
+ d = D.new
+ a = EM::DelegatedHttpResponse.new( d )
+ a.chunk "ABC"
+ a.chunk "DEF"
+ a.chunk "GHI"
+ a.send_response
+ assert_equal([
+ "HTTP/1.1 200 OK\r\n",
+ "Transfer-Encoding: chunked\r\n",
+ "\r\n",
+ "3\r\n",
+ "ABC\r\n",
+ "3\r\n",
+ "DEF\r\n",
+ "3\r\n",
+ "GHI\r\n",
+ "0\r\n",
+ "\r\n"
+ ].join, d.output_data)
+ assert( d.closed_after_writing )
+ end
end
-
-
-
View
37 test/test_response.rb
@@ -42,7 +42,7 @@ def test_properties
a = EventMachine::HttpResponse.new
a.status = 200
a.content = "Some content"
- a.headers["Content-type"] = "text/xml"
+ a.headers["Content-Type"] = "text/xml"
end
def test_header_sugarings
@@ -52,8 +52,8 @@ def test_header_sugarings
a.headers["X-bayshore"] = "aaaa"
assert_equal({
- "Content-type" => "text/xml",
- "Set-cookie" => ["a=b"],
+ "Content-Type" => "text/xml",
+ "Set-Cookie" => ["a=b"],
"X-bayshore" => "aaaa"
}, a.headers)
end
@@ -63,8 +63,8 @@ def test_send_response
a.status = 200
a.send_response
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 0\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 0\r\n",
"\r\n"
].join, a.output_data)
assert_equal( true, a.closed_after_writing )
@@ -77,9 +77,9 @@ def test_send_response_1
a.content = "ABC"
a.send_response
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 3\r\n",
- "Content-type: text/plain\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 3\r\n",
+ "Content-Type: text/plain\r\n",
"\r\n",
"ABC"
].join, a.output_data)
@@ -94,9 +94,9 @@ def test_send_response_no_close
a.keep_connection_open
a.send_response
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 3\r\n",
- "Content-type: text/plain\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 3\r\n",
+ "Content-Type: text/plain\r\n",
"\r\n",
"ABC"
].join, a.output_data)
@@ -117,8 +117,8 @@ def test_send_headers
a.status = 200
a.send_headers
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Content-length: 0\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Content-Length: 0\r\n",
"\r\n"
].join, a.output_data)
assert( ! a.closed_after_writing )
@@ -135,8 +135,8 @@ def test_send_chunks
a.keep_connection_open
a.send_response
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Transfer-encoding: chunked\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Transfer-Encoding: chunked\r\n",
"\r\n",
"3\r\n",
"ABC\r\n",
@@ -157,8 +157,8 @@ def test_send_chunks_with_close
a.chunk "GHI"
a.send_response
assert_equal([
- "HTTP/1.1 200 ...\r\n",
- "Transfer-encoding: chunked\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "Transfer-Encoding: chunked\r\n",
"\r\n",
"3\r\n",
"ABC\r\n",
@@ -173,6 +173,3 @@ def test_send_chunks_with_close
end
end
-
-
-

0 comments on commit 8341835

Please sign in to comment.
Something went wrong with that request. Please try again.