lib/ftw/agent.rb in ftw-0.0.11 vs lib/ftw/agent.rb in ftw-0.0.13
- old
+ new
@@ -4,10 +4,11 @@
require "ftw/protocol"
require "ftw/pool"
require "ftw/websocket"
require "addressable/uri"
require "cabin"
+require "openssl"
# This should act as a proper web agent.
#
# * Reuse connections.
# * SSL/TLS.
@@ -34,11 +35,21 @@
# puts response.body.head
#
# TODO(sissel): TBD: implement cookies... delicious chocolate chip cookies.
class FTW::Agent
include FTW::Protocol
+ require "ftw/agent/configuration"
+ include FTW::Agent::Configuration
+ class TooManyRedirects < StandardError
+ attr_accessor :response
+ def initialize(reason, response)
+ super(reason)
+ @response = response
+ end
+ end
+
# List of standard HTTP methods described in RFC2616
STANDARD_METHODS = %w(options get head post put delete trace connect)
# Everything is private by default.
# At the bottom of this class, public methods will be declared.
@@ -46,11 +57,19 @@
def initialize
@pool = FTW::Pool.new
@logger = Cabin::Channel.get
- @redirect_max = 20
+ configuration[REDIRECTION_LIMIT] = 20
+
+ @certificate_store = OpenSSL::X509::Store.new
+ @certificate_store.add_file("/etc/ssl/certs/ca-bundle.trust.crt")
+ @certificate_store.verify_callback = proc do |*args|
+ p :verify_callback => args
+ true
+ end
+
end # def initialize
# Define all the standard HTTP methods (Per RFC2616)
# As an example, for "get" method, this will define these methods:
#
@@ -137,10 +156,14 @@
options[:headers].each do |key, value|
request.headers.add(key, value)
end
end
+ if options.include?(:body)
+ request.body = options[:body]
+ end
+
return request
end # def request
# Execute a FTW::Request in this Agent.
#
@@ -158,47 +181,58 @@
connection, error = connect(request.headers["Host"], request.port)
if !error.nil?
p :error => error
raise error
end
- connection.secure if request.protocol == "https"
+
+ if request.protocol == "https"
+ connection.secure(:certificate_store => @certificate_store)
+ end
response = request.execute(connection)
redirects = 0
+ # Follow redirects
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
+ # read_body will consume the body and release this 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
+ if redirects > configuration[REDIRECTION_LIMIT]
+ # TODO(sissel): include original a useful debugging information like
+ # the trace of redirections, etc.
+ raise TooManyRedirects.new("Redirect more than " \
+ "#{configuration[REDIRECTION_LIMIT]} times, aborting.", response)
+ # I don't like this api from FTW::Agent. I think 'get' and other methods
+ # should return (object, error), and if there's an error
+ end
+
+ @logger.debug("Redirecting", :location => response.headers["Location"])
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"
+ if request.protocol == "https"
+ connection.secure(:certificate_store => @certificate_store)
+ end
response = request.execute(connection)
- end
+ end # while being redirected
# RFC 2616 section 9.4, HEAD requests MUST NOT have a message body.
if request.method != "HEAD"
response.body = connection
else
@@ -247,7 +281,11 @@
@logger.debug("Pool fetched a connection", :connection => connection)
connection.mark
return connection, nil
end # def connect
+ # TODO(sissel): Implement methods for managing the certificate store
+ # TODO(sissel): Implement methods for managing the cookie store
+ # TODO(sissel): Implement methods for managing the cache
+ # TODO(sissel): Implement configuration stuff? Is FTW::Agent::Configuration the best way?
public(:initialize, :execute, :websocket!, :upgrade!, :shutdown)
end # class FTW::Agent