lib/ftw/protocol.rb in ftw-0.0.13 vs lib/ftw/protocol.rb in ftw-0.0.14
- old
+ new
@@ -1,11 +1,14 @@
require "ftw/namespace"
+require "ftw/crlf"
require "cabin"
require "logger"
# This module provides web protocol handling as a mixin.
module FTW::Protocol
+ include FTW::CRLF
+
# Read an HTTP message from a given connection
#
# This method blocks until a full http message header has been consumed
# (request *or* response)
#
@@ -54,7 +57,106 @@
parser.headers.each { |field, value| response.headers.add(field, value) }
return response
end
end # def read_http_message
- public(:read_http_message)
+ def write_http_body(body, io, chunked=false)
+ if chunked
+ write_http_body_chunked(body, io)
+ else
+ write_http_body_normal(body, io)
+ end
+ end # def write_http_body
+
+ # Encode the given text as in 'chunked' encoding.
+ def encode_chunked(text)
+ return sprintf("%x%s%s%s", text.size, CRLF, text, CRLF)
+ end # def encode_chunked
+
+ def write_http_body_chunked(body, io)
+ if body.is_a?(String)
+ io.write(encode_chunked(body))
+ elsif body.respond_to?(:sysread)
+ true while io.write(encode_chunked(body.sysread(16384)))
+ elsif body.respond_to?(:read)
+ true while io.write(encode_chunked(body.read(16384)))
+ elsif body.respond_to?(:each)
+ body.each { |s| io.write(encode_chunked(s)) }
+ end
+
+ # The terminating chunk is an empty one.
+ io.write(encode_chunked(""))
+ end # def write_http_body_chunked
+
+ def write_http_body_normal(body, io)
+ if body.is_a?(String)
+ io.write(body)
+ elsif body.respond_to?(:read)
+ true while io.write(body.read(16384))
+ elsif body.respond_to?(:each)
+ body.each { |s| io.write(s) }
+ end
+ end # def write_http_body_normal
+
+ # Read the body of this message. The block is called with chunks of the
+ # response as they are read in.
+ #
+ # This method is generally only called by http clients, not servers.
+ def read_http_body(&block)
+ if @body.respond_to?(:read)
+ if headers.include?("Content-Length") and headers["Content-Length"].to_i > 0
+ @logger.debug("Reading body with Content-Length")
+ read_http_body_length(headers["Content-Length"].to_i, &block)
+ elsif headers["Transfer-Encoding"] == "chunked"
+ @logger.debug("Reading body with chunked encoding")
+ read_http_body_chunked(&block)
+ end
+
+ # If this is a poolable resource, release it (like a FTW::Connection)
+ @body.release if @body.respond_to?(:release)
+ elsif !@body.nil?
+ yield @body
+ end
+ end # def read_http_body
+
+ # Old api compat
+ alias_method :read_body, :read_http_body
+
+ # Read the length bytes from the body. Yield each chunk read to the block
+ # given. This method is generally only called by http clients, not servers.
+ def read_http_body_length(length, &block)
+ remaining = length
+ while remaining > 0
+ data = @body.read
+ @logger.debug("Read bytes", :length => data.size)
+ if data.size > remaining
+ # Read too much data, only wanted part of this. Push the rest back.
+ yield data[0..remaining]
+ remaining = 0
+ @body.pushback(data[remaining .. -1]) if remaining < 0
+ else
+ yield data
+ remaining -= data.size
+ end
+ end
+ end # def read_http_body_length
+
+ # This is kind of messed, need to fix it.
+ def read_http_body_chunked(&block)
+ parser = HTTP::Parser.new
+
+ # Fake fill-in the response we've already read into the parser.
+ parser << to_s
+ parser << CRLF
+ parser.on_body = block
+ done = false
+ parser.on_message_complete = proc { done = true }
+
+ while !done # will break on special conditions below
+ data = @body.read
+ offset = parser << data
+ if offset != data.length
+ raise "Parser did not consume all data read?"
+ end
+ end
+ end # def read_http_body_chunked
end # module FTW::Protocol