Module: FTW::Protocol
- Includes:
- CRLF
- Included in:
- Agent, Request, Response, WebServer, Rack::Handler::FTW
- Defined in:
- lib/ftw/protocol.rb
Overview
This module provides web protocol handling as a mixin.
Constant Summary
Constants included from CRLF
Instance Method Summary collapse
-
#discard_body ⇒ Object
A shorthand for discarding the body of a request or response.
-
#encode_chunked(text) ⇒ Object
Encode the given text as in ‘chunked’ encoding.
-
#read_body(&block) ⇒ Object
Read the body of this message.
-
#read_http_body(&block) ⇒ Object
Read the body of this message.
-
#read_http_body_chunked(&block) ⇒ Object
This is kind of messed, need to fix it.
-
#read_http_body_length(length, &block) ⇒ Object
Read the length bytes from the body.
-
#read_http_message(connection) ⇒ Object
Read an HTTP message from a given connection.
-
#write_http_body(body, io, chunked = false) ⇒ Object
def read_http_message.
-
#write_http_body_chunked(body, io) ⇒ Object
def encode_chunked.
-
#write_http_body_normal(body, io) ⇒ Object
def write_http_body_chunked.
Instance Method Details
#discard_body ⇒ Object
A shorthand for discarding the body of a request or response.
This is the same as:
foo.read_body { |c| }
142 143 144 |
# File 'lib/ftw/protocol.rb', line 142 def discard_body read_body { |c| } end |
#encode_chunked(text) ⇒ Object
Encode the given text as in ‘chunked’ encoding.
71 72 73 |
# File 'lib/ftw/protocol.rb', line 71 def encode_chunked(text) return sprintf("%x%s%s%s", text.bytesize, CRLF, text, CRLF) end |
#read_body(&block) ⇒ Object
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.
If no block is given, the entire response body is returned as a string.
127 128 129 130 131 132 133 134 135 |
# File 'lib/ftw/protocol.rb', line 127 def read_body(&block) if !block_given? content = "" read_http_body { |chunk| content << chunk } return content else read_http_body(&block) end end |
#read_http_body(&block) ⇒ Object
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.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ftw/protocol.rb', line 104 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? block.call(@body) end end |
#read_http_body_chunked(&block) ⇒ Object
This is kind of messed, need to fix it.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/ftw/protocol.rb', line 166 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. = proc { done = true } while !done # will break on special conditions below # TODO(sissel): In JRuby, this read will sometimes hang for ever # because there's some wonkiness in IO.select on SSLSockets in JRuby. # Maybe we should fix it... data = @body.read offset = parser << data if offset != data.length raise "Parser did not consume all data read?" end end end |
#read_http_body_length(length, &block) ⇒ Object
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.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/ftw/protocol.rb', line 148 def read_http_body_length(length, &block) remaining = length while remaining > 0 data = @body.read(remaining) @logger.debug("Read bytes", :length => data.bytesize) if data.bytesize > 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.bytesize end end end |
#read_http_message(connection) ⇒ Object
Read an HTTP message from a given connection
This method blocks until a full http message header has been consumed (request or response)
The body of the message, if any, will not be consumed, and the read position for the connection will be left at the end of the message headers.
The ‘connection’ object must respond to #read(timeout) and #pushback(string)
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 |
# File 'lib/ftw/protocol.rb', line 19 def (connection) parser = HTTP::Parser.new headers_done = false parser.on_headers_complete = proc { headers_done = true; :stop } # headers_done will be set to true when parser finishes parsing the http # headers for this request while !headers_done # TODO(sissel): This read could toss an exception of the server aborts # prior to sending the full headers. Figure out a way to make this happy. # Perhaps fabricating a 500 response? data = connection.read(16384) # Feed the data into the parser. Offset will be nonzero if there's # extra data beyond the header. offset = parser << data end # If we consumed part of the body while parsing headers, put it back # onto the connection's read buffer so the next consumer can use it. if offset < data.length connection.pushback(data[offset .. -1]) end # This will have an 'http_method' if it's a request if !parser.http_method.nil? # have http_method, so this is an HTTP Request message request = FTW::Request.new request.method = parser.http_method request.request_uri = parser.request_url request.version = "#{parser.http_major}.#{parser.http_minor}".to_f parser.headers.each { |field, value| request.headers.add(field, value) } return request else # otherwise, no http_method, so this is an HTTP Response message response = FTW::Response.new response.version = "#{parser.http_major}.#{parser.http_minor}".to_f response.status = parser.status_code parser.headers.each { |field, value| response.headers.add(field, value) } return response end end |
#write_http_body(body, io, chunked = false) ⇒ Object
def read_http_message
62 63 64 65 66 67 68 |
# File 'lib/ftw/protocol.rb', line 62 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 |
#write_http_body_chunked(body, io) ⇒ Object
def encode_chunked
75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/ftw/protocol.rb', line 75 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 |
#write_http_body_normal(body, io) ⇒ Object
def write_http_body_chunked
90 91 92 93 94 95 96 97 98 |
# File 'lib/ftw/protocol.rb', line 90 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 |