Class: FTW::Request

Inherits:
Object
  • Object
show all
Includes:
Cabin::Inspectable, CRLF, HTTP::Message, Protocol
Defined in:
lib/ftw/request.rb

Overview

An HTTP Request.

See RFC2616 section 5: <tools.ietf.org/html/rfc2616#section-5>

Constant Summary

Constants included from CRLF

CRLF::CRLF

Constants included from HTTP::Message

HTTP::Message::UnsupportedHTTPVersion, HTTP::Message::VALID_VERSIONS

Instance Attribute Summary collapse

Attributes included from HTTP::Message

#headers, #version

Instance Method Summary collapse

Methods included from Protocol

#discard_body, #encode_chunked, #read_body, #read_http_body, #read_http_body_chunked, #read_http_body_length, #read_http_message, #write_all, #write_http_body, #write_http_body_chunked, #write_http_body_normal

Methods included from HTTP::Message

#body, #body=, #body?, #content?, #to_s

Instance Attribute Details

#methodObject

The http method. Like GET, PUT, POST, etc.. RFC2616 5.1.1 - <tools.ietf.org/html/rfc2616#section-5.1.1>

Warning: this accessor obscures the ruby Kernel#method() method. I would like to call this ‘verb’, but my preference is first to adhere to RFC terminology. Further, ruby’s stdlib Net::HTTP calls this ‘method’ as well (See Net::HTTPGenericRequest).



29
30
31
# File 'lib/ftw/request.rb', line 29

def method
  @method
end

#portObject

RFC2616 section 14.23 allows the Host header to include a port, but I have never seen this in practice, and I shudder to think about what poorly-behaving web servers will barf if the Host header includes a port. So, instead of storing the port in the Host header, it is stored here. It is not included in the Request when sent from a client and it is not used on a server.



44
45
46
# File 'lib/ftw/request.rb', line 44

def port
  @port
end

#protocolObject

This is not an RFC2616 field. It exists so that the connection handling this request knows what protocol to use. The protocol for this request. Usually ‘http’ or ‘https’ or perhaps ‘spdy’ maybe?



49
50
51
# File 'lib/ftw/request.rb', line 49

def protocol
  @protocol
end

#request_uriObject Also known as: path

This is the Request-URI. Many people call this the ‘path’ of the request. RFC2616 5.1.2 - <tools.ietf.org/html/rfc2616#section-5.1.2>



33
34
35
# File 'lib/ftw/request.rb', line 33

def request_uri
  @request_uri
end

Instance Method Details

#execute(connection) ⇒ Object

Execute this request on a given connection: Writes the request, returns a Response object.

This method will block until the HTTP response header has been completely received. The body will not have been read yet at the time of this method’s return.

The ‘connection’ should be a FTW::Connection instance, but it might work with a normal IO object.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ftw/request.rb', line 73

def execute(connection)
  tries = 3
  begin
    connection.write(to_s + CRLF)
    if body?
      write_http_body(body, connection,
                      headers["Transfer-Encoding"] == "chunked") 
    end
  rescue => e
    # TODO(sissel): Rescue specific exceptions, not just anything.
    # Reconnect and retry
    if tries > 0
      tries -= 1
      connection.connect
      retry
    else
      raise e
    end
  end

  response = read_http_message(connection)
  # TODO(sissel): make sure we got a response, not a request, cuz that'd be weird.
  return response
end

#request_lineObject Also known as: start_line

Get the request line (first line of the http request) From the RFC: Request-Line = Method SP Request-URI SP HTTP-Version CRLF

Note: I skip the trailing CRLF. See the to_s method where it is provided.



145
146
147
# File 'lib/ftw/request.rb', line 145

def request_line
  return "#{method} #{request_uri} HTTP/#{version}"
end

#use_uri(uri) ⇒ Object

Use a URI to help fill in parts of this Request.



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
# File 'lib/ftw/request.rb', line 99

def use_uri(uri)
  # Convert URI objects to Addressable::URI
  case uri
    when URI, String
      uri = Addressable::URI.parse(uri.to_s)
  end

  # TODO(sissel): Use uri.password and uri.user to set Authorization basic
  # stuff.
  if uri.password || uri.user
    encoded = Base64.strict_encode64("#{uri.user}:#{uri.password}")
    @headers.set("Authorization", "Basic #{encoded}")
  end
  # uri.password
  # uri.user
  @request_uri = uri.path
  # Include the query string, too.
  @request_uri += "?#{uri.query}" if !uri.query.nil?

  @headers.set("Host", uri.host)
  @protocol = uri.scheme
  if uri.port.nil?
    # default to port 80
    uri.port = { "http" => 80, "https" => 443 }.fetch(uri.scheme, 80)
  end
  @port = uri.port
  
  # TODO(sissel): support authentication
end