File: request.rb

package info (click to toggle)
ruby-ftw 0.0.49-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 548 kB
  • sloc: ruby: 1,922; makefile: 5
file content (155 lines) | stat: -rw-r--r-- 5,287 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
require "addressable/uri" # gem addressable
require "cabin" # gem cabin
require "ftw/crlf"
require "ftw/http/message"
require "ftw/namespace"
require "ftw/response"
require "ftw/protocol"
require "uri" # ruby stdlib
require "base64" # ruby stdlib

# An HTTP Request.
#
# See RFC2616 section 5: <http://tools.ietf.org/html/rfc2616#section-5>
class FTW::Request
  include FTW::HTTP::Message
  include FTW::Protocol
  include FTW::CRLF
  include Cabin::Inspectable

  private

  # The http method. Like GET, PUT, POST, etc..
  # RFC2616 5.1.1 - <http://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).
  attr_accessor :method

  # This is the Request-URI. Many people call this the 'path' of the request.
  # RFC2616 5.1.2 - <http://tools.ietf.org/html/rfc2616#section-5.1.2>
  attr_accessor :request_uri

  # Lemmings. Everyone else calls Request-URI the 'path' (including me, most of
  # the time), so let's just follow along.
  alias_method :path, :request_uri

  # 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.
  attr_accessor :port

  # 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?
  attr_accessor :protocol

  # Make a new request with a uri if given.
  #
  # The uri is used to set the address, protocol, Host header, etc.
  def initialize(uri=nil)
    super()
    @port = 80
    @protocol = "http"
    @version = 1.1
    use_uri(uri) if !uri.nil?
    @logger = Cabin::Channel.get
  end # def initialize

  # 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.
  #
  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 # def execute

  # Use a URI to help fill in parts of this Request.
  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 # def use_uri

  # Set the method for this request. Usually something like "GET" or "PUT"
  # etc. See <http://tools.ietf.org/html/rfc2616#section-5.1.1>
  def method=(method)
    # RFC2616 5.1.1 doesn't say the method has to be uppercase.
    # It can be any 'token' besides the ones defined in section 5.1.1:
    # The grammar for 'token' is:
    #          token          = 1*<any CHAR except CTLs or separators>
    # TODO(sissel): support section 5.1.1 properly. Don't upcase, but 
    # maybe upcase things that are defined in 5.1.1 like GET, etc.
    @method = method.upcase
  end # def method=

  # 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.
  def request_line
    return "#{method} #{request_uri} HTTP/#{version}"
  end # def request_line

  # Define the Message's start_line as request_line
  alias_method :start_line, :request_line

  public(:method, :method=, :request_uri, :request_uri=, :path, :port, :port=,
         :protocol, :protocol=, :execute, :use_uri, :request_line, :start_line)

end # class FTW::Request < Message