Class: Akita::HarLogger::HttpResponse

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

Instance Method Summary collapse

Constructor Details

#initialize(env, status, headers, body) ⇒ HttpResponse

Returns a new instance of HttpResponse.



8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/akita/har_logger/http_response.rb', line 8

def initialize(env, status, headers, body)
  @self = {
    status: status,
    statusText: getStatusText(status),
    httpVersion: getHttpVersion(env),
    cookies: getCookies(headers),
    headers: (HarUtils.hashToList headers),
    content: getContent(headers, body),
    redirectURL: getRedirectUrl(headers),
    headersSize: getHeadersSize(env, status, headers),
    bodySize: getBodySize(body),
  }
end

Instance Method Details

#getBodySize(body) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/akita/har_logger/http_response.rb', line 160

def getBodySize(body)
  length = 0
  # Convert each body part into a string in case we're dealing with
  # non-Rack-compliant components.
  body.each { |part| length += part.to_s.bytesize }
  length
end

#getContent(headers, body) ⇒ Object



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
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
# File 'lib/akita/har_logger/http_response.rb', line 76

def getContent(headers, body)
  # XXX Handle compression
  # XXX Figure out how to properly join together multi-part bodies.

  # Try to convert the body into UTF-8. If this fails, assume the body is
  # binary data.
  # XXX TODO Take charset part of Content-Type header into account.
  text = +""
  haveBinaryData = false
  body.each { |part|
    partStr = part.to_s

    if partStr.encoding == Encoding::ASCII_8BIT then
      # Have 8-bit ASCII data. Try to interpret as UTF-8. If this fails,
      # treat as binary data.
      forced = String.new(partStr).force_encoding(Encoding::UTF_8)
      if forced.valid_encoding? then
        text << forced
        next
      end

      haveBinaryData = true
      break
    end

    if !partStr.valid_encoding? then
      # Source encoding is not valid. Treat as binary data.
      haveBinaryData = true
      break
    end

    # Try to re-encode as UTF-8. If this fails, treat as binary data.
    begin
      text << partStr.encode(Encoding::UTF_8)
    rescue Encoding::UndefinedConversionError
      haveBinaryData = true
      break
    end
  }

  if haveBinaryData then
    # TODO Encode binary body data with base64.
    # XXX Omit for now.
    text = ""
  end

  {
    size: getBodySize(body),

    # XXX What to use when no Content-Type is given?
    mimeType: HarUtils.fixEncoding(headers['Content-Type']),

    text: text,
  }
end

#getCookies(headers) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/akita/har_logger/http_response.rb', line 44

def getCookies(headers)
  result = []
  headers.each { |k, v|
    if "Set-Cookie".casecmp(k) != 0 then next end

    # Couldn't find a great library for parsing Set-Cookie headers, so
    # let's roll our own...
    #
    # According to RFC 6265, the value of Set-Cookie has the format
    # "cookieName=cookieValue", optionally followed by a semicolon and
    # attribute-value pairs. The cookieValue can be optionally enclosed
    # in double quotes. Neither cookieName nor cookieValue can contain
    # double quotes, semicolons, or equal signs.
    match = /^([^=]*)=([^;]*)(|;.*)$/.match(v)
    if !match then next end

    cookie_name = match[1]
    cookie_value = match[2]

    # Strip quotation marks from the value if they are present.
    match = /^"(.*)"$/.match(cookie_value)
    if match then cookie_value = match[1] end

    result << {
      name: HarUtils.fixEncoding(cookie_name),
      value: HarUtils.fixEncoding(cookie_value),
    }
  }

  result
end

#getHeadersSize(env, status, headers) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/akita/har_logger/http_response.rb', line 140

def getHeadersSize(env, status, headers)
  # 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
  # response (HTTP version, status code, status text, CRLF). For example,
  #
  #   HTTP/1.1 404 Not Found<CR><LF>
  status_length =
    getHttpVersion(env).length + 1
      + status.to_s.length + 1
      + getStatusText(status).length + 2

  # Add the size of the headers. Add 2 to the starting value to account
  # for the CRLF on the blank line.
  headers.reduce(status_length + 2) { |accum, (k, v)|
    # Header-Name: header value<CR><LF>
    accum + k.length + 2 + v.to_s.length + 2
  }
end

#getHttpVersion(env) ⇒ Object

Obtains the HTTP version in the response.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/akita/har_logger/http_response.rb', line 32

def getHttpVersion(env)
  # XXX Assume the server replies with the same HTTP version as the
  # XXX request. This seems to hold true empirically.

  # 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

#getRedirectUrl(headers) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/akita/har_logger/http_response.rb', line 132

def getRedirectUrl(headers)
  # Use the "Location" header if it exists. Otherwise, based on some HAR
  # examples found online, it looks like an empty string is used.
  headers.key?('Location') ?
    HarUtils.fixEncoding(headers['Location']) :
    ''
end

#getStatusText(status) ⇒ Object

Obtains the status text corresponding to a status code.



27
28
29
# File 'lib/akita/har_logger/http_response.rb', line 27

def getStatusText(status)
  HarUtils.fixEncoding(Rack::Utils::HTTP_STATUS_CODES[status])
end

#to_json(*args) ⇒ Object



22
23
24
# File 'lib/akita/har_logger/http_response.rb', line 22

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