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_all(io, string) ⇒ Object
def write_http_body_normal.
-
#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| }
158 159 160 |
# File 'lib/ftw/protocol.rb', line 158 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.
143 144 145 146 147 148 149 150 151 |
# File 'lib/ftw/protocol.rb', line 143 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.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/ftw/protocol.rb', line 120 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.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/ftw/protocol.rb', line 182 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.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/ftw/protocol.rb', line 164 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_all(io, string) ⇒ Object
def write_http_body_normal
109 110 111 112 113 114 |
# File 'lib/ftw/protocol.rb', line 109 def write_all(io, string) while string.bytesize > 0 w = io.write(string) string = string.byteslice(w..-1) 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 89 90 91 92 93 94 95 |
# File 'lib/ftw/protocol.rb', line 75 def write_http_body_chunked(body, io) if body.is_a?(String) write_all( io, encode_chunked(body)) elsif body.respond_to?(:sysread) begin while cont = body.sysread(16384) write_all( io, encode_chunked(cont)) end rescue EOFError end elsif body.respond_to?(:read) while cont = body.read(16384) write_all( io, encode_chunked(cont) ) end elsif body.respond_to?(:each) body.each { |s| write_all( io, encode_chunked(s)) } end # The terminating chunk is an empty one. write_all(io, encode_chunked("")) end |
#write_http_body_normal(body, io) ⇒ Object
def write_http_body_chunked
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ftw/protocol.rb', line 97 def write_http_body_normal(body, io) if body.is_a?(String) write_all(io, body) elsif body.respond_to?(:read) while cont = body.read(16384) write_all(io, cont) end elsif body.respond_to?(:each) body.each { |s| write_all( io, s) } end end |