class Sqreen::Session

Constants

MAX_DELAY
MUTEX
RETRY_CONNECT_SECONDS
RETRY_FOREVER
RETRY_MANY
RETRY_REQUEST_SECONDS

Attributes

request_compression[RW]

Public Class Methods

new(server_url, cert_store, token, app_name = nil, proxy_url = nil) click to toggle source
# File lib/sqreen/session.rb, line 53
def initialize(server_url, cert_store, token, app_name = nil, proxy_url = nil)
  @token = token
  @app_name = app_name
  @session_id = nil
  @server_url = server_url
  @request_compression = false
  @connected = nil
  @con = nil

  uri = parse_uri(server_url)
  use_ssl = (uri.scheme == 'https')

  proxy_params = []
  if proxy_url
    proxy_uri = parse_uri(proxy_url)
    proxy_params = [proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password]
  end

  @req_nb = 0

  @http = Net::HTTP.new(uri.host, uri.port, *proxy_params)
  @http.use_ssl = use_ssl
  @http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SQREEN_SSL_NO_VERIFY'] # for testing
  @http.cert_store = cert_store if use_ssl
  self.use_signals = false
end

Public Instance Methods

compress(data) click to toggle source
# File lib/sqreen/session.rb, line 228
def compress(data)
  return data unless request_compression
  out = StringIO.new
  w = Zlib::GzipWriter.new(out)
  w.write(data)
  w.close
  out.string
end
connect() click to toggle source
# File lib/sqreen/session.rb, line 112
def connect
  return if connected?
  Sqreen.log.warn "connection to #{@server_url}..."
  @session_id = nil
  @conn_retry = 0
  begin
    @con = @http.start
  rescue StandardError => e
    Sqreen.log.debug { "Caught exception during request: #{e.inspect}" }
    Sqreen.log.debug { e.backtrace }
    Sqreen.log.debug { "Cannot connect, retry in #{RETRY_CONNECT_SECONDS} seconds" }
    sleep RETRY_CONNECT_SECONDS
    @conn_retry += 1
    retry
  else
    Sqreen.log.warn 'connection success.'
  end
end
connected?() click to toggle source
# File lib/sqreen/session.rb, line 104
def connected?
  @con && @con.started?
end
disconnect() click to toggle source
# File lib/sqreen/session.rb, line 108
def disconnect
  @http.finish if connected?
end
do_http_request(method, path, data, headers = {}, max_retry = 2) click to toggle source
# File lib/sqreen/session.rb, line 167
def do_http_request(method, path, data, headers = {}, max_retry = 2)
  now = Time.now.utc
  headers['X-Session-Key'] = @session_id if @session_id
  headers['X-Sqreen-Time'] = now.to_f.to_s
  headers['User-Agent'] = "sqreen-ruby/#{Sqreen::VERSION}"
  headers['X-Sqreen-Beta'] = format('pid=%d;tid=%s;nb=%d;t=%f',
                                    Process.pid,
                                    thread_id,
                                    @req_nb,
                                    Time.now.utc.to_f)
  headers['Content-Type'] = 'application/json'
  headers['Accept'] = 'application/json'
  if request_compression && !method.casecmp(:GET).zero?
    headers['Content-Encoding'] = 'gzip'
  end

  @req_nb += 1

  path = prefix_path(path)

  payload = {}
  resiliently(RETRY_REQUEST_SECONDS, max_retry) do
    Sqreen.log.debug { format('%s %s %s %s (%s)', method, path, JSON.dump(data), headers.inspect, @token) }
    res = nil
    MUTEX.synchronize do
      res = case method.upcase
            when :GET
              @con.get(path, headers)
            when :POST
              json_data = nil
              unless data.nil?
                serialized = Serializer.serialize(data)
                json_data = compress(SafeJSON.dump(serialized))
              end
              @con.post(path, json_data, headers)
            else
              Sqreen.log.debug { format('unknown method %s', method) }
              raise Sqreen::NotImplementedYet
            end
    end
    if res && res.code == '401'
      raise Sqreen::Unauthorized, 'HTTP 401: shall relogin'
    end
    if res && res.body
      if res['Content-Type'] && res['Content-Type'].start_with?('application/json')
        payload = JSON.parse(res.body)
        unless payload['status']
          Sqreen.log.debug { format('Cannot %s %s. Parsed response body was: %s', method, path, payload.inspect) }
        end
      else
        Sqreen.log.debug { "Unexpected response Content-Type: #{res['Content-Type']}" }
        Sqreen.log.debug { "Unexpected response body: #{res.body.inspect}" }
      end
    else
      Sqreen.log.debug { 'warning: empty return value' }
    end
    Sqreen.log.debug { format('%s %s (DONE: %s in %f ms)', method, path, res && res.code, (Time.now.utc - now) * 1000) }
  end
  payload
