Completed
Push — master ( 39535f...23b608 )
by Thomas
9s
created

ppp_libmodule.HttpRequestHandler.make_response()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1
Metric Value
cc 1
dl 0
loc 10
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
1
"""Handles the HTTP frontend (ie. answers to requests from a
2
UI."""
3
4 1
import time
5 1
import json
6 1
import logging
7
8 1
from ppp_datamodel.exceptions import AttributeNotProvided
9 1
from ppp_datamodel.communication import Request
10
11 1
from .config import Config
12 1
from .exceptions import ClientError, BadGateway, InvalidConfig
13
14 1
DOC_URL = 'https://github.com/ProjetPP/Documentation/blob/master/' \
15
          'module-communication.md#frontend'
16
17 1
class HttpRequestHandler:
18
    """Handles one request."""
19 1
    def __init__(self, environ, start_response, router_class):
20 1
        self.environ = environ
21 1
        self.start_response = start_response
22 1
        self.router_class = router_class
23 1
    def make_response(self, status, content_type, response):
24
        """Shortcut for making a response to the client's request."""
25 1
        headers = [('Access-Control-Allow-Origin', '*'),
26
                   ('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'),
27
                   ('Access-Control-Allow-Headers', 'Content-Type'),
28
                   ('Access-Control-Max-Age', '86400'),
29
                   ('Content-type', content_type)
30
                  ]
31 1
        self.start_response(status, headers)
32 1
        return [response.encode()]
33
34 1
    def on_bad_method(self):
35
        """Returns a basic response to GET requests (probably sent by humans
36
        trying to open the link in a web browser."""
37 1
        text = 'Bad method, only POST is supported. See: ' + DOC_URL
38 1
        return self.make_response('405 Method Not Allowed',
39
                                  'text/plain',
40
                                  text
41
                                 )
42
43 1
    def on_unknown_uri(self):
44
        """Returns a basic response to GET requests (probably sent by humans
45
        trying to open the link in a web browser."""
46
        text = 'URI not found, only / is supported. See: ' + DOC_URL
47
        return self.make_response('404 Not Found',
48
                                  'text/plain',
49
                                  text
50
                                 )
51
52 1
    def on_bad_request(self, hint):
53
        """Returns a basic response to invalid requests."""
54 1
        return self.make_response('400 Bad Request',
55
                                  'text/plain',
56
                                  hint
57
                                 )
58
59 1
    def on_bad_gateway(self, exc):
60
        """Returns a basic response when a module is buggy."""
61
        return self.make_response('502 Bad Gateway',
62
                                  'text/plain',
63
                                  exc.args[0]
64
                                 )
65
66 1
    def on_client_error(self, exc):
67
        """Handler for any error in the request detected by the module."""
68 1
        return self.on_bad_request(exc.args[0])
69
70
    def on_internal_error(self): # pragma: no cover
71
        """Returns a basic response when the module crashed"""
72
        return self.make_response('500 Internal Server Error',
73
                                  'text/plain',
74
                                  'Internal server error. Sorry :/'
75
                                 )
76
77 1
    def _get_times(self):
78 1
        wall_time = time.time()
79 1
        get_process_time = getattr(time, 'process_time', None)
80 1
        if get_process_time: # Python ≥ 3.3 only
81 1
            process_time = get_process_time()
82
        else:
83
            process_time = None
84 1
        return (wall_time, process_time)
85
86 1
    def _add_times_to_answers(self, answers, start_wall_time, start_process_time):
87 1
        (end_wall_time, end_process_time) = self._get_times()
88 1
        times_dict = {'start': start_wall_time, 'end': end_wall_time}
89 1
        if start_wall_time and end_process_time:
90 1
            times_dict['cpu'] = end_process_time - start_process_time
91 1
        for answer in answers:
92 1
            if not answer.trace:
93
                continue
94 1
            if answer.trace[0].times == {}:
95 1
                answer.trace[0].times.update(times_dict)
96
97 1
    def process_request(self, request):
98
        """Processes a request."""
99 1
        try:
100 1
            request = Request.from_json(request.read().decode())
101 1
        except ValueError:
102 1
            raise ClientError('Data is not valid JSON.')
103 1
        except KeyError:
104
            raise ClientError('Missing mandatory field in request object.')
105 1
        except AttributeNotProvided as exc:
106 1
            raise ClientError('Attribute not provided: %s.' % exc.args[0])
107
108 1
        (start_wall_time, start_process_time) = self._get_times()
109 1
        answers = self.router_class(request).answer()
110 1
        self._add_times_to_answers(answers, start_wall_time, start_process_time)
111
112 1
        answers = [x.as_dict() for x in answers]
113 1
        return self.make_response('200 OK',
114
                                  'application/json',
115
                                  json.dumps(answers)
116
                                 )
117
118 1
    def on_post(self):
119
        """Extracts the request, feeds the module, and returns the response."""
120 1
        request = self.environ['wsgi.input']
121 1
        try:
122 1
            return self.process_request(request)
123 1
        except ClientError as exc:
124 1
            return self.on_client_error(exc)
125
        except BadGateway as exc:
126
            return self.on_bad_gateway(exc)
127
        except InvalidConfig:
128
            raise
129
        except Exception as exc: # pragma: no cover # pylint: disable=W0703
130
            logging.error('Unknown exception: ', exc_info=exc)
131
            return self.on_internal_error()
132
133 1
    def on_options(self):
134
        """Tells the client we allow requests from any Javascript script."""
135
        return self.make_response('200 OK', 'text/html', '')
136
137 1
    def dispatch(self):
138
        """Handles dispatching of the request."""
139 1
        method_name = 'on_' + self.environ['REQUEST_METHOD'].lower()
140 1
        method = getattr(self, method_name, None)
141 1
        if method:
142 1
            return method()
143
        else:
144 1
            return self.on_bad_method()
145
146