""" 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())