class EmmyHttp::Client::Client

Attributes

adapter[RW]
connection[RW]
decoder[RW]
operation[RW]
parser[RW]
redirects[RW]
request[RW]
response[RW]
state[RW]
stop_reason[RW]
url[RW]

Public Class Methods

new(request, adapter) click to toggle source
# File lib/emmy_http/client/client.rb, line 21
def initialize(request, adapter)
  @request   = request
  @adapter   = adapter
  @operation = adapter.operation
  @redirects = 0
  #@cookie    = Cookie.new
  prepare_url
  change_state(:idle)
end

Public Instance Methods

attach(conn) click to toggle source
# File lib/emmy_http/client/client.rb, line 217
def attach(conn)
  listen conn, :connect, :connect
  listen conn, :data, :data
  listen conn, :close, :close
end
change_state(new_state) click to toggle source
# File lib/emmy_http/client/client.rb, line 212
def change_state(new_state)
  @state = new_state
  change!(state, self)
end
close(reason=nil) click to toggle source
# File lib/emmy_http/client/client.rb, line 78
def close(reason=nil)
  if state == :body && !response.content_length?
    response.finish
  end

  if response && response.redirection?
    change_state(:redirect)
    self.url = url + response.location
    self.response = nil
    parser.reset!
    begin
      operation.reconnect
    rescue EventMachine::ConnectionError => e
      finalize(e.to_s)
    end
    return
  end

  finalize(stop_reason || reason)
end
connect() click to toggle source
# File lib/emmy_http/client/client.rb, line 66
def connect
  connection.start_tls(request.ssl ? request.ssl.serializable_hash : {}) if ssl?
  send_request
  change_state(:wait_response)
end
data(chunk) click to toggle source
# File lib/emmy_http/client/client.rb, line 72
def data(chunk)
  parser << chunk
rescue EmmyHttp::ParserError => e
  stop(e.message)
end
dettach(conn) click to toggle source
# File lib/emmy_http/client/client.rb, line 223
def dettach(conn)
  stop_listen conn, :connect
  stop_listen conn, :data
  stop_listen conn, :close
end
finalize(reason=nil) click to toggle source
# File lib/emmy_http/client/client.rb, line 187
def finalize(reason=nil)
  dettach(connection)
  if response && response.finished?
    change_state(:success)
    response.data!(decoder.finalize || '') if decoder
    operation.success!(response, operation, connection)
  else
    change_state(:catch_error)
    operation.error!(reason ? reason.to_s : 'bad response', operation, connection)
  end
end
initialize_connection(conn) click to toggle source
# File lib/emmy_http/client/client.rb, line 31
def initialize_connection(conn)
  @connection = conn
  @parser  = Client::Parser.new
  #@parser.header_value_type = :mixed
  client = self

  conn.pending_connect_timeout = request.timeouts.connect
  conn.comm_inactivity_timeout = request.timeouts.inactivity
  attach(conn)

  parser.on :head do |headers|
    #parser.http_version
    change_state(:body)
    client.response = EmmyHttp::Response.new(
      status: parser.status_code, # for responses
      headers: headers,
      body: ''
    )
    client.decoder = new_decoder_by_encoding(headers['Content-Encoding']) if request.decoding
    operation.head!(response, operation, connection)
    #parser.reset = true if request.no_body?
  end

  parser.on :body do |chunk|
    response.data!(client.decoder ? (client.decoder.decompress(chunk) || '') : chunk)
  end

  parser.on :completed do
    client.response.finish
    request.keep_alive? ? finalize : client.stop
  end

  change_state(:wait_connect)
end
new_decoder_by_encoding(encoding) click to toggle source
# File lib/emmy_http/client/client.rb, line 229
def new_decoder_by_encoding(encoding)
  case encoding
  when 'deflate', 'compressed'
    EmmyHttp::Client::Decoders::Deflate.new
  when 'gzip'
    EmmyHttp::Client::Decoders::GZip.new
  else
    nil
  end
