Message.py 7.73 KB
"""
@author Georg Hopp

"""
from ..Message import Message as BaseMessage

class Message(BaseMessage):
    START_READY   = 0x01
    HEADERS_READY = 0x02
    BODY_READY    = 0x04

    METHODS = ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT')
    METHOD_OPTIONS = METHODS.index('OPTIONS')
    METHOD_GET     = METHODS.index('GET')
    METHOD_HEAD    = METHODS.index('HEAD')
    METHOD_POST    = METHODS.index('POST')
    METHOD_PUT     = METHODS.index('PUT')
    METHOD_DELETE  = METHODS.index('DELETE')
    METHOD_TRACE   = METHODS.index('TRACE')
    METHOD_CONNECT = METHODS.index('CONNECT')

    def __init__(self, remote):
        super(Message, self).__init__(remote)
        self.state = 0

        self._chunk_size = 0
        self._chunked    = False

        self._headers = {}
        self._body    = ''

        self._http    = None
        self._method  = None
        self._uri     = None
        self._code    = None
        self._message = None

    """
    cleaner
    =====================================================================
    """
    def resetStartLine(self):
        self._http    = None
        self._uri     = None
        self._code    = None
        self._message = None
        self.state &= ~Message.START_READY

    def resetHeaders(self):
        self._headers = {}
        self.state &= ~Message.HEADERS_READY

    def resetBody(self):
        self._body = ''
        self.state &= ~Message.BODY_READY
        self._chunked = False
        self._chunk_size = 0

    def reset(self):
        self.resetStartLine()
        self.resetHeaders()
        self.resetBody()

    def removeHeadersByKey(self, key):
        """
        Remove HTTP headers to a given key. This will remove all headers right
        now associated to that key. Keys are alwasys stored lower case and
        cenverted to title case during composition.

        returns None

        @key: str The header key to remove.
        """
        if key.lower() in self._headers:
            del(self._headers[key.lower()])

    def removeHeader(self, header):
        """
        Remove a header.

        returns None

        @header: tuple Holds key and value of the header to remove.
        """
        key = header[0].lower()
        if key in self._headers:
            if header[1] in self._headers[key]:
                self._headers[key].remove(header[1])

    """
    setter
    =====================================================================
    """
    def setRequestLine(self, method, uri, http):
        if self.isResponse():
            raise Exception('try to make a request from a response')
        self._method = method
        self._uri    = uri
        self._http   = http

    def setStateLine(self, http, code, message):
        if self.isRequest():
            raise Exception('try to make a response from a request')
        self._http    = http
        self._code    = code
        self._message = message

    def setHeader(self, key, value):
        """
        Add a header to the message.
        Under some circumstances HTTP allows to have multiple headers with
        the same key. Thats the reason why the values are handled in a list
        here.

        Returns None

        key:   The header key (The part before the colon :).
        value: The header value (The part behind the colon :).
               Value might also be a list a values for this key.
        """
        key = key.lower()
        if key in self._headers:
            self._headers[key] += [v.strip() for v in value.split(',')
                                   if v.strip() not in self._headers[key]]
        else:
            self._headers[key.lower()] = [v.strip() for v in value.split(',')]

    def replaceHeader(self, key, value):
        self._headers[key.lower()] = [v.strip() for v in value.split(',')]

    def setHeaders(self, headers):
        """
        This sets a bunch of headers at once. It will add the headers and not
        override anything. It is neccessary to clear the headers before calling
        this if only the headers given here should be in the message.

        Returns None

        headers: Either a list of tuples [(key,value),...] or
                 a dictionary {key:value,...}.
                 In both cases the values should be a list again.
        """
        if type(headers) == dict:
            headers = headers.items()

        for h in headers:
            self.setHeader(h[0], h[1])

    def setBody(self, data):
        """
        Set the body of a message. Currently we do not support sending 
        chunked message so this is simple...

        Returns None

        data: The data to set in the message body.
        """
        self.replaceHeader('Content-Length', '%d'%len(data))
        self._body = data

    """
    getter
    =====================================================================
    """
    def getHttpVersion(self):
        return self._http

    def getMethod(self):
        return self._method

    def getUri(self):
        return self._uri

    def getResponseCode(self):
        return self._code

    def getResponseMessage(self):
        return self._message

    def getStartLine(self):
        line = ''
        if self.isRequest():
            method = Message.METHODS[self._method]
            line = ' '.join((method, self._uri, self._http))
        elif self.isResponse():
            line = ' '.join((self._http, str(self._code), self._message))
        return line

    def getHeaders(self):
        return [(k, self.getHeader(k)) for k in self._headers]

    def getHeader(self, key):
        """
        Get all values currently associated to this header key.

        returns list All values to the given key.

        @key: str The key to get values for.
        """
        key = key.lower()
        if key not in self._headers: return ''
        return ', '.join(self._headers[key])

    def getBody(self):
        return self._body


    """
    checker
    =====================================================================
    """
    def headerKeyExists(self, key):
        return key.lower() in self._headers

    def startlineReady(self):
        return Message.START_READY == self.state & Message.START_READY

    def headersReady(self):
        return Message.HEADERS_READY == self.state & Message.HEADERS_READY

    def bodyReady(self):
        return Message.BODY_READY == self.state & Message.BODY_READY

    def ready(self):
        return self.headersReady() and self.bodyReady()

    def isRequest(self):
        return self._method is not None

    def isResponse(self):
        return self._code is not None

    def isCloseMessage(self):
        if self.isRequest():
            # HTTP always expects a response to be send, so a request is
            # never the close message.
            return False
        else:
            con_header = self.getHeader('Connection').lower()
            if self._http == 'HTTP/1.0':
                return 'keep-alive' not in con_header
            else:
                return 'close' in con_header

    def isUpgradeMessage(self):
        con_header = self.getHeader('Connection').lower()
        return 'upgrade' in con_header

    def isOptions(self):
        return Message.METHOD_OPTIONS == self.getMethod()

    def isGet(self):
        return Message.METHOD_GET == self.getMethod()

    def isHead(self):
        return Message.METHOD_HEAD == self.getMethod()

    def isPost(self):
        return Message.METHOD_POST == self.getMethod()

    def isPut(self):
        return Message.METHOD_PUT == self.getMethod()

    def isDelete(self):
        return Message.METHOD_DELETE == self.getMethod()

    def isTrace(self):
        return Message.METHOD_TRACE == self.getMethod()

    def isConnect(self):
        return Message.METHOD_CONNECT == self.getMethod()

# vim: set ft=python et ts=8 sw=4 sts=4: