summaryrefslogtreecommitdiff
path: root/apifw/http.py
blob: 0675442be6e72040b8615b7d308534095021cf5b (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
# 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]