end
prepare_body(headers) click to toggle source
# File lib/emmy_http/client/client.rb, line 123
def prepare_body(headers)
  body, form, json, file = request.body, request.form, request.json, request.file

  if body
    raise "body cannot be hash use form attribute instead" if body.is_a?(Hash)
    body_text = body.is_a?(Array) ? body.join : body.to_s
    headers['Content-Length'] = body_text.bytesize
    body_text

  elsif form
    form_encoded = form.is_a?(String) ? form : Encoders.www_form(form)
    body_text = Encoders.rfc3986(form_encoded)
    headers['Content-Type'] = 'application/x-www-form-urlencoded'
    headers['Content-Length'] = body_text.bytesize
    body_text

  elsif json
    json_string = json.is_a?(String) ? json : (json.respond_to?(:to_json) ? json.to_json : JSON.dump(json))
    headers['Content-Type']   = 'application/json'
    headers['Content-Length'] = json_string.size
    json_string

  elsif file
    headers['Content-Length'] = File.size(file)
    nil
  else
    headers['Content-Length'] = 0 if %w('POST PUT').include? request.type
    '' # empty body
  end
end
prepare_headers(headers) click to toggle source
# File lib/emmy_http/client/client.rb, line 154
def prepare_headers(headers)
  headers.merge!(EmmyHttp::Utils.convert_headers(request.headers))

  # TODO: proxy authorization

  # TODO: cookie

  headers['Connection']      = 'close' unless request.keep_alive?
  headers['User-Agent']      =  Client::USER_AGENT unless headers.key? 'User-Agent'
  headers['Authorization'] ||= Encoders.encode_auth(url.userinfo) if url.userinfo

  unless headers.key? 'Host'
    headers['Host']  = url.host
    headers['Host'] += ":#{url.port}" unless url.port.nil? || (url.scheme == 'http'  && url.port == 80) ||
                                             (url.scheme == 'https' && url.port == 443)
  end
end
prepare_url() click to toggle source
# File lib/emmy_http/client/client.rb, line 108
def prepare_url
  @url = request.real_url
  raise 'relative url' if @url.relative?

  @url.normalize!

  if path = request.real_path
    raise 'path is not relative' unless path.relative?
    @url += path
  end
  @url.user     = request.user if request.user
  @url.password = request.password if request.password
  @url.query = request.query.is_a?(Hash) ? Encoders.query(request.query) : request.query.to_s if request.query
end
send_http_request(headers, body) click to toggle source
# File lib/emmy_http/client/client.rb, line 172
def send_http_request(headers, body)

  # Send headers

  head = "#{request.type.upcase} #{url_path} #{Client::HTTP_VERSION}\r\n"
  head = headers.inject(head) { |r, (k, v)| "#{r}#{k}:#{v}\r\n" }
  head += "\r\n"
  connection.send_data head

  # Send body

  connection.send_data body if body
  connection.send_stream_file_data request.file if request.file
end
send_request() click to toggle source
# File lib/emmy_http/client/client.rb, line 99
def send_request
  headers = {}
  body    = prepare_body(headers)
  body    = Encoders.encode_body(request.headers["Content-Encoding"], body) if request.encoding?

  prepare_headers(headers)
  send_http_request(headers, body)
end
ssl?() click to toggle source
# File lib/emmy_http/client/client.rb, line 203
def ssl?
  request.ssl || url.scheme == 'https' || url.port == 443
end
stop(reason=nil) click to toggle source
# File lib/emmy_http/client/client.rb, line 207
def stop(reason=nil)
  @stop_reason ||= reason
  connection.close_connection if connection
end
to_a() click to toggle source
# File lib/emmy_http/client/client.rb, line 240
def to_a
  ["tcp://#{url.host}:#{url.port || url.default_port}", connection || EmmyMachine::Connection, method(:initialize_connection), self]
end
url_path() click to toggle source
# File lib/emmy_http/client/client.rb, line 199
def url_path
  url.query ? url.path + '?' + url.query : url.path
end