Description:
This was written for an undergraduate course in Network Programming.

This particular piece of code was written in Python to implement a basic web server based solely on the published standards surrounding HTTP and similar protocols.

"""
HTTPython - A simple web server written in Python
Copyright (C) 2011  Jon Olson

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

"""
@author Jon Olson
@requires Python 2.7
"""


import SocketServer


class message( object ):
    """
    The basic unit of HTTP communication, consisting of a structured sequence of octets matching the
    syntax defined in RFC1945 Section 4 and    transmitted via the connection.
    """
    pass

   
class request( message ):
    """
    An HTTP request message (as defined in RFC 1945 Section 5).
    """
    pass

   
class response( message ):
    """
    An HTTP response message (as defined in RFC 1945 Section 6).
    """
    pass

   
class resource( object ):
    """
    A network data object or service which can be identified by a URI (RFC 1945 Section 3.2).
    """
    pass

   
class entity( resource ):
    """
    A particular representation or rendition of a data resource, or reply from a service resource,
    that may be enclosed within a request or response message. An entity consists of metainformation
    in the form of entity headers and content in the form of an entity body.
    """
    pass

   
class client( object ):
    """
    An application program that establishes connections for the purpose of sending requests.
    """
    pass
   
   
class user_agent( client ):
    """
    The client which initiates a request. These are often browsers, editors, spiders (web-traversing
    robots), or other end user tools.
    """
    pass
   
   
class ThreadedTCPServer( SocketServer.ThreadingMixIn, SocketServer.TCPServer ):
    """
    Threading supported for a server instance
    """
    pass


class patterns( object ):
    """
    Much of the content in this section is defined as part of RFC 1945
    """
   
    OCTET        = r'(?s).'
    CHAR          = r'\0-\x7F'
    UPALPHA      = r'A-Z'
    LOALPHA      = r'a-z'
    ALPHA        = LOALPHA + UPALPHA
    DIGIT        = r'0-9'
    CTL          = r'\0-\x1F\x7F'
    CR            = r'\x0D'
    LF            = r'\x0A'
    SP            = r'\x20'
    HT            = r'\x09'
    QUOTE        = r'\x22'
    CRLF          = CR + LF
    #CRLF          = '['+CR + LF+']'
    #CRLF          = r'\r\n'
    LWS          = '(?:(?:' + CRLF +')?[' + SP + HT + ']+)'
    TEXT          = r'(?:[^' + CTL + ']|' + LWS + ')'
    HEX          = DIGIT + r'A-Fa-f'
    tspecials    = r'\(\)<>@,;:\\"/\[\]\?={}' + SP + HT
    token        = r'(?:[^' + CTL + tspecials + r']+)'
    # TEXT excluding "(" and ")"
    ctext        = r'(?:[^' + CTL + '()]|' + LWS + ')'
    comment      = r'\(' + ctext + r'*?\)'
    # CHAR excluding '"', CTLs, but include LWS
    qdtext        = r'(?:[\x20\x21\x23-\x7E]|' + LWS + ')'
    quoted_string = r'"' + qdtext + '*"'
    word          = '(?:' + token + '|' + quoted_string + ')'
   
    # HTTP-Version
    http_version  = r'(?P<HTTP_Version>(?i)HTTP/[' + DIGIT + r']+\.[' + DIGIT + ']+)'
   
    # URI
    reserved    = r';/\?:@&=\+'
    extra      = r"!\*'\(\),"
    safe        = r'\$_\.\-'
    unsafe      = r'(?:[' + CTL + SP + r'"#%<>])'
    national    = r'[^a-zA-Z0-9;/?:@&=+!*\'(),$_.\-\0-\x20\x7F"#%<>]'
    escape      = r'(?:%[' + HEX + '][' + HEX + '])'
    unreserved  = r'(?:[' + ALPHA + DIGIT + safe + extra + ']|' + national + ')'
    uchar      = r'(?:' + unreserved + '|' + escape + ')'
    pchar      = r'(?:' + uchar + '|[:@&=+])'
    fragment    = r'(?:' + uchar + '|[' + reserved + '])*'
    query      = fragment
    net_loc    = r'(?:' + pchar + '|[;?])*'
    scheme      = r'(?:[' + ALPHA + DIGIT + '\+\-\.]+)'
    param      = r'(?:' + pchar + '|[/])*'
    params      = r'(?:' + param + r'(?:[;]' + param + ')*)'
    segment    = r'(?:' + pchar + '*)'
    fsegment    = r'(?:' + pchar + '+)'
    path        = r'(?:' + fsegment + r'(?:[/]' + segment + ')*)'
    rel_path    = r'(?:' + path + '?(?:[;]' + params + ')?(?:[?]' + query + ')?)'
    abs_path    = r'(?:/' + rel_path + ')'
    net_path    = r'(?://' + net_loc + abs_path + '?)'
    relativeURI = r'(?:' + net_path + '|' + abs_path + '|' + rel_path + ')'
    absoluteURI = r'(?:' + scheme + ':' + r'(?:' + uchar + '|[' + reserved + '])*)'

    URI        = r'(?:(?:' + absoluteURI + '|' + relativeURI + ')(?:#' + fragment + ')?)'
   
   
    # http URL
    port        = r'(?:[' + DIGIT + ']*)'
    # from RFC 1123
    ip4digit    = r'(?:[1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
    ip4        = ip4digit.join((r'(?:',r'\.',r'\.',r'\.',')'))
    # from RFC 3513
    ip6digit    = r'(?:[' + HEX + ']{1,4})'
    ip6        = ip6digit.join((r'(?:','(?::','){7}|::' + ip4 + '?|(?:',':){1,7}:|',':(?::',\
                    '){1,6}|(?:',':){2}(?::','){1,5}|(?:',':){3}(?::','){1,4}|(?:',':){4}(?::',\
                    '){1,3}|(?:',':){5}(?::','){1,2}|(?:',':){6}:','|:(?::','){1,7}|(?:(?:',\
                    ':){6}|(?:',':){1,5}:|(?:',':){1,4}:',':|(?:',':){1,3}:(?::','){1,2}:|(?:',\
                    ':){1,2}:(?::','){1,3}:|',':(?::','){1,5}:|:(?::','){1,4}:)' + ip4 + ')'))
    specials    = r'()<>@,;:\\".\[\]'
    atom        = r'(?:[^' + CTL + SP + specials + '\x80-\xFF]+)'
    domainref  = atom
    LWS2        = '(?:(?:(?:' + CRLF + ')?(?:' + SP + '|' + HT + '))+)'
    dtext      = r'[^\[\]\\' + CR + '\x80-\xFF]' + LWS2
    quotedpair  = r'(?:\\[' + CHAR + '])'
    domainlit  = r'(?:\[(?:' + dtext + '|' + quotedpair + r')*\])'
    LetDig      = ALPHA + DIGIT
    LetDigHyp  = LetDig + r'\-'
    LDHstr      = r'(?:[' + LetDigHyp + ']+)'
    label      = r'(?:[' + ALPHA + r'](?:' + LDHstr + '?[' + LetDig + '])?)'
    subdomain  = r'(?:' + domainref + '|' + domainlit + '|' + label + ')'
    domain      = r'(?:' + subdomain + r'(?:\.' + subdomain + r')*| )'
   
    host        = r'(?:' + domain + '|' + ip4 + '|' + ip6 + ')'
   
    http_url    = r'(?:http://' + host + '(?::' + port + ')?' + abs_path + '?)'
   
   
    # HTTP date
    month      = r'(?:J[au]n|Feb|Ma[ry]|Apr|Jul|Aug|Sep|Oct|Nov|Dec)'
    wkday      = r'(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
    weekday    = r'(?:(?:Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day)'
    time        = r'(?:(?:[0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])'
    date1      = r'(?:[' + DIGIT +']{2}' + SP + month + SP + '[' + DIGIT + ']{4})'
    date2      = r'(?:[' + DIGIT +']{2}-' + month + '-[' + DIGIT + ']{2})'
    date3      = r'(?:' + month + SP + '(?:[' + DIGIT + ']|' + SP + ')[' + DIGIT + '])'
    asctime_date = r'(?:' + wkday + SP + date3 + SP + time + SP + '[' + DIGIT + ']{4})'
    rfc850_date  = r'(?:' + weekday + ',' + SP + date2 + SP + time + SP + 'GMT)'
    rfc1123_date = r'(?:' + wkday + ',' + SP + date1 + SP + time + SP + 'GMT)'
   
    http_date  = r'(?:' + rfc1123_date + '|' + rfc850_date + '|' + asctime_date + ')'
   
   
    # media types
    parameter  = r'(?:' + token + r'=(?:' + token + '|' + quoted_string + '))'
    media_type  = r'(?:' + token + '/' + token + '(?:;' + parameter + ')*)'
   
   
    # product tokens
    product    = r'(?:' + token + '(?:/' + token + ')?)'
    products    = r'(?:' + product + '(?:\s+' + product + ')*)'
   
   
    # http messages
    request_uri    = r'(?:' + absoluteURI + '|' + abs_path + ')'
    field_content  = r'(?:' + TEXT + '*|(?:' + token + '|[' + tspecials + ']|' + quoted_string\
                        + ')*)'
    field_value    = r'(?:(?:' + field_content + '|' + LWS + ')*)'
    message_header  = r'(?:' + token + r':(?:' + field_value + ')?)'
    header_breakdown = r'(?:(?P<name>' + token + r'):\s*(?P<value>' + field_value + ')?)'
   
    # HTTP/0.9 messages
    simple_request  = r'(?:GET' + SP + '(?P<Simple_Request>' + request_uri + ')' + CRLF + ')'
    simple_response = r'(?:^' + OCTET + '*)'
   
    # HTTP/1.0 messages
    status_line    = r'(?:' + http_version + SP + '[' + DIGIT + ']{3}' + SP + '(?:[^' + CTL\
                        + ']|[' + SP + HT + ']+)*)'
    request_line    = r'(?P<Request_Line>(?P<REQUEST_METHOD>GET|HEAD|POST|' + token + ')' + SP\
                        + '(?P<Request_URI>' + request_uri + ')' + SP + http_version + ')'
    full_request    = r'(?:' + request_line + CRLF + r'(?P<HEADERS>(?:' + message_header + CRLF\
                        + ')*)' + CRLF + '(?P<Entity_Body>' + OCTET + '*))'
    full_response  = r'(?:' + status_line + CRLF + r'(?:' + message_header + CRLF\
                        + ')*' + CRLF + '(?:' + OCTET + '*))'
                           
    http_request    = r'(?:' + simple_request + '|' + full_request + ')'
    http_response  = r'(?:' + simple_response + '|' + full_response + ')'
   
    http_message    = r'(?si)^(?:' + simple_request + '|' + simple_response + '|' + full_request\
                        + '|' + full_response + ')'
                       
    uri_breakdown    = r'(?P<uri>(?P<absolute>(?P<scheme>' + scheme + ')://(?P<host>(?:' + label\
                        + '(?:[.]' + label + ')*|' + ip4 + '|\[' + ip6 + '\]))(?::(?P<port>' + port\
                        + '))?)?(?P<path>/' + path + '?)(?:[;](?P<parameters>' + params\
                        + '))?(?:[?](?P<query>' + query + '))?(?:[#](?P<fragment>' + fragment\
                        + '))?)'
   
   
    import re
   
    @staticmethod
    def eq( pattern, against, flags = re.S | re.I ):
        """
        Determine if the given pattern completely matches the given test
        """
       
        return patterns.fullMatch( pattern, against, flags ) is not None
   
    @staticmethod
    def contains( pattern, against, flags = re.S | re.I ):
        """
        Determine if the given pattern is contained in the given text
        """
       
        return patterns.search( pattern, against, flags ) is not None
       
    @staticmethod
    def search( pattern, against, flags = re.S | re.I ):
        """
        Determine if the given pattern is contained in the given text
        """
       
        import re
        return re.search( pattern, against, flags )
       
    @staticmethod
    def matches( pattern, against, flags = re.S | re.I ):
        """
        Determines if the given pattern matches the beginning of the given text, returning the
        matching portion
        """
       
        import re
        return re.match( pattern, against, flags )
       
    @staticmethod
    def fullMatch( pattern, against, flags = re.S | re.I ):
        """
        Determines if the given pattern matches the entirety of the given text, returning the
        details
        """
       
        return patterns.matches( '^' + pattern + '$', against, flags )
       
    @staticmethod
    def headerIter( headerData, flags = re.S ):
        """
        Used to break apart the headers in a loop. It returns an iterator
        """
       
        import re
        return re.finditer( patterns.header_breakdown, headerData, flags )
       
       
class header( object ):
    """
    A header used for both request and response handling
    """
   
    def __init__( self, name, contents = None ):
        """
        Initialize the internal variables and assign the passed values using the properties
        @param name The name of the header
        @param contents The content of the header
        """
       
        dbg(name = name, contents = contents)
        self.__name = None
        self.__contents = None
        self.name = name
        self.contents = contents
       
       
    @property
    def name( self ):
        """
        name getter
        """
       
        return self.__name
       
    @name.setter
    def name( self, value ):
        """
        name setter
        """
       
        if patterns.eq( patterns.token, value ):
            self.__name = value
        elif patterns.eq( patterns.message_header, value ):
            m = patterns.fullMatch( patterns.header_breakdown, value )
            self.__name = m.group('name')
            self.contents = m.group('value')
        else:
            raise ValueError
       
       
    @property
    def contents( self ):
        """
        contents getter
        """
       
        return self.__contents
       
    @contents.setter
    def contents( self, value ):
        """
        content setter
        """
       
        if patterns.eq( patterns.field_value, value ):
            self.__contents = value
        else:
            raise ValueError
           
    def __str__( self ):
        """
        string constructor
        """
       
        if self.__contents is None:
            return str( self.__name )
        else:
            return '{}: {}'.format( self.__name, self.__contents )


class ThreadedTCPHandler( SocketServer.StreamRequestHandler ):
    """
    This class handles each request that is received by the server
    """
   
    def handle( self ):
        """
        This method performs the handling
        """
       
        dbg('Got a request from', self.client_address)
       
        import socket
       
        data = ''
        block = 2 ** 14
       
        # read in the data sent from the client
        timing = self.request.gettimeout()
        self.request.settimeout(0.5)
        # fetch data from the buffer until either it gets an incomplete block back, or the timeout
        # happens
        while True:
            try:
                data += self.request.recv( block )
                if len(data) % block != 0:
                    break
            except (socket.timeout):
                break
        self.request.settimeout(timing)
       
        dbg('Request was {} bytes long.'.format(len(data)))
       
        # check to see that it is, in fact, a standard request
        m = patterns.fullMatch( patterns.http_request, data )
        if m is None:
            raise BadRequest, data
       
        dbg('pattern test against the request passed!')
       
        # extract necessary information from the request data
        info = self.extractInfo( m.groupdict() )
        info['version'] = info['version'].upper()
       
        # attempt to handle the
        try:
            response = server.handle( **info )
            response = info['version'] + ' ' + '\r\n'.join(map(str,response))
        except HTTPStatusCode as e:
            response = info['version'] + ' {} {}\r\n'.format(e.code,e.reason)
        self.wfile.write( response )
       
    def extractInfo( self, matchData ):
        """
        Extract the details of the request.
        """
       
        out = dict()
       
        out['uri'] = matchData['Simple_Request']
        if out['uri'] is None:
            out['uri']    = self.extractURI( matchData['Request_URI'] )
            out['method']  = matchData['REQUEST_METHOD'].lower()
            out['version'] = matchData['HTTP_Version'].lower()
        else:
            out['method']  = 'get'
            out['version'] = 'http/0.9'
        out['headers'] = self.extractHeaders( matchData['HEADERS'] )
        out['body'] = matchData['Entity_Body']
       
        dbg('extracted info:', **out)
       
        return out
       
    def extractHeaders( self, headerData ):
        """
        Breakdown the headers into their individual name-value pairs
        """
       
        headers = list()
       
        dbg(headerData = headerData)
       
        for i in patterns.headerIter( headerData ):
            dbg(**i.groupdict())
            headers.append( header(i.group('name'), i.group('value')) )
           
        return headers
       
    def extractURI( self, uri ):
        """
        Breakdown the request URI into its subcomponents, if possible
        """
       
        m = patterns.fullMatch( patterns.uri_breakdown, uri )
       
        # if for some reason it wasn't possible to parse the uri, we'll return it so the code
        # can handle it elsewhere
        if m is not None:
            m = m.groupdict()
            # ensure the port is properly specified
            if m['port'] is None or m['port'] > 65535:
                m['port'] = 80
            return m
        else:
            return {'uri': uri}
           

class HTTPStatusCode( Exception ):
    """
    These are used primarily to communicate the HTTP status codes as specified in RFC 1945
    """
   
    def __init__( self, code, reason ):
        """
        Initialize the exception
        """
       
        Exception.__init__( self )
        self.code = code
        self.reason = reason
       
    def __str__( self ):
        """
        Produce a string version of the exception. This is used by the handling class as a means of
        not needing to know details of the variables internal to this class.
        """
       
        return '{} {}'.format(self.code, self.reason)
       

class Success( HTTPStatusCode ):
    """
    2XX status codes fall under this category
    From RFC 1945:
    This class of status code indicates that the client's request was
    successfully received, understood, and accepted.
    """
    pass


class OK( Success ):
    """
    From RFC 1945:
    The request has succeeded. The information returned with the
    response is dependent on the method used in the request, as follows:
   
    GET    an entity corresponding to the requested resource is sent
           in the response;
   
    HEAD  the response must only contain the header information and
           no Entity-Body;
   
    POST  an entity describing or containing the result of the action.
    """
   
    def __init__( self, code = 200, reason = 'OK' ):
        Success.__init__( self, code, reason )


class Created( Success ):
    """
    From RFC 1945:
    The request has been fulfilled and resulted in a new resource being
    created. The newly created resource can be referenced by the URI(s)
    returned in the entity of the response. The origin server should
    create the resource before using this Status-Code. If the action
    cannot be carried out immediately, the server must include in the
    response body a description of when the resource will be available;
    otherwise, the server should respond with 202 (accepted).
   
    Of the methods defined by this specification, only POST can create a
    resource.
    """
   
    def __init__( self, code = 201, reason = 'Created' ):
        Success.__init__( self, code, reason )


class Accepted( Success ):
    """
    The request has been accepted for processing, but the processing
    has not been completed. The request may or may not eventually be
    acted upon, as it may be disallowed when processing actually takes
    place. There is no facility for re-sending a status code from an
    asynchronous operation such as this.
   
    The 202 response is intentionally non-committal. Its purpose is to
    allow a server to accept a request for some other process (perhaps
    a batch-oriented process that is only run once per day) without
    requiring that the user agent's connection to the server persist
    until the process is completed. The entity returned with this
    response should include an indication of the request's current
    status and either a pointer to a status monitor or some estimate of
    when the user can expect the request to be fulfilled.   
    """
   
    def __init__( self, code = 202, reason = 'Accepted' ):
        Success.__init__( self, code, reason )


class NoContent( Success ):
    """
    The server has fulfilled the request but there is no new
    information to send back. If the client is a user agent, it should
    not change its document view from that which caused the request to
    be generated. This response is primarily intended to allow input
    for scripts or other actions to take place without causing a change
    to the user agent's active document view. The response may include
    new metainformation in the form of entity headers, which should
    apply to the document currently in the user agent's active view.
    """
   
    def __init__( self, code = 204, reason = 'No Content' ):
        Success.__init__( self, code, reason )


class Redirection( HTTPStatusCode ):
    """
    This class of status code indicates that further action needs to be
    taken by the user agent in order to fulfill the request. The action
    required may be carried out by the user agent without interaction
    with the user if and only if the method used in the subsequent
    request is GET or HEAD. A user agent should never automatically
    redirect a request more than 5 times, since such redirections usually
    indicate an infinite loop.
    """
    pass
   
class MultipleChoices( Redirection ):
    """
    This response code is not directly used by HTTP/1.0 applications,
    but serves as the default for interpreting the 3xx class of
    responses.
   
    The requested resource is available at one or more locations.
    Unless it was a HEAD request, the response should include an entity
    containing a list of resource characteristics and locations from
    which the user or user agent can choose the one most appropriate.
    If the server has a preferred choice, it should include the URL in
    a Location field; user agents may use this field value for
    automatic redirection.
    """

    def __init__( self, code = 300, reason = 'Multiple Choices' ):
        Redirection.__init__( self, code, reason )
       

class MovedPermanently( Redirection ):
    """
    The requested resource has been assigned a new permanent URL and
    any future references to this resource should be done using that
    URL. Clients with link editing capabilities should automatically
    relink references to the Request-URI to the new reference returned
    by the server, where possible.
   
    The new URL must be given by the Location field in the response.
    Unless it was a HEAD request, the Entity-Body of the response
    should contain a short note with a hyperlink to the new URL.
   
    If the 301 status code is received in response to a request using
    the POST method, the user agent must not automatically redirect the
    request unless it can be confirmed by the user, since this might
    change the conditions under which the request was issued.

        Note: When automatically redirecting a POST request after
        receiving a 301 status code, some existing user agents will
        erroneously change it into a GET request.
    """

    def __init__( self, code = 301, reason = 'Moved Permanently' ):
        Redirection.__init__( self, code, reason )


class MovedTemporarily( Redirection ):
    """
    The requested resource resides temporarily under a different URL.
    Since the redirection may be altered on occasion, the client should
    continue to use the Request-URI for future requests.
   
    The URL must be given by the Location field in the response. Unless
    it was a HEAD request, the Entity-Body of the response should
    contain a short note with a hyperlink to the new URI(s).
   
    If the 302 status code is received in response to a request using
    the POST method, the user agent must not automatically redirect the
    request unless it can be confirmed by the user, since this might
    change the conditions under which the request was issued.
   
        Note: When automatically redirecting a POST request after
        receiving a 302 status code, some existing user agents will
        erroneously change it into a GET request.
    """

    def __init__( self, code = 302, reason = 'Moved Temporarily' ):
        Redirection.__init__( self, code, reason )


class NotModified( Redirection ):
    """
    If the client has performed a conditional GET request and access is
    allowed, but the document has not been modified since the date and
    time specified in the If-Modified-Since field, the server must
    respond with this status code and not send an Entity-Body to the
    client. Header fields contained in the response should only include
    information which is relevant to cache managers or which may have
    changed independently of the entity's Last-Modified date. Examples
    of relevant header fields include: Date, Server, and Expires. A
    cache should update its cached entity to reflect any new field
    values given in the 304 response.
    """

    def __init__( self, code = 304, reason = 'Not Modified' ):
        Redirection.__init__( self, code, reason )
       
       
class ClientError( HTTPStatusCode ):
    """
    The 4xx class of status code is intended for cases in which the
    client seems to have erred. If the client has not completed the
    request when a 4xx code is received, it should immediately cease
    sending data to the server. Except when responding to a HEAD request,
    the server should include an entity containing an explanation of the
    error situation, and whether it is a temporary or permanent
    condition. These status codes are applicable to any request method.
   
        Note: If the client is sending data, server implementations on TCP
        should be careful to ensure that the client acknowledges receipt
        of the packet(s) containing the response prior to closing the
        input connection. If the client continues sending data to the
        server after the close, the server's controller will send a reset
        packet to the client, which may erase the client's unacknowledged
        input buffers before they can be read and interpreted by the HTTP
        application.
    """


class BadRequest( ClientError ):
    """
    The request could not be understood by the server due to malformed
    syntax. The client should not repeat the request without
    modifications.
    """
   
    def __init__( self, code = 400, reason = 'Bad Request' ):
        ClientError.__init__( self, code, reason )

       
class Unauthorized( ClientError ):
    """
    The request requires user authentication. The response must include
    a WWW-Authenticate header field (Section 10.16) containing a
    challenge applicable to the requested resource. The client may
    repeat the request with a suitable Authorization header field
    (Section 10.2). If the request already included Authorization
    credentials, then the 401 response indicates that authorization has
    been refused for those credentials. If the 401 response contains
    the same challenge as the prior response, and the user agent has
    already attempted authentication at least once, then the user
    should be presented the entity that was given in the response,
    since that entity may include relevant diagnostic information. HTTP
    access authentication is explained in Section 11.
    """
   
    def __init__( self, code = 401, reason = 'Unauthorized' ):
        ClientError.__init__( self, code, reason )

       
class Forbidden( ClientError ):
    """
    The server understood the request, but is refusing to fulfill it.
    Authorization will not help and the request should not be repeated.
    If the request method was not HEAD and the server wishes to make
    public why the request has not been fulfilled, it should describe
    the reason for the refusal in the entity body. This status code is
    commonly used when the server does not wish to reveal exactly why
    the request has been refused, or when no other response is
    applicable.
    """
   
    def __init__( self, code = 403, reason = 'Forbidden' ):
        ClientError.__init__( self, code, reason )

       
class NotFound( ClientError ):
    """
    The server has not found anything matching the Request-URI. No
    indication is given of whether the condition is temporary or
    permanent. If the server does not wish to make this information
    available to the client, the status code 403 (forbidden) can be
    used instead.
    """
   
    def __init__( self, code = 404, reason = 'Not Found' ):
        ClientError.__init__( self, code, reason )
       
       
class ServerError( HTTPStatusCode ):
    """
    Response status codes beginning with the digit "5" indicate cases in
    which the server is aware that it has erred or is incapable of
    performing the request. If the client has not completed the request
    when a 5xx code is received, it should immediately cease sending data
    to the server. Except when responding to a HEAD request, the server
    should include an entity containing an explanation of the error
    situation, and whether it is a temporary or permanent condition.
    These response codes are applicable to any request method and there
    are no required header fields.
    """
    pass
   

class InternalServerError( ServerError ):
    """
    The server encountered an unexpected condition which prevented it
    from fulfilling the request.
    """
   
    def __init__( self, code = 500, reason = 'Internal Server Error' ):
        ServerError.__init__( self, code, reason )
       
       
class NotImplemented( ServerError ):
    """
    The server does not support the functionality required to fulfill
    the request. This is the appropriate response when the server does
    not recognize the request method and is not capable of supporting
    it for any resource.
    """
   
    def __init__( self, code = 501, reason = 'Not Implemented' ):
        ServerError.__init__( self, code, reason )
       
       
class BadGateway( ServerError ):
    """
    The server, while acting as a gateway or proxy, received an invalid
    response from the upstream server it accessed in attempting to
    fulfill the request.
    """
   
    def __init__( self, code = 502, reason = 'Bad Gateway' ):
        ServerError.__init__( self, code, reason )
       
       
class ServiceUnavailable( ServerError ):
    """
    The server is currently unable to handle the request due to a
    temporary overloading or maintenance of the server. The implication
    is that this is a temporary condition which will be alleviated
    after some delay.
   
        Note: The existence of the 503 status code does not imply
        that a server must use it when becoming overloaded. Some
        servers may wish to simply refuse the connection.
    """
   
    def __init__( self, code = 503, reason = 'Service Unavailable' ):
        ServerError.__init__( self, code, reason )
       
       
class server( object ):
    """
    An application program that accepts connections in order to service requests by sending back
    responses.
    """
   
    import logging
   
    def __init__( self ):
        """
        Initialize the internal server(s)
        """
       
        self.__servers = list()
        self.__listen = (('localhost', 8888),)
       
        import SocketServer
       
        for pair in self.__listen:
            dbg( 'Creating server on '+str(pair) )
            self.__servers.append( SocketServer.TCPServer( pair, ThreadedTCPHandler ) )
           
    def start( self ):
        """
        Start up the servers
        """
       
        try:
            for s in self.__servers:
                s.serve_forever()
        except KeyboardInterrupt:
            pass
       
    @staticmethod
    def handle( method, uri, version, headers, body ):
        """
        Handle a request after it has been processed to extract out the information within
        """
       
        dbg( 'method={} uri={} version={} headers={} body={}'.\
            format(method, str(uri), version, str(headers), body))
       
        response = [OK()]
       
        import os, os.path, re
        req = os.path.realpath(os.path.normcase(os.path.normpath(os.getcwd()+uri['path'])))
        if re.match(re.escape(os.getcwd()), req) is not None:
            if os.path.isdir( req ):
                if os.path.isfile(os.path.normcase(os.path.normpath(req + os.sep + 'index.html'))):
                    req = os.path.normcase(os.path.normpath(req + os.sep + 'index.html'))
                    response.extend(server.fileResponse(req))
                else:
                    response.extend(server.directoryListing(os.getcwd(),req))
            elif os.path.isfile( req ):
                response.extend(server.fileResponse(req))
            else:
                response = [NotFound()]
        else:
            response = [NotFound()]
                   
        dbg(response)
        return response
       
    @staticmethod
    def fileResponse( filename ):
        """
        The response is a file, so attempt to guess the mime-type of the file and load the file into
        the output buffer.
        """
       
        t = server.mimetype( filename )
        response = list()
        response.append(header('Content-type',t))
        response.append('')
        response.append(server.getContents( filename ))
        return response
       
    @staticmethod
    def mimetype( filename ):
        """
        Attempt to guess the mime-type of a file
        """
       
        import mimetypes
        mimetypes.init()
        t,e = mimetypes.guess_type( filename )
       
        # if the mime system doesn't know, let's assume it's a plain text file.
        if t is None:
            dbg( filename, 'was typed as None.' )
            t = 'text/plain'
       
        return t
       
    @staticmethod
    def getContents( filename ):
        """
        Get the contents of a file for output
        """
       
        with open( filename, 'rb' ) as f:
            out = ''.join(f.readlines())
            return out
        return None
       
    @staticmethod
    def directoryListing( cwd, d ):
        """
        Generate a directory listing (in html)
        """
       
        import os
       
        # remove any trailing slashes
        cwd = cwd.rstrip(os.sep)
       
        # get a listing of the directory's contents
        l = os.listdir( d )
       
        # ensure there is only one trailing slash on the directory being listed
        d = d[len(cwd):].rstrip(os.sep) + os.sep
       
        # generate the headers necessary
        o = [header('Content-Type','text/html'),'',]
       
        # generate the HTML for the listing
        o.append('<html>')
        o.append('\t<head><title>Directory listing for {}</title></head>'.format(d))
        o.append('\t<body>')
        o.append('\t\t<h1>Directory listing for {}</h1>'.format(d))
        o.append('\t\t<ol>')
        for f in l:
            if os.path.isdir(cwd+d+f):
                f += os.sep
            f = f.replace(os.sep,'/')
            o.append('\t\t\t<li><a href="{0}{1}">{1}</a></li>'.format(d.replace(os.sep,'/'),f))
        o.append('\t\t</ol>')
        o.append('\t</body>')
        o.append('</html>')
       
        # remove this when development is complete
        dbg( o )
       
        return o
       
    def __str__( self ):
        """
        Generate a string representation of the class.
        """
       
        return 'server instance.'
       

def SetupLogging( date = None, fmt = None, fn = None, fm = None, lvl = None ):
    """
    setup the logging system for the server. see the logging module for specific details.
    """
   
    import logging
    import sys
    if date is None:
        date = '%Y-%m-%d %H:%M:%S'
    if fmt is None:
        fmt = '[%(asctime)s] %(name)s: %(levelname)s - %(message)s'
    if fn is None:
        fn = 'http.log'
    if fm is None:
        fm = 'a'
    if lvl is None:
        lvl = logging.DEBUG
    logging.basicConfig( datefmt = date, format = fmt, filename = fn, filemode = fm, level = lvl )
   
   
def dbg( *args, **kwargs ):
    """
    generate a debug message
    @param args
    @param kwargs
    """
   
    a = ' '.join(map(str,args))
    b = list()
    for i in kwargs.items():
        b.append('='.join(map(str,i)))
    a += ' '.join(b)
    import logging
    logging.debug(a)


if __name__ == '__main__':
    """
    This segment of code is only called when the file is run as a script, not a module
    """
   
    SetupLogging()
    svr = server()
    svr.start()
       
# eof