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

CRLF::CRLF

Instance Method Summary collapse

Instance Method Details

#discard_bodyObject

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.on_message_complete = 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 read_http_message(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