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
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
|
# Copyright (C) 2017 Lars Wirzenius
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
HTTP_OK = 200
HTTP_CREATED = 201
HTTP_UNAUTHORIZED = 401
HTTP_FORBIDDEN = 403
HTTP_NOT_FOUND = 404
HTTP_BAD_REQUEST = 400
HTTP_CONFLICT = 409
HTTP_CONFLICT = 409
HTTP_LENGTH_REQUIRED = 411
class HttpTransaction:
# This class executes HTTP transactions. It is called by the web
# framework, by some method that we don't specify. See the
# BottleLoggingPlugin to use Bottle. This class is independent
# from Bottle. The entry point is the "perform_tranaction" method,
# which is tgiven the callback to call for the HTTP request that
# has been made. This class makes the call, but also logs the
# request and response, and catches and logs any excpetion.
# Actually, the request and response get logged by a sub-class of
# this class, which is supposed to define the
# "construct_request_log" and "construct_response_log" methods.
#
# Log messages, as used by this class, are dicts, with no
# particular specified keys, although "msg_type" is added by this
# class. The request and response methods are supposed to return
# dicts that get merged with what this class provides. The merged
# result is then actually logged.
def __init__(self):
self._logger = lambda log, **kwargs: None
self._counter = lambda: None
def construct_request_log(self):
# Override in subclass, as necessary.
return {}
def construct_response_log(self):
# Override in subclass, as necessary.
return {}
def amend_response(self):
# Make any necessary changes to the response.
# Override in subclass, as necessary.
pass
def set_dict_logger(self, logger):
# Set function to actually do logging.
self._logger = logger
def set_transaction_counter(self, counter):
self._counter = counter
def _log_request(self):
log = {
'msg_type': 'http-request',
}
self._logger(self._combine_dicts(log, self.construct_request_log()))
def _log_response(self):
log = {
'msg_type': 'http-response',
}
self._logger(self._combine_dicts(log, self.construct_response_log()))
def _log_error(self, exc):
log = {
'msg_type': 'error',
'msg_text': 'caught exception at runtime',
'exception': str(exc),
}
self._logger(self._combine_dicts(log), stack_info=True)
def _combine_dicts(self, *dicts):
log = {}
for d in dicts:
log.update(d)
return log
def perform_transaction(self, callback, *args, **kwargs):
try:
self._counter()
self._log_request()
data = callback(*args, **kwargs)
self.amend_response()
self._log_response()
return data
except SystemExit:
# If we're exiting, we exit. No need to log an error.
raise
except BaseException as e:
# Everything else results in an error logged.
self._log_error(e)
raise
class Response:
def __init__(self, values):
self._dict = {}
self._keys = ['status', 'headers', 'body']
for key in self._keys:
self[key] = ''
for key, value in values.items():
self[key] = value
def __setitem__(self, key, value):
assert key in self._keys
self._dict[key] = value
def __getitem__(self, key):
return self._dict[key]
|