class EventMachine::HttpClient

Constants

CONTENT_ENCODING
CONTENT_LENGTH
CONTENT_TYPE
CRLF
ETAG
HOST
KEEP_ALIVE
LAST_MODIFIED
LOCATION
TRANSFER_ENCODING

Attributes

error[R]
last_effective_url[R]
method[RW]
options[RW]
redirects[R]
response[R]
response_header[R]
uri[RW]

Public Instance Methods

close(msg, dns_error = false) click to toggle source
Alias for: on_error
connect_proxy?() click to toggle source

determines if a http-proxy should be used with the CONNECT verb

# File lib/em-http/client.rb, line 364
def connect_proxy?; http_proxy? && (@options[:proxy][:use_connect] == true); end
connection_completed() click to toggle source

start HTTP request once we establish connection to host

# File lib/em-http/client.rb, line 261
def connection_completed
  # if a socks proxy is specified, then a connection request
  # has to be made to the socks server and we need to wait
  # for a response code
  if socks_proxy? and @state == :response_header
    @state = :connect_socks_proxy
    send_socks_handshake

    # if we need to negotiate the proxy connection first, then
    # issue a CONNECT query and wait for 200 response
  elsif connect_proxy? and @state == :response_header
    @state = :connect_http_proxy
    send_request_header

    # if connecting via proxy, then state will be :proxy_connected,
    # indicating successful tunnel. from here, initiate normal http
    # exchange

  else
    @state = :response_header
    ssl = @options[:tls] || @options[:ssl] || {}
    start_tls(ssl) if @uri.scheme == "https" or @uri.port == 443
    send_request_header
    send_request_body
  end
end
disconnect(&blk) click to toggle source

assign disconnect callback for websocket

# File lib/em-http/client.rb, line 315
def disconnect(&blk)
  @disconnect = blk
end
dispatch() click to toggle source

Response processing

# File lib/em-http/client.rb, line 523
def dispatch
  while case @state
      when :connect_socks_proxy
        parse_socks_response
      when :connect_http_proxy
        parse_response_header
      when :response_header
        parse_response_header
      when :chunk_header
        parse_chunk_header
      when :chunk_body
        process_chunk_body
      when :chunk_footer
        process_chunk_footer
      when :response_footer
        process_response_footer
      when :body
        process_body
      when :websocket
        process_websocket
      when :finished, :invalid
        break
      else raise RuntimeError, "invalid state: #{@state}"
    end
  end
finished?() click to toggle source
# File lib/em-http/client.rb, line 485
def finished?
  @state == :finished || (@state == :body && @bytes_remaining.nil?)
end
has_bytes?(num) click to toggle source

determines if there is enough data in the buffer

# File lib/em-http/client.rb, line 349
def has_bytes?(num)
  @data.size >= num
end
headers(&blk) click to toggle source

assign a headers parse callback

# File lib/em-http/client.rb, line 320
def headers(&blk)
  @headers = blk
end
http_proxy?() click to toggle source

determines if a proxy should be used that uses http-headers as proxy-mechanism

this is the default proxy type if none is specified

# File lib/em-http/client.rb, line 360
def http_proxy?; proxy? && [nil, :http].include?(@options[:proxy][:type]); end
normalize_body() click to toggle source
# File lib/em-http/client.rb, line 338
def normalize_body
  @normalized_body ||= begin
    if @options[:body].is_a? Hash
      form_encode_body(@options[:body])
    else
      @options[:body]
    end
  end
end
on_body_data(data) click to toggle source

Called when part of the body has been read

# File lib/em-http/client.rb, line 464
def on_body_data(data)
  if @content_decoder
    begin
      @content_decoder << data
    rescue HttpDecoders::DecoderError
      on_error "Content-decoder error"
    end
  else
    on_decoded_body_data(data)
  end
end
on_decoded_body_data(data) click to toggle source
# File lib/em-http/client.rb, line 476
def on_decoded_body_data(data)
  data.force_encoding @content_charset  if @content_charset
  if @stream
    @stream.call(data)
  else
    @response << data
  end
end
on_error(msg, dns_error = false) click to toggle source

request failed, invoke errback

# File lib/em-http/client.rb, line 300
def on_error(msg, dns_error = false)
  @error = msg

  # no connection signature on DNS failures
  # fail the connection directly
  dns_error == true ? fail(self) : unbind
end
Also aliased as: close
on_request_complete() click to toggle source

request is done, invoke the callback

# File lib/em-http/client.rb, line 289
def on_request_complete
  begin
    @content_decoder.finalize! if @content_decoder
  rescue HttpDecoders::DecoderError
    on_error "Content-decoder error"
  end

  close_connection
