class HTTP::Connection

A connection to the HTTP server

Constants

BUFFER_SIZE

Attempt to read this much data

CLOSE
HTTP_1_0

HTTP/1.0

HTTP_1_1

HTTP/1.1

KEEP_ALIVE

Allowed values for CONNECTION header

Attributes

proxy_response_headers[R]

Returned after HTTP CONNECT (via proxy)

Public Class Methods

new(req, options) click to toggle source

@param [HTTP::Request] req @param [HTTP::Options] options @raise [HTTP::ConnectionError] when failed to connect

# File lib/http/connection.rb, line 31
def initialize(req, options)
  @persistent           = options.persistent?
  @keep_alive_timeout   = options.keep_alive_timeout.to_f
  @pending_request      = false
  @pending_response     = false
  @failed_proxy_connect = false
  @buffer               = "".b

  @parser = Response::Parser.new

  @socket = options.timeout_class.new(options.timeout_options)
  @socket.connect(options.socket_class, req.socket_host, req.socket_port, options.nodelay)

  send_proxy_connect_request(req)
  start_tls(req, options)
  reset_timer
rescue IOError, SocketError, SystemCallError => e
  raise ConnectionError, "failed to connect: #{e}", e.backtrace
end

Public Instance Methods

close() click to toggle source

Close the connection @return [void]

# File lib/http/connection.rb, line 128
def close
  @socket.close unless @socket.closed?

  @pending_response = false
  @pending_request  = false
end
expired?() click to toggle source

Whether our connection has expired @return [Boolean]

# File lib/http/connection.rb, line 143
def expired?
  !@conn_expires_at || @conn_expires_at < Time.now
end
failed_proxy_connect?() click to toggle source

@return [Boolean] whenever proxy connect failed

# File lib/http/connection.rb, line 61
def failed_proxy_connect?
  @failed_proxy_connect
end
finish_response() click to toggle source

Callback for when we’ve reached the end of a response @return [void]

# File lib/http/connection.rb, line 116
def finish_response
  close unless keep_alive?

  @parser.reset
  @socket.reset_counter if @socket.respond_to?(:reset_counter)
  reset_timer

  @pending_response = false
end
keep_alive?() click to toggle source

Whether we’re keeping the conn alive @return [Boolean]

# File lib/http/connection.rb, line 137
def keep_alive?
  !!@keep_alive && !@socket.closed?
end
read_headers!() click to toggle source

Reads data from socket up until headers are loaded @return [void]

# File lib/http/connection.rb, line 105
def read_headers!
  until @parser.headers?
    result = read_more(BUFFER_SIZE)
    raise ConnectionError, "couldn't read response headers" if result == :eof
  end

  set_keep_alive
end
readpartial(size = BUFFER_SIZE) click to toggle source

Read a chunk of the body

@return [String] data chunk @return [nil] when no more data left

# File lib/http/connection.rb, line 90
def readpartial(size = BUFFER_SIZE)
  return unless @pending_response

  chunk = @parser.read(size)
  return chunk if chunk

  finished = (read_more(size) == :eof) || @parser.finished?
  chunk    = @parser.read(size)
  finish_response if finished

  chunk || "".b
end
send_request(req) click to toggle source

Send a request to the server

@param [Request] req Request to send to the server @return [nil]

# File lib/http/connection.rb, line 69
def send_request(req)
  if @pending_response
    raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body."
  end

  if @pending_request
    raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body."
  end

  @pending_request = true

  req.stream @socket

  @pending_response = true
  @pending_request  = false
end

Private Instance Methods

read_more(size) click to toggle source

Feeds some more data into parser @return [void]

# File lib/http/connection.rb, line 213
def read_more(size)
  return if @parser.finished?

  value = @socket.readpartial(size, @buffer)
  if value == :eof
    @parser << ""
    :eof
  elsif value
    @parser << value
  end
rescue IOError, SocketError, SystemCallError => e
  raise ConnectionError, "error reading from socket: #{e}", e.backtrace
end
reset_timer() click to toggle source

Resets expiration of persistent connection. @return [void]

# File lib/http/connection.rb, line 190
def reset_timer
  @conn_expires_at = Time.now + @keep_alive_timeout if @persistent
end
send_proxy_connect_request(req) click to toggle source

Open tunnel through proxy

# File lib/http/connection.rb, line 166
def send_proxy_connect_request(req)
  return unless req.uri.https? && req.using_proxy?

  @pending_request = true

  req.connect_using_proxy @socket

  @pending_request  = false
  @pending_response = true

  read_headers!
  @proxy_response_headers = @parser.headers

  if @parser.status_code != 200
    @failed_proxy_connect = true
    return
  end

  @parser.reset
  @pending_response = false
end
set_keep_alive() click to toggle source

Store whether the connection should be kept alive. Once we reset the parser, we lose all of this state. @return [void]

# File lib/http/connection.rb, line 197
def set_keep_alive
  return @keep_alive = false unless @persistent

  @keep_alive =
    case @parser.http_version
    when HTTP_1_0 # HTTP/1.0 requires opt in for Keep Alive
      @parser.headers[Headers::CONNECTION] == KEEP_ALIVE
    when HTTP_1_1 # HTTP/1.1 is opt-out
      @parser.headers[Headers::CONNECTION] != CLOSE
    else # Anything else we assume doesn't supportit
      false
    end
end
start_tls(req, options) click to toggle source

Sets up SSL context and starts TLS if needed. @param (see initialize) @return [void]

# File lib/http/connection.rb, line 152
def start_tls(req, options)
  return unless req.uri.https? && !failed_proxy_connect?

  ssl_context = options.ssl_context

  unless ssl_context
    ssl_context = OpenSSL::SSL::SSLContext.new
    ssl_context.set_params(options.ssl || {})
  end

  @socket.start_tls(req.uri.host, options.ssl_socket_class, ssl_context)
end