Class: Sqreen::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/sqreen/session.rb

Constant Summary collapse

RETRY_CONNECT_SECONDS =
10
RETRY_REQUEST_SECONDS =
10
MAX_DELAY =
300
RETRY_FOREVER =
:forever
RETRY_MANY =
301
MUTEX =
Mutex.new
@@path_prefix =
'/sqreen/v0/'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server_url, cert_store, token, app_name = nil, proxy_url = nil) ⇒ Session

Returns a new instance of Session.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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

Instance Attribute Details

#request_compressionObject

Returns the value of attribute request_compression.



51
52
53
# File 'lib/sqreen/session.rb', line 51

def request_compression
  @request_compression
end

Instance Method Details

#compress(data) ⇒ Object



228
229
230
231
232
233
234
235
# 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

#connectObject



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/sqreen/session.rb', line 104

def connected?
  @con && @con.started?
end

#disconnectObject



108
109
110
# File 'lib/sqreen/session.rb', line 108

def disconnect
  @http.finish if connected?
end

#do_http_request(method, path, data, headers = {}, max_retry = 2) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# 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) ⇒ Object



135
136
137
# 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_actionspackObject



291
292
293
# File 'lib/sqreen/session.rb', line 291

def get_actionspack
  get('actionspack', {}, RETRY_MANY)
end

#heartbeat(cmd_res = {}, metrics = []) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
# 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) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/sqreen/session.rb', line 237

def (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) ⇒ Object

Perform agent logout

Parameters:

  • retrying (Boolean) (defaults to: true)

    whether to try again on error



316
317
318
319
320
321
322
323
324
325
326
# 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) ⇒ Object



91
92
93
94
95
96
97
# 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) ⇒ Object



131
132
133
# 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) ⇒ Object



309
310
311
312
# 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) ⇒ Object

XXX never called



283
284
285
# File 'lib/sqreen/session.rb', line 283

def post_attack(attack)
  @evt_sub_strategy.post_attack(attack)
end

#post_batch(events) ⇒ Object



305
306
307
# File 'lib/sqreen/session.rb', line 305

def post_batch(events)
  @evt_sub_strategy.post_batch(events)
end

#post_bundle(bundle_sig, dependencies) ⇒ Object



287
288
289
# 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) ⇒ Object



278
279
280
# File 'lib/sqreen/session.rb', line 278

def post_metrics(metrics)
  @evt_sub_strategy.post_metrics(metrics)
end

#post_request_record(request_record) ⇒ Object



295
296
297
# 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) ⇒ Object

Post an exception to Sqreen for analysis

Parameters:



301
302
303
# File 'lib/sqreen/session.rb', line 301

def post_sqreen_exception(exception)
  @evt_sub_strategy.post_sqreen_exception(exception)
end

#prefix_path(path) ⇒ Object



99
100
101
102
# 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) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# 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

#rulesObject



261
262
263
# File 'lib/sqreen/session.rb', line 261

def rules
  get('rulespack', {}, RETRY_MANY)
end

#thread_idObject



157
158
159
160
161
162
163
164
165
# 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) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# 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