Exemple #1
0
class ApricotParser(object):
    ''' Apricot Http Parser '''
    def __init__(self, parseType="request"):
        # create abstract parser
        self.parser = AbstractParser()

        # determine httptools parser
        if parseType.lower() == "request":
            self.parsed = HttpRequestParser(self.parser)
        else:
            self.parsed = HttpResponseParser(self.parser)

        # set null url info
        self.url = None
        self.host = None
        self.schema = None
        self.port = None
        self.path = None
        self.params = {}

    async def feed_async(self, data=b''):
        self.feed(data)

    def feed(self, data=b''):
        ''' feed data to parser '''
        self.raw_data = data
        self.parsed.feed_data(memoryview(data))
        self.set_attributes()  # set apricot attributes

    def set_attributes(self):
        ''' set ApricotParser attributes '''

        # check parser type
        if isinstance(self.parsed, HttpRequestParser):
            self.method = self.parsed.get_method().decode()
        else:
            self.status = self.parsed.get_status_code()

        # set basic attr's
        try:
            self.headers = self.parser.headers
            self.body = self.parser.body
            self.http_ver = self.parsed.get_http_version()
            self.keep_alive = self.parsed.should_keep_alive()
        except Exception as e:
            print("error in parser: " + str(e))

        if self.keep_alive == None: self.keep_alive = False

        # parse url and get info
        self.get_url_info()

    def get_url_info(self):
        ''' Parse url info '''
        if 'Host' in self.headers:
            try:
                # make url
                self.url = b'http://'
                self.url += self.headers['Host'].encode()

                # add path if request
                if isinstance(self.parsed, HttpRequestParser):
                    path = self.raw_data.split(b'\r\n')[0].split()[1]
                    self.url += path
                    self.path = path

                # parse url and get basic info
                URL = parse_url(self.url)
                self.schema = URL.schema.decode()
                self.host = URL.host.decode()
                self.port = URL.port

                # get url query parameters if any
                if URL.query is not None:
                    for param in URL.query.split(b'&'):
                        parts = param.split(b'=')
                        key = parts[0].decode()
                        value = b'='.join(parts[1:]).decode()
                        self.params[key] = value

                # decode url from bytes to string
                self.url = self.url.decode()
            except:
                pass
Exemple #2
0
class HttpProtocol:
    """The protocol class constructs the incoming request and response."""

    def __init__(self, bufsize: int = 2048):
        self._bufsize = bufsize
        self._data_hooks = []  # type: List[Callable]
        self._url_hooks = []  # type: List[Callable]
        self._parser = None  # type: PARSER_TYPE
        self._is_done = False
        self._body = bytes()
        self.msg = None

    def add_url_hook(self, func: Callable):
        """Add hook to be executed during on_url()"""
        self._url_hooks.append(func)

    def add_data_hook(self, func: Callable):
        """Add hook to be executed during recv()"""
        self._data_hooks.append(func)

    def set_bufsize(self, bufsize: int):
        """Set the bytes size to read from the socket"""
        self._bufsize = bufsize

    def set_request_parser(self):
        """Set a new request parser with self as callback"""
        self._parser = HttpRequestParser(self)

    def set_response_parser(self):
        """Set a new response parser with self as callback"""
        self._parser = HttpResponseParser(self)

    def set_request(self, request: dict):
        """Set new request"""
        request['version'] = 'HTTP/1.1'
        request['method'] = 'GET'
        request['headers'] = {}
        request['body'] = {}
        request['url'] = {
            'scheme': '',
            'netloc': '',
            'path': '',
            'params': '',
            'query': {},
            'fragment': ''
        }
        self.msg = request

    def set_response(self, response: dict):
        """Set new response"""
        response['version'] = 'HTTP/1.1'
        response['status'] = 200
        response['reason'] = 'OK'
        response['headers'] = {}
        response['body'] = {}
        self.msg = response

    def feed(self, data: bytes):
        """feed data to the underlying parser"""
        for hook in self._data_hooks:
            hook(data)

        self._parser.feed_data(data)

    def recv(self, sock: socket.socket):
        """Receive all incoming http data on a socket and execute hooks"""
        while not self._is_done:
            data = sock.recv(self._bufsize)
            self.feed(data)

    # HTTPTOOLS CALLBACK METHODS
    def on_message_begin(self):
        """Triggered on first bytes"""

    def on_status(self, status: bytes):
        """Status integer callback"""
        if self.msg is not None:
            self.msg['status'] = self._parser.get_status_code()
            self.msg['reason'] = status.decode()

    def on_url(self, url: bytes):
        """Request message attribute will have url and method at this point"""
        url = parse_url(url)

        if self.msg is not None:
            self.msg['url'] = url
            self.msg['method'] = self._parser.get_method().decode()

        for hook in self._url_hooks:
            hook(url)

    def on_header(self, name: bytes, value: bytes):
        """Alter request header as they come in if necessary"""
        if self.msg is not None:
            self.msg['headers'][name.decode().lower()] = value.decode()

    def on_headers_complete(self):
        """End of headers trigger"""

    def on_body(self, body: bytes):
        """Optionally access body stream"""
        self._body += body

    def on_message_complete(self):
        """Trigger when message finishes"""

        if self.msg is not None:
            self.msg['version'] = 'HTTP/' + self._parser.get_http_version()

            if self.msg['headers'].get('content-encoding', '') == 'gzip':
                self._body = gzip.decompress(self._body)
            self.msg['body'] = load_json(self._body)

        self._is_done = True

    def on_chunk_header(self):
        """Chunked message header"""

    def on_chunk_complete(self):
        """Chunked response end"""