summaryrefslogtreecommitdiff
path: root/project1/proj1_s4498062/webhttp/composer.py
blob: bce6208325aa45f35f4691fcd04d3e51f0004198 (plain) (blame)
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
""" Composer for HTTP responses

This module contains a composer, which can compose responses to
HTTP requests from a client.
"""

from multiprocessing import Process, Queue
import re
import time

from config import config
import encodings
from message import Response
from resource import Resource, FileExistError, FileAccessError
import weblogging as logging

class ResponseComposer:
    """Class that composes a HTTP response to a HTTP request"""

    def __init__(self, timeout):
        """Initialize the ResponseComposer
        
        Args:
            timeout (int): connection timeout
        """
        self.timeout = timeout
    
    def compose_response(self, request):
        """Compose a response to a request
        
        Args:
            request (webhttp.Request): request from client

        Returns:
            webhttp.Response: response to request

        """
        response = Response()

        q = Queue()

        if re.search(r'\/\.\.?(\/|$)', request.uri):
            p = Process(target=self.serve_error, 
                    args=(403,), kwargs=dict(queue=q))
        else:
            p = Process(target=self.serve, 
                    args=(request.uri,), kwargs=dict(queue=q, request=request))

        p.start()
        p.join(self.timeout)

        if p.is_alive():
            p.terminate()
            p.join()

            logging.warning('Had to kill Composer.serve')

            resp = Response()
            resp.code = 504
            resp.set_header('Connection', 'close')
            resp.body = 'Internal timeout'
            resp.set_content_length()
        else:
            try:
                resp = q.get(False)
            except:
                # After a timeout, we just make the most minimalistic response
                # here (that is, no special error files are considered).
                resp = Response()
                resp.code = 500
                resp.set_header('Connection', 'close')
                resp.body = 'This should never happen'
                resp.set_content_length()
        
        return resp


    def serve(self, uri, code=200, etag=None, request=None, queue=None):
        resp = Response()
        req = request

        try:
            resource = Resource(uri)
            if req != None and (
                    resource.etag_match(req.get_header('If-None-Match')) or \
                    req.get_header('If-Match') != None and \
                    not resource.etag_match(req.get_header('If-Match'))):
                resp.code = 304
            else:
                resp.code = code
                resp.body = None
                encs = req.encodings() if req != None else [encodings.IDENTITY]
                for enc in encs:
                    try:
                        resp.body = resource.get_content(enc)
                        resp.set_header('Content-Encoding', encodings.str(enc))
                        break
                    except UnknownEncodingError:
                        pass
                if resp.body == None:
                    return self.serve_error(406)
            resp.set_header('ETag', resource.generate_etag())
            resp.set_header('Content-Type', resource.get_content_type())
            resp.set_content_length()
        except FileExistError:
            if code < 400:
                self.serve_error(404, queue=queue)
            else:
                resp.code = code
                resp.body = 'Error %d' % code
                resp.set_content_length()
        except FileAccessError:
            self.serve_error(403, queue=queue)
        
        conn = 'keep-alive'
        if req != None and req.get_header('Connection') == 'close':
            conn = 'close'
        resp.set_header('Connection', conn)

        queue.put(resp)

    def serve_error(self, code, queue=None):
        return self.serve(config('error%d' % code, default=None), 
                code=code, queue=queue)

    def make_date_string(self):
        """Make string of date and time
        
        Returns:
            str: formatted string of date and time
        """
        return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())