Class: Akita::HarLogger::HttpRequest

Inherits:
Object
  • Object
show all
Defined in:
lib/akita/har_logger/http_request.rb

Instance Method Summary collapse

Constructor Details

#initialize(env) ⇒ HttpRequest

Produces an HttpRequest from a request’s HTTP environment.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/akita/har_logger/http_request.rb', line 9

def initialize(env)
  req = Rack::Request.new env

  @self = {
    method: getMethod(env),
    url: HarUtils.fixEncoding(req.url),
    httpVersion: getHttpVersion(env),
    cookies: getCookies(env),
    headers: getHeaders(env),
    queryString: getQueryString(env),
    headersSize: getHeadersSize(env),
    bodySize: getBodySize(env),
  }

  # Augment with post data if we have any.
  postData = getPostData(env)
  if postData != nil then
    @self[:postData] = postData
  end
end

Instance Method Details

#getBodySize(env) ⇒ Object

Obtains the size of the request body from an HTTP environment.



172
173
174
175
# File 'lib/akita/har_logger/http_request.rb', line 172

def getBodySize(env)
  # Assume no content if Content-Length header was not provided.
  env.key?('CONTENT_LENGTH') ? env['CONTENT_LENGTH'].to_i : 0
end

#getCookies(env) ⇒ Object

Builds a list of cookie objects from an HTTP environment.



50
51
52
53
# File 'lib/akita/har_logger/http_request.rb', line 50

def getCookies(env)
  req = Rack::Request::new env
  HarUtils.hashToList req.cookies
end

#getHeaders(env) ⇒ Object

Builds a list of headers from an HTTP environment.



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/akita/har_logger/http_request.rb', line 56

def getHeaders(env)
  # HTTP headers in the environment can be identified with the "HTTP_"
  # prefix. Filter for these. In the resulting map, rewrite keys of the
  # form "HTTP_FOO_BAR_BAZ" into "Foo-Bar-Baz", and convert into an
  # array.
  HarUtils.hashToList (
    env.select { |k,v| k.start_with? 'HTTP_' }.
      transform_keys { |k|
        k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
      }
  )
end

#getHeadersSize(env) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/akita/har_logger/http_request.rb', line 150

def getHeadersSize(env)
  # XXX This seems to under-count, compared to a HAR produced by Firefox.

  # Count the number of bytes needed to produce the first line of the
  # request (HTTP method, full path, HTTP version, CRLF). For example,
  #
  #   GET /index.html?foo=bar&baz=qux HTTP/1.1<CR><LF>
  req = Rack::Request::new env
  line_length =
    getMethod(env).length + 1
      + req.fullpath.length + 1
      + getHttpVersion(env).length + 2

  # Add the size of the headers. Add 2 to the starting value to account
  # for the CRLF on the blank line.
  getHeaders(env).reduce(line_length + 2) { |accum, entry|
    # Header-Name: header value<CR><LF>
    accum + entry[:name].length + 2 + entry[:value].length + 2
  }
end

#getHttpVersion(env) ⇒ Object

Obtains the client-requested HTTP version from an HTTP environment.



40
41
42
43
44
45
46
47
# File 'lib/akita/har_logger/http_request.rb', line 40

def getHttpVersion(env)
  # The environment doesn't have HTTP_VERSION when running with `rspec`;
  # assume HTTP/1.1 when this happens. We don't return nil, so we can
  # calculate the size of the headers.
  env.key?('HTTP_VERSION') ?
    HarUtils.fixEncoding(env['HTTP_VERSION']) :
    'HTTP/1.1'
end

#getMethod(env) ⇒ Object

Obtains the client’s request method from an HTTP environment.



35
36
37
# File 'lib/akita/har_logger/http_request.rb', line 35

def getMethod(env)
  HarUtils.fixEncoding (Rack::Request.new env).request_method
end

#getPostData(env) ⇒ Object

Obtains the posted data from an HTTP environment.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/akita/har_logger/http_request.rb', line 92

def getPostData(env)
  if env.key?('CONTENT_TYPE') && env['CONTENT_TYPE'] then
    result = { mimeType: env['CONTENT_TYPE'] }

    # Populate 'params' if we have URL-encoded parameters. Otherwise,
    # populate 'text'.
    req = Rack::Request.new env
    if env['CONTENT_TYPE'] == 'application/x-www-form-urlencoded' then
      # Decoded parameters can be found as a map in req.params.
      #
      # Requests originating from specs can be malformed: the values in
      # req.params are not necessarily strings. Encode all of req.params
      # in JSON and pretend the content type was "application/json".
      if HarUtils.allValuesAreStrings req.params then
        # Convert req.params into an array.
        #
        # XXX Spec has space for files, but are file uploads ever
        # URL-encoded?
        result[:params] = HarUtils.hashToList req.params
      else
        result[:mimeType] = 'application/json'
        result[:text] = req.params.to_json
      end
    else
      # Rack has been observed to use ASCII-8BIT encoding for the request
      # body when the request specifies UTF-8. Reinterpret the content
      # body according to what the request says it is, and re-encode into
      # UTF-8.
      #
      # Gracefully handle any characters that are invalid in the source
      # encoding and characters that have no UTF-8 representation by
      # replacing with '?'. Log a warning when this happens.
      sourceCharset = getPostDataCharSet(env)
      source = String.new(req.body.read).force_encoding(sourceCharset)
      utf8EncodingSuccessful = false
      if source.valid_encoding? then
        begin
          result[:text] = source.encode(Encoding::UTF_8)
          utf8EncodingSuccessful = true
        rescue Encoding::UndefinedConversionError
          Rails.logger.warn "[#{caller_locations(0, 1)}] Unable to losslessly convert request body from #{source.encoding} to UTF-8. Characters undefined in UTF-8 will be replaced with '?'."
        end
      else
        Rails.logger.warn "[#{caller_locations(0, 1)}] Request body is not valid #{source.encoding}. Invalid characters and characters undefined in UTF-8 will be replaced with '?'."
      end

      if !utf8EncodingSuccessful then
        result[:text] = source.encode(Encoding::UTF_8,
            invalid: :replace, undef: :replace, replace: '?')
      end
    end

    result
  else
    nil
  end
end

#getPostDataCharSet(env) ⇒ Object

Obtains the character set of the posted data from an HTTP environment.



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/akita/har_logger/http_request.rb', line 77

def getPostDataCharSet(env)
  req = Rack::Request.new env
  if req.content_charset != nil then
    return req.content_charset
  end

  # RFC 2616 says that "text/*" defaults to ISO-8859-1.
  if env['CONTENT_TYPE'].start_with?('text/') then
    return Encoding::ISO_8859_1
  end

  Encoding::UTF_8
end

#getQueryString(env) ⇒ Object

Builds a list of query parameters from an HTTP environment.



70
71
72
73
74
# File 'lib/akita/har_logger/http_request.rb', line 70

def getQueryString(env)
  req = Rack::Request::new env
  paramMap = Rack::Utils.parse_nested_query req.query_string
  HarUtils.hashToList paramMap
end

#to_json(*args) ⇒ Object



30
31
32
# File 'lib/akita/har_logger/http_request.rb', line 30

def to_json(*args)
  @self.to_json(*args)
end