end
get(path, headers = {}, max_retry = 2) click to toggle source
# File lib/sqreen/session.rb, line 135
def get(path, headers = {}, max_retry = 2)
  do_http_request(:GET, path, nil, headers, max_retry)
end
get_actionspack() click to toggle source
# File lib/sqreen/session.rb, line 291
def get_actionspack
  get('actionspack', {}, RETRY_MANY)
end
heartbeat(cmd_res = {}, metrics = []) click to toggle source
# File lib/sqreen/session.rb, line 265
def heartbeat(cmd_res = {}, metrics = [])
  payload = {}
  unless metrics.nil? || metrics.empty?
    # never reached with signals
    payload['metrics'] = metrics.map do |m|
      Sqreen::Legacy::EventToHash.convert_agg_metric(m)
    end
  end
  payload['command_results'] = cmd_res unless cmd_res.nil? || cmd_res.empty?

  post('app-beat', payload.empty? ? nil : payload, {}, RETRY_MANY)
end
login(framework) click to toggle source
# File lib/sqreen/session.rb, line 237
def login(framework)
  headers = prelogin_auth_headers(framework)

  Sqreen.log.warn "Using app name: #{headers['x-app-name']}"

  res = post('app-login', RuntimeInfos.all(framework), headers, RETRY_FOREVER)

  if !res || !res['status']
    public_error = format('Cannot login. Token may be invalid: %s', @token)
    Sqreen.log.error public_error
    raise(Sqreen::TokenInvalidException,
          format('invalid response: %s', res.inspect))
  end
  Sqreen.log.info 'Login success.'
  @session_id = res['session_id']

  Kit::Configuration.session_key = @session_id
  Kit.reset

  Sqreen.log.debug { "received session_id #{@session_id}" }
  Sqreen.logged_in = true
  res
end
logout(retrying = true) click to toggle source

Perform agent logout @param retrying [Boolean] whether to try again on error

# File lib/sqreen/session.rb, line 316
def logout(retrying = true)
  # Do not try to connect if we are not connected
  unless connected?
    Sqreen.log.debug('Not connected: not trying to logout')
    return
  end
  # Perform not very resilient logout not to slow down client app shutdown
  get('app-logout', {}, retrying ? 2 : 1)
  Sqreen.logged_in = false
  disconnect
end
parse_uri(uri) click to toggle source
# File lib/sqreen/session.rb, line 91
def parse_uri(uri)
  # This regexp is the Ruby constant URI::PATTERN::HOSTNAME augmented
  # with the _ character that is frequent in Docker linked containers.
  re = '(?:(?:[a-zA-Z\\d](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.)*(?:[a-zA-Z](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.?'
  parser = URI::Parser.new :HOSTNAME => re
  parser.parse(uri)
end
post(path, data, headers = {}, max_retry = 2) click to toggle source
# File lib/sqreen/session.rb, line 131
def post(path, data, headers = {}, max_retry = 2)
  do_http_request(:POST, path, data, headers, max_retry)
