Class: FTW::Agent

Inherits:
Object
  • Object
show all
Includes:
Protocol
Defined in:
lib/ftw/agent.rb

Overview

This should act as a proper web agent.

  • Reuse connections.

  • SSL/TLS.

  • HTTP Upgrade support.

  • HTTP 1.1 (RFC2616).

  • WebSockets (RFC6455).

  • Support Cookies.

All standard HTTP methods defined by RFC2616 are available as methods on this agent: get, head, put, etc.

Example:

agent = FTW::Agent.new
request = agent.get("http://www.google.com/")
response = agent.execute(request)
puts response.body.read

For any standard http method (like ‘get’) you can invoke it with ‘!’ on the end and it will execute and return a FTW::Response object:

agent = FTW::Agent.new
response = agent.get!("http://www.google.com/")
puts response.body.head

TODO(sissel): TBD: implement cookies… delicious chocolate chip cookies.

Constant Summary collapse

STANDARD_METHODS =

List of standard HTTP methods described in RFC2616

%w(options get head post put delete trace connect)

Instance Method Summary collapse

Methods included from Protocol

#read_http_message

Constructor Details

#initializeAgent

Returns a new instance of Agent.



48
49
50
51
52
53
54
55
# File 'lib/ftw/agent.rb', line 48

def initialize
  @pool = FTW::Pool.new
  @logger = Cabin::Channel.get($0)
  @logger.subscribe(Logger.new(STDOUT))
  @logger.level = :warn

  @redirect_max = 20
end

Instance Method Details

#execute(request) ⇒ FTW::Response

Execute a FTW::Request in this Agent.

If an existing, idle connection is already open to the target server of this Request, it will be reused. Otherwise, a new connection is opened.

Redirects are always followed.

Parameters:

Returns:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ftw/agent.rb', line 158

def execute(request)
  # TODO(sissel): Make redirection-following optional, but default.

  connection, error = connect(request.headers["Host"], request.port)
  if !error.nil?
    p :error => error
    raise error
  end
  connection.secure if request.protocol == "https"
  response = request.execute(connection)

  redirects = 0
  while response.redirect? and response.headers.include?("Location")
    redirects += 1
    if redirects > @redirect_max
      # TODO(sissel): Abort somehow...
    end
    # RFC2616 section 10.3.3 indicates HEAD redirects must not include a
    # body. Otherwise, the redirect response can have a body, so let's
    # throw it away.
    if request.method == "HEAD" 
      # Head requests have no body
      connection.release
    elsif response.content?
      # Throw away the body
      response.body = connection
      # read_body will release the connection
      response.read_body { |chunk| }
    end

    # TODO(sissel): If this response has any cookies, store them in the
    # agent's cookie store

    @logger.debug("Redirecting", :location => response.headers["Location"])
    redirects += 1
    request.use_uri(response.headers["Location"])
    connection, error = connect(request.headers["Host"], request.port)
    # TODO(sissel): Do better error handling than raising.
    if !error.nil?
      p :error => error
      raise error
    end
    connection.secure if request.protocol == "https"
    response = request.execute(connection)
  end

  # RFC 2616 section 9.4, HEAD requests MUST NOT have a message body.
  if request.method != "HEAD"
    response.body = connection
  else
    connection.release
  end
 
  # TODO(sissel): If this response has any cookies, store them in the
  # agent's cookie store
  return response
end

#upgrade!(uri, protocol, options = {}) ⇒ Object

Send the request as an HTTP upgrade.

Returns the response and the FTW::Connection for this connection. If the upgrade was denied, the connection returned will be nil.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ftw/agent.rb', line 85

def upgrade!(uri, protocol, options={})
  req = request("GET", uri, options)
  req.headers["Connection"] = "Upgrade"
  req.headers["Upgrade"] = protocol
  response = execute(req)
  if response.status == 101
    return response, response.body
  else
    return response, nil
  end
end

#websocket!(uri, options = {}) ⇒ Object

Make a new websocket connection.

This will send the http request. If the websocket handshake is successful, a FTW::WebSocket instance will be returned. Otherwise, a FTW::Response will be returned.

See #request for what the ‘uri’ and ‘options’ parameters should be.



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ftw/agent.rb', line 104

def websocket!(uri, options={})
  # TODO(sissel): Use FTW::Agent#upgrade! ?
  req = request("GET", uri, options)
  ws = FTW::WebSocket.new(req)
  response = execute(req)
  if ws.handshake_ok?(response)
    # response.body is a FTW::Connection
    ws.connection = response.body
    return ws
  else
    return response
  end
end