end
post_init() click to toggle source
# File lib/em-http/client.rb, line 241
def post_init
  @parser = HttpClientParser.new
  @data = EventMachine::Buffer.new
  @chunk_header = HttpChunkHeader.new
  @response_header = HttpResponseHeader.new
  @parser_nbytes = 0
  @redirects = 0
  @response = ''
  @error = ''
  @headers = nil
  @last_effective_url = nil
  @content_decoder = nil
  @content_charset = nil
  @stream = nil
  @disconnect = nil
  @state = :response_header
  @socks_state = nil
end
proxy?() click to toggle source
# File lib/em-http/client.rb, line 354
def proxy?; !@options[:proxy].nil?; end
receive_data(data) click to toggle source
# File lib/em-http/client.rb, line 458
def receive_data(data)
  @data << data
  dispatch
end
send(data) click to toggle source

raw data push from the client (WebSocket) should only be invoked after handshake, otherwise it will inject data into the header exchange

frames need to start with 0x00-0x7f byte and end with an 0xFF byte. Per spec, we can also set the first byte to a value betweent 0x80 and 0xFF, followed by a leading length indicator

# File lib/em-http/client.rb, line 332
def send(data)
  if @state == :websocket
    send_data("\x00#{data}\xff")
  end
end
send_request_body() click to toggle source
# File lib/em-http/client.rb, line 448
def send_request_body
  if @options[:body]
    body = normalize_body
    send_data body
    return
  elsif @options[:file]
    stream_file_data @options[:file], :http_chunks => false
  end
end
send_request_header() click to toggle source
# File lib/em-http/client.rb, line 387
def send_request_header
  query   = @options[:query]
  head    = @options[:head] ? munge_header_keys(@options[:head]) : {}
  file    = @options[:file]
  proxy   = @options[:proxy]
  body    = normalize_body

  request_header = nil

  if http_proxy?
    # initialize headers for the http proxy
    head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
    head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]

    # if we need to negotiate the tunnel connection first, then
    # issue a CONNECT query to the proxy first. This is an optional
    # flag, by default we will provide full URIs to the proxy
    if @state == :connect_http_proxy
      request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
    end
  end

  if websocket?
    head['upgrade'] = 'WebSocket'
    head['connection'] = 'Upgrade'
    head['origin'] = @options[:origin] || @uri.host

  else
    # Set the Content-Length if file is given
    head['content-length'] = File.size(file) if file

    # Set the Content-Length if body is given
    head['content-length'] =  body.bytesize if body

    # Set the cookie header if provided
    if cookie = head.delete('cookie')
      head['cookie'] = encode_cookie(cookie)
    end

    # Set content-type header if missing and body is a Ruby hash
    if not head['content-type'] and options[:body].is_a? Hash
      head['content-type'] = "application/x-www-form-urlencoded"
    end
  end

  # Set the Host header if it hasn't been specified already
  head['host'] ||= encode_host

  # Set the User-Agent if it hasn't been specified
  head['user-agent'] ||= "EventMachine HttpClient"

  # Record last seen URL
  @last_effective_url = @uri

  # Build the request headers
  request_header ||= encode_request(@method, @uri, query, proxy)
  request_header << encode_headers(head)
  request_header << CRLF
  send_data request_header
end
send_socks_handshake() click to toggle source
# File lib/em-http/client.rb, line 377
def send_socks_handshake
  # Method Negotiation as described on
  # http://www.faqs.org/rfcs/rfc1928.html Section 3

  @socks_state = :method_negotiation

  methods = socks_methods
  send_data [5, methods.size].pack('CC') + methods.pack('C*')
end
socks_methods() click to toggle source
# File lib/em-http/client.rb, line 369
def socks_methods
  methods = []
  methods << 2 if !options[:proxy][:authorization].nil? # 2 => Username/Password Authentication
  methods << 0 # 0 => No Authentication Required

  methods
end
socks_proxy?() click to toggle source

determines if a SOCKS5 proxy should be used

# File lib/em-http/client.rb, line 367
def socks_proxy?; proxy? && (@options[:proxy][:type] == :socks); end
stream(&blk) click to toggle source

assign a stream processing block

# File lib/em-http/client.rb, line 310
def stream(&blk)
  @stream = blk
end
unbind() click to toggle source
# File lib/em-http/client.rb, line 489
def unbind
  if finished? && (@last_effective_url != @uri) && (@redirects < @options[:redirects])
    begin
      # update uri to redirect location if we're allowed to traverse deeper
      @uri = @last_effective_url

      # keep track of the depth of requests we made in this session
      @redirects += 1

      # swap current connection and reassign current handler
      req = HttpOptions.new(@method, @uri, @options)
      reconnect(req.host, req.port)

      @response_header = HttpResponseHeader.new
      @state = :response_header
      @response = ''
      @data.clear
    rescue EventMachine::ConnectionError => e
      on_error(e.message, true)
    end
  else
    if finished?
      succeed(self)
    else
      @disconnect.call(self) if @state == :websocket and @disconnect
      fail(self)
    end
  end
end
websocket?() click to toggle source
# File lib/em-http/client.rb, line 353
def websocket?; @uri.scheme == 'ws'; end