Class: Contrast::Agent::Request

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Components::Logger::InstanceMethods, Components::Scope::InstanceMethods, Utils::RequestUtils
Defined in:
lib/contrast/agent/request/request.rb

Overview

This class is the Contrast representation of the Rack::Request object. It provides access to the original Rack::Request object as well as extracts data in a format that the Agent expects, caching those transformations in order to avoid repeatedly creating Strings & thrashing GC.

Constant Summary collapse

EMPTY_PATH =
'/'

Constants included from Utils::RequestUtils

Utils::RequestUtils::END_PATTERN, Utils::RequestUtils::HASH_PATTERN, Utils::RequestUtils::ID_, Utils::RequestUtils::MEDIA_TYPE_MARKERS, Utils::RequestUtils::NUM_, Utils::RequestUtils::NUM_PATTERN, Utils::RequestUtils::STATIC_SUFFIXES, Utils::RequestUtils::UUID_PATTERN, Utils::RequestUtils::WIN_PATTERN

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Components::Scope::InstanceMethods

#contrast_enter_method_scopes!, #contrast_exit_method_scopes!, #with_app_scope, #with_contrast_scope, #with_deserialization_scope, #with_split_scope

Methods included from Components::Logger::InstanceMethods

#cef_logger, #logger

Methods included from Utils::RequestUtils

#normalize_params, #read_body, #traverse_parsed_multipart

Constructor Details

#initialize(rack_request) ⇒ Request

Initialize new Contrast Request

Parameters:

  • rack_request (Rack::Request)

    The passed to the Agent RackRequest to be wrapped.



44
45
46
# File 'lib/contrast/agent/request/request.rb', line 44

def initialize rack_request
  @rack_request = rack_request
end

Instance Attribute Details

#discovered_routeContrast::Agent::Reporting::RouteDiscovery



35
36
37
# File 'lib/contrast/agent/request/request.rb', line 35

def discovered_route
  @discovered_route
end

#observed_routeContrast::Agent::Reporting::ObservedRoute

Returns the route, used for coverage, of this request.

Returns:



33
34
35
# File 'lib/contrast/agent/request/request.rb', line 33

def observed_route
  @observed_route
end

#rack_requestRack::Request (readonly)

Returns The passed to the Agent RackRequest to be wrapped.

Returns:

  • (Rack::Request)

    The passed to the Agent RackRequest to be wrapped.



31
32
33
# File 'lib/contrast/agent/request/request.rb', line 31

def rack_request
  @rack_request
end

Instance Method Details

#bodyObject

Try and read the body and return memorized object of the body. If body contains file, do not parse it, otherwiseBody might be nil



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/contrast/agent/request/request.rb', line 137

def body
  # Memoize a flag indicating whether we've tried to read the body or not
  # (can't use body because it might be nil)
  @_body_read ||= begin
    body = rack_request.body
    if defined?(Rack::Multipart) &&
          defined?(Rack::Multipart::UploadedFile) &&
          body.is_a?(Rack::Multipart::UploadedFile)

      logger.trace('not parsing uploaded file body',
                   file_name: body.original_filename,
                   content_type: body.content_type)
      @_body = nil
    else
      logger.trace('parsing body from request', body_type: body.cs__class.cs__name)
      @_body = Contrast::Utils::StringUtils.force_utf8(read_body(body))
    end

    true
  end

  # Return memoized body (which might be nil)
  @_body
end

#document_typeObject

Returns the request file type



105
106
107
108
109
110
111
112
113
# File 'lib/contrast/agent/request/request.rb', line 105

