-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathhttp.py
More file actions
84 lines (67 loc) · 2.63 KB
/
http.py
File metadata and controls
84 lines (67 loc) · 2.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
75
76
77
78
79
80
81
82
83
84
"""HTTP client request and response capture.
Importing this module patches http.client.HTTPConnection to record
appmap events of any HTTP requests.
"""
import time
from http.client import HTTPConnection
from urllib.parse import parse_qs, urlsplit
from _appmap.event import HttpClientRequestEvent, HttpClientResponseEvent
from _appmap.recorder import Recorder
from _appmap.utils import patch_class, values_dict
def is_secure(self: HTTPConnection):
"""Checks whether HTTP connection is secure."""
# isinstance(self, HTTPSConnection) won't work with
# eg. urllib3 HTTPConnection. Instead try duck typing.
return hasattr(self, "key_file")
def base_url(self: HTTPConnection):
"""Extract base URL from an HTTPConnection.
Example result: https://appmap.example:3000
"""
scheme = "https" if is_secure(self) else "http"
port = "" if self.port == self.default_port else f":{self.port}"
return f"{scheme}://{self.host}{port}"
# pylint: disable=missing-function-docstring
@patch_class(HTTPConnection)
class HTTPConnectionPatch:
"""Patch methods for HTTPConnection, building and recording appmap events
as requests are issues and responses received.
"""
# pylint: disable=attribute-defined-outside-init
def putrequest(self, orig, method, url, *args, **kwargs):
split = urlsplit(url)
self._appmap_request = HttpClientRequestEvent(
method,
base_url(self) + split.path,
values_dict(parse_qs(split.query).items()),
)
orig(self, method, url, *args, **kwargs)
def putheader(self, orig, header, *values):
request = self._appmap_request.http_client_request
if not hasattr(request, "headers"):
request["headers"] = {}
headers = request["headers"]
if header not in headers:
headers[header] = []
headers[header].extend(values)
orig(self, header, *values)
def getresponse(self, orig):
event = self._appmap_request
del self._appmap_request
request = event.http_client_request
if "headers" in request:
request["headers"] = values_dict(request["headers"].items())
enabled = Recorder.get_enabled()
if enabled:
Recorder.add_event(event)
start = time.monotonic()
response = orig(self)
if enabled:
Recorder.add_event(
HttpClientResponseEvent(
response.status,
headers=response.headers,
elapsed=(time.monotonic() - start),
parent_id=event.id,
)
)
return response