1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
require "ftw/namespace"
require "ftw/http/headers"
require "ftw/crlf"
# HTTP Message, RFC2616
# For specification, see RFC2616 section 4: <http://tools.ietf.org/html/rfc2616#section-4>
#
# You probably won't use this class much. Instead, check out {FTW::Request} and {FTW::Response}
module FTW::HTTP::Message
include FTW::CRLF
# The HTTP headers - See {FTW::HTTP::Headers}.
# RFC2616 5.3 - <http://tools.ietf.org/html/rfc2616#section-5.3>
attr_reader :headers
# The HTTP version. See {VALID_VERSIONS} for valid versions.
# This will always be a Numeric object.
# Both Request and Responses have version, so put it in the parent class.
attr_accessor :version
# HTTP Versions that are valid.
VALID_VERSIONS = [1.0, 1.1]
# For backward-compatibility, this exception inherits from ArgumentError
UnsupportedHTTPVersion = Class.new(ArgumentError)
private
# A new HTTP message.
def initialize
@headers = FTW::HTTP::Headers.new
@body = nil
end # def initialize
# Get a header value by field name.
#
# @param [String] the name of the field. (case insensitive)
def [](field)
return @headers[field]
end # def []
# Set a header field
#
# @param [String] the name of the field. (case insensitive)
# @param [String] the value to set for this field
def []=(field, value)
@headers[field] = value
end # def []=
# Set the body of this message
#
# The 'message_body' can be an IO-like object, Enumerable, or String.
#
# See RFC2616 section 4.3: <http://tools.ietf.org/html/rfc2616#section-4.3>
def body=(message_body)
# TODO(sissel): if message_body is a string, set Content-Length header
# TODO(sissel): if it's an IO object, set Transfer-Encoding to chunked
# TODO(sissel): if it responds to each or appears to be Enumerable, then
# set Transfer-Encoding to chunked.
@body = message_body
# don't set any additional length/encoding headers if they are already set.
return if headers.include?("Content-Length") or headers.include?("Transfer-Encoding")
if (message_body.respond_to?(:read) or message_body.respond_to?(:each)) and
headers["Transfer-Encoding"] = "chunked"
else
headers["Content-Length"] = message_body.bytesize
end
end # def body=
# Get the body of this message
#
# Returns an Enumerable, IO-like object, or String, depending on how this
# message was built.
def body
# TODO(sissel): verification todos follow...
# TODO(sissel): RFC2616 section 4.3 - if there is a message body
# then one of "Transfer-Encoding" *or* "Content-Length" MUST be present.
# otherwise, if neither header is present, no body is present.
# TODO(sissel): Responses to HEAD requests or those with status 1xx, 204,
# or 304 MUST NOT have a body. All other requests have a message body,
# even if that body is of zero length.
return @body
end # def body
# Should this message have a content?
#
# In HTTP 1.1, there is a body if response sets Content-Length *or*
# Transfer-Encoding, it has a body. Otherwise, there is no body.
def content?
return (headers.include?("Content-Length") and headers["Content-Length"].to_i > 0) \
|| headers.include?("Transfer-Encoding")
end # def content?
# Does this message have a body?
def body?
return !@body.nil?
end # def body?
# Set the HTTP version. Must be a valid version. See VALID_VERSIONS.
def version=(ver)
# Accept string "1.0" or simply "1", etc.
ver = ver.to_f if !ver.is_a?(Float)
if !VALID_VERSIONS.include?(ver)
raise UnsupportedHTTPVersion.new("#{self.class.name}#version = #{ver.inspect} is" \
"invalid. It must be a number, one of #{VALID_VERSIONS.join(", ")}")
end
@version = ver
end # def version=
# Serialize this Request according to RFC2616
# Note: There is *NO* trailing CRLF. This is intentional.
# The RFC defines:
# generic-message = start-line
# *(message-header CRLF)
# CRLF
# [ message-body ]
# Thus, the CRLF between header and body is not part of the header.
def to_s
return [start_line, @headers].join(CRLF)
end
public(:initialize, :headers, :version, :version=, :[], :[]=, :body=, :body,
:content?, :body?, :to_s)
end # class FTW::HTTP::Message
|