summaryrefslogtreecommitdiff
path: root/apifw/http.py
blob: 7b5a8e9a287e5e08724608b6d888692356a07e8e (plain)
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
130
131
132
133
134
135
136
# 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_callback(self):
        log = {
            'msg_type': 'http-callback',
        }
        self._logger(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._log_callback()
            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]