end
post_agent_message(framework, agent_message) click to toggle source
# File lib/sqreen/session.rb, line 309
def post_agent_message(framework, agent_message)
  headers = prelogin_auth_headers(framework)
  post('app_agent_message', agent_message.to_h, headers, 0)
end
post_attack(attack) click to toggle source

XXX never called

# File lib/sqreen/session.rb, line 283
def post_attack(attack)
  @evt_sub_strategy.post_attack(attack)
end
post_batch(events) click to toggle source
# File lib/sqreen/session.rb, line 305
def post_batch(events)
  @evt_sub_strategy.post_batch(events)
end
post_bundle(bundle_sig, dependencies) click to toggle source
# File lib/sqreen/session.rb, line 287
def post_bundle(bundle_sig, dependencies)
  post('bundle', { 'bundle_signature' => bundle_sig, 'dependencies' => dependencies }, {}, RETRY_MANY)
end
post_metrics(metrics) click to toggle source
# File lib/sqreen/session.rb, line 278
def post_metrics(metrics)
  @evt_sub_strategy.post_metrics(metrics)
end
post_request_record(request_record) click to toggle source
# File lib/sqreen/session.rb, line 295
def post_request_record(request_record)
  @evt_sub_strategy.post_request_record(request_record)
end
post_sqreen_exception(exception) click to toggle source

Post an exception to Sqreen for analysis @param exception [RemoteException] Exception and context to be sent over

# File lib/sqreen/session.rb, line 301
def post_sqreen_exception(exception)
  @evt_sub_strategy.post_sqreen_exception(exception)
end
prefix_path(path) click to toggle source
# File lib/sqreen/session.rb, line 99
def prefix_path(path)
  return '/sqreen/v1/' + path if path == 'app-login' || path == 'app-beat'
  @@path_prefix + path
end
resiliently(retry_request_seconds, max_retry, current_retry = 0) { || ... } click to toggle source
# File lib/sqreen/session.rb, line 139
def resiliently(retry_request_seconds, max_retry, current_retry = 0)
  connect unless connected?
  return yield
rescue => e
  Sqreen.log.debug { "Caught exception during request: #{e.inspect}" }
  Sqreen.log.debug { e.backtrace }

  current_retry += 1

  raise e if max_retry != RETRY_FOREVER && current_retry >= max_retry || e.is_a?(Sqreen::NotImplementedYet) || e.is_a?(Sqreen::Unauthorized)

  sleep_delay = [MAX_DELAY, retry_request_seconds * current_retry].min
  Sqreen.log.debug { format("Sleeping %ds before retry #{current_retry}/#{max_retry}", sleep_delay) }
  sleep(sleep_delay)

  retry
end
rules() click to toggle source
# File lib/sqreen/session.rb, line 261
def rules
  get('rulespack', {}, RETRY_MANY)
end
thread_id() click to toggle source
# File lib/sqreen/session.rb, line 157
def thread_id
  th = Thread.current
  return '' unless th
  re = th.to_s.scan(/:(0x.*)>/)
  return '' unless re && !re.empty?
  res = re[0]
  return '' unless res && !res.empty?
  res[0]
end
use_signals=(do_use) click to toggle source
# File lib/sqreen/session.rb, line 80
def use_signals=(do_use)
  return if do_use == @use_signals

  @use_signals = do_use
  if do_use
    @evt_sub_strategy = Sqreen::Signals::SignalsSubmissionStrategy.new
  else
    @evt_sub_strategy = Sqreen::Legacy::OldEventSubmissionStrategy.new(method(:post))
  end
end

Private Instance Methods

prelogin_auth_headers(framework) click to toggle source
# File lib/sqreen/session.rb, line 330
def prelogin_auth_headers(framework)
  {
    'x-api-key' => @token,
    'x-app-name' => @app_name || framework.application_name,
  }.reject { |_k, v| v == nil }
end