0001-misc-log-HTTP-response-headers-safely-43201.patch
passerelle/utils/__init__.py | ||
---|---|---|
168 | 168 |
return False |
169 | 169 | |
170 | 170 | |
171 |
def make_headers_safe(headers): |
|
172 |
'''Convert dict of HTTP headers to text safely, as some services returns 8-bits encoding in headers. |
|
173 |
''' |
|
174 |
return { |
|
175 |
force_text(key, errors='replace'): force_text(value, errors='replace') |
|
176 |
for key, value in headers.items() |
|
177 |
} |
|
178 | ||
179 | ||
171 | 180 |
def log_http_request(logger, request, response=None, exception=None, error_log=True, extra=None): |
172 | 181 |
log_function = logger.info |
173 | 182 |
message = '' |
174 | 183 |
extra = extra or {} |
184 | ||
175 | 185 |
if request is not None: |
176 | 186 |
message = '%s %s' % (request.method, request.url) |
177 | 187 |
extra['request_url'] = request.url |
178 | 188 |
if logger.level == 10 and request: # DEBUG |
179 |
extra['request_headers'] = dict(request.headers.items())
|
|
189 |
extra['request_headers'] = make_headers_safe(request.headers)
|
|
180 | 190 |
if request.body: |
181 | 191 |
if hasattr(logger, 'connector'): |
182 | 192 |
max_size = logger.connector.logging_parameters.requests_max_size |
... | ... | |
187 | 197 |
message = message + ' (=> %s)' % response.status_code |
188 | 198 |
extra['response_status'] = response.status_code |
189 | 199 |
if logger.level == 10: # DEBUG |
190 |
extra['response_headers'] = dict(response.headers.items())
|
|
200 |
extra['response_headers'] = make_headers_safe(response.headers)
|
|
191 | 201 |
# log body only if content type is allowed |
192 | 202 |
if content_type_match(response.headers.get('Content-Type')): |
193 | 203 |
if hasattr(logger, 'connector'): |
tests/test_requests.py | ||
---|---|---|
3 | 3 |
import pytest |
4 | 4 |
import mohawk |
5 | 5 |
import mock |
6 |
import requests |
|
7 | ||
6 | 8 |
from httmock import urlmatch, HTTMock, response |
7 | 9 | |
8 | 10 |
from django.test import override_settings |
9 | 11 | |
10 |
from passerelle.utils import Request, CaseInsensitiveDict |
|
12 |
from passerelle.utils import Request, CaseInsensitiveDict, log_http_request
|
|
11 | 13 |
from passerelle.utils.http_authenticators import HawkAuth |
12 | 14 |
import utils |
13 | 15 |
from utils import FakedResponse |
14 | 16 | |
15 | 17 | |
18 | ||
16 | 19 |
class MockFileField(object): |
17 | 20 |
def __init__(self, path): |
18 | 21 |
self.path = path |
... | ... | |
383 | 386 |
assert mocked_get.call_args[1]['timeout'] == 42 |
384 | 387 |
Request(logger=logger).get('http://example.net/whatever', timeout=None) |
385 | 388 |
assert mocked_get.call_args[1]['timeout'] is None |
389 | ||
390 | ||
391 |
def test_log_http_request(caplog): |
|
392 |
@urlmatch() |
|
393 |
def bad_headers(url, request): |
|
394 |
return response(200, 'coin', |
|
395 |
headers={'Error Webservice': b'\xe9'}, |
|
396 |
request=request) |
|
397 |
with HTTMock(bad_headers): |
|
398 |
resp = requests.get('https://example.com/') |
|
399 |
caplog.set_level(logging.DEBUG) |
|
400 |
assert len(caplog.records) == 0 |
|
401 |
log_http_request(logging.getLogger(), resp.request, resp) |
|
402 |
assert len(caplog.records) == 1 |
|
403 |
extra = {key: value for key, value in caplog.records[0].__dict__.items() if key.startswith(('request_', 'response_'))} |
|
404 |
del extra['request_headers']['User-Agent'] |
|
405 |
assert extra == { |
|
406 |
'request_headers': { |
|
407 |
u'Accept': u'*/*', |
|
408 |
u'Accept-Encoding': u'gzip, deflate', |
|
409 |
u'Connection': u'keep-alive', |
|
410 |
}, |
|
411 |
'request_url': 'https://example.com/', |
|
412 |
'response_headers': { |
|
413 |
u'Error Webservice': u'\ufffd' |
|
414 |
}, |
|
415 |
'response_status': 200 |
|
416 |
} |
|
386 |
- |