def document_type
  @_document_type ||= if /xml/i.match?(media_type) || body&.start_with?('<?xml')
                        :XML
                      elsif /json/i.match?(media_type) || body&.match?(/\s*[{\[]/)
                        :JSON
                      else
                        :NORMAL
                      end
end

#file_namesObject

returns multipart filenames



172
173
174
175
176
177
178
179
180
181
# File 'lib/contrast/agent/request/request.rb', line 172

def file_names
  @_file_names ||= begin
    names = {}
    parsed_data = Rack::Multipart.parse_multipart(rack_request.env)
    traverse_parsed_multipart(parsed_data, names)
  rescue StandardError => _e
    logger.warn('Unable to parse multipart request!')
    {}
  end
end

#hash_idObject

returns or generates the hash checksum for the request



186
187
188
# File 'lib/contrast/agent/request/request.rb', line 186

def hash_id
  @_hash_id ||= Contrast::Utils::HashDigest.generate_request_hash(self)
end

#headersObject

Header keys upcased and any underscores replaced with dashes



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/contrast/agent/request/request.rb', line 118

def headers
  @_headers ||= with_contrast_scope do
    hash = {}
    env.each do |key, value|
      next unless key

      name = key.to_s
      next unless name.start_with?(Contrast::Utils::ObjectShare::HTTP_SCORE)

      hash[Contrast::Utils::StringUtils.normalized_key(name)] = value
    end
    hash
  end
end

#normalized_uriObject

Returns a normalized form of the URI. In “normal” URIs this will return an unchanged String, but in REST-y URIs this will normalize the digit path tokens, e.g.:

/accounts/5/view …becomes: /accounts/n/view

Should also handle the ;jsessionid.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/contrast/agent/request/request.rb', line 71

def normalized_uri
  @_normalized_uri ||= begin
    path = rack_request.path_info || rack_request.path.to_s
    path = EMPTY_PATH if Contrast::Utils::DuckUtils.empty_duck?(path)

    # /foo/bar;jsessionid=123 => /foo/bar
    uri = path.split(Contrast::Utils::ObjectShare::SEMICOLON)[0]
    # /foo/bar?query_string=123 => /foo/bar
    uri = uri.split(Contrast::Utils::ObjectShare::QUESTION_MARK)[0]

    # Replace with tokens:
    # NUM_ => '/{n}/'
    # ID_ =>  '{ID}'
    #
    # replace UUIDs: /123e4567-e89b-42d3-a456-556642440000/ => /{ID}/
    uri.gsub!(UUID_PATTERN, ID_)
    # replace hash patterns: /6f1ed002ab5595859014ebf0951522d9/ => /{ID}/
    uri.gsub!(HASH_PATTERN, ID_)
    # replace windows SID: /S-1-5-21-1843332746-572796286-2118856591-1000/ => /{ID}/
    uri.gsub!(WIN_PATTERN, ID_)
    # replace interior number tokens: /123/ => /{n}/
    uri.gsub!(NUM_PATTERN, NUM_)
    # replace last number tokens: /123 => /{n}
    uri.gsub!(END_PATTERN, NUM_[0..-2])
    uri
  rescue StandardError => e
    logger.error('error normalizing uri', error: e, backtrace: e.backtrace)
    EMPTY_PATH
  end
end

#parametersObject

flattened hash of request params



165
166
167
# File 'lib/contrast/agent/request/request.rb', line 165

def parameters
  @_parameters ||= with_contrast_scope { normalize_params(rack_request.params) }
end

#static?Boolean

Utility method for checking if a request is for a static resource.

Returns:

  • (Boolean)

    true, if the request is for a well-known static type as determined by the request suffix or the accept header.



193
194
195
196
197
198
199
200
201
# File 'lib/contrast/agent/request/request.rb', line 193

def static?
  return true if normalized_uri&.match?(STATIC_SUFFIXES)

  accepts = Array(headers['ACCEPT'])&.first&.to_s
  return false unless accepts
  return false if accepts.start_with?('*/*')

  accepts.start_with?(*MEDIA_TYPE_MARKERS)
end

#versionString

The HTTP version of this request

Returns:



50
51
52
53
54
55
56
57
58
# File 'lib/contrast/agent/request/request.rb', line 50

def version
  env = rack_request.env
  return '1.1' unless env

  version = env['HTTP_VERSION']
  return '1.1' unless version

  version.split('/')[-1]
end