class HTTP::Client

Clients make requests and receive responses

def finish_response

Callback for when we've reached the end of a response
def finish_response
  @socket.close if @socket && !@socket.closed?
  @parser.reset
  @socket = nil
end

def initialize(default_options = {})

def initialize(default_options = {})
  @default_options = HTTP::Options.new(default_options)
  @parser = HTTP::Response::Parser.new
  @socket = nil
end

def make_request_body(opts, headers)

Create the request body object to send
def make_request_body(opts, headers)
  if opts.body
    opts.body
  elsif opts.form
    headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
    URI.encode_www_form(opts.form)
  elsif opts.json
    headers['Content-Type'] ||= 'application/json'
    MimeType[:json].encode opts.json
  end
end

def make_request_uri(uri, options)

Merges query params if needed
def make_request_uri(uri, options)
  uri = URI uri.to_s unless uri.is_a? URI
  if options.params && !options.params.empty?
    params    = CGI.parse(uri.query.to_s).merge(options.params || {})
    uri.query = URI.encode_www_form params
  end
  uri
end

def perform(req, options)

Perform a single (no follow) HTTP request
def perform(req, options)
  # finish previous response if client was re-used
  # TODO: this is pretty wrong, as socket shoud be part of response
  #       connection, so that re-use of client will not break multiple
  #       chunked responses
  finish_response
  uri = req.uri
  # TODO: keep-alive support
  @socket = options[:socket_class].open(req.socket_host, req.socket_port)
  @socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS)
  req.stream @socket
  begin
    read_more BUFFER_SIZE until @parser.headers
  rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex
    raise IOError, "problem making HTTP request: #{ex}"
  end
  body = Response::Body.new(self)
  res  = Response.new(@parser.status_code, @parser.http_version, @parser.headers, body, uri)
  finish_response if :head == req.verb
  res
end

def read_more(size)

Feeds some more data into parser
def read_more(size)
  @parser << @socket.readpartial(size) unless @parser.finished?
  true
rescue EOFError
  false
end

def readpartial(size = BUFFER_SIZE)

Read a chunk of the body
def readpartial(size = BUFFER_SIZE)
  return unless @socket
  read_more size
  chunk = @parser.chunk
  finish_response if @parser.finished?
  chunk
end

def request(verb, uri, opts = {})

Make an HTTP request
def request(verb, uri, opts = {})
  opts    = @default_options.merge(opts)
  uri     = make_request_uri(uri, opts)
  headers = opts.headers
  proxy   = opts.proxy
  body    = make_request_body(opts, headers)
  req = HTTP::Request.new(verb, uri, headers, proxy, body)
  res = perform req, opts
  if opts.follow
    res = Redirector.new(opts.follow).perform req, res do |request|
      perform request, opts
    end
  end
  res
end

def start_tls(socket, options)

Initialize TLS connection
def start_tls(socket, options)
  # TODO: abstract away SSLContexts so we can use other TLS libraries
  context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new
  socket  = options[:ssl_socket_class].new(socket, context)
  socket.connect
  socket
end