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
|
""" 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, request=request))
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, req, queue=queue)
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, req, queue=queue)
else:
resp.code = code
resp.body = 'Error %d' % code
resp.set_content_length()
except FileAccessError:
self.serve_error(403, req, queue=queue)
conn = 'close'
if req != None and req.get_header('Connection') == 'keep-alive':
conn = 'keep-alive'
resp.set_header('Connection', conn)
queue.put(resp)
def serve_error(self, code, request, queue=None):
return self.serve(config('error%d' % code, default=None),
code=code, request=request, 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())
|