Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/hip/_body.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import io
import zlib

from ._parsers import HTTPParser
from ._streams import Stream
from .parsers import HTTPParser

__all__ = ["GZipStream", "HTTPStream"]

Expand Down
2 changes: 1 addition & 1 deletion src/hip/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from ._body import HTTPStream
from ._content import Text
from ._headers import Headers
from ._parsers import HTTPParser
from ._request import Request
from ._response import Response
from ._timeouts import sleep
from .backends.sync import NetworkBackend
from .parsers import HTTPParser

__all__ = ["Server"]

Expand Down
2 changes: 1 addition & 1 deletion src/hip/parsers.py → src/hip/parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._parsers import HTTPParser, ProtocolError
from .http import HTTPParser, ProtocolError

__all__ = [
"HTTPParser",
Expand Down
2 changes: 2 additions & 0 deletions src/hip/parsers/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class ProtocolError(Exception):
pass
87 changes: 3 additions & 84 deletions src/hip/_parsers.py → src/hip/parsers/http.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import enum
import time

from ._streams import Stream
from .._streams import Stream
from .exceptions import ProtocolError
from .readahead import ReadAheadParser

__all__ = ["HTTPParser", "Mode", "ProtocolError"]

Expand Down Expand Up @@ -46,10 +48,6 @@ class Mode(enum.Enum):
# 3. ...


class ProtocolError(Exception):
pass


class HTTPParser:
"""
Usage...
Expand Down Expand Up @@ -432,85 +430,6 @@ def __repr__(self) -> str:
return f"<HTTPParser [{detail}]>"


class ReadAheadParser:
"""
A buffered I/O stream, with methods for read-ahead parsing.
"""

def __init__(self, stream: Stream) -> None:
self._buffer = b""
self._stream = stream
self._chunk_size = 4096

def _read_some(self) -> bytes:
if self._buffer:
ret, self._buffer = self._buffer, b""
return ret
return self._stream.read(self._chunk_size)

def _push_back(self, buffer):
assert self._buffer == b""
self._buffer = buffer

def read(self, size: int) -> bytes:
"""
Read and return up to 'size' bytes from the stream, with I/O buffering provided.

* Returns b'' to indicate connection close.
"""
buffer = bytearray()
while len(buffer) < size:
chunk = self._read_some()
if not chunk:
break
buffer.extend(chunk)

if len(buffer) > size:
buffer, push_back = buffer[:size], buffer[size:]
self._push_back(bytes(push_back))
return bytes(buffer)

def read_until(
self,
marker: bytes,
max_size: int,
exc_text: str,
allow_empty_eof: bool = False,
) -> bytes:
"""
Read and return bytes from the stream, delimited by marker.

* The marker is not included in the return bytes.
* The marker is consumed from the I/O stream.
* Raises `EOFError` if the stream closes before any bytes are read and
`allow_empty_eof` is set.
* Raises `ProtocolError` if the stream closes before the marker otherwise.
* Raises `ProtocolError` if marker did not occur within 'max_size + len(marker)' bytes.
"""
buffer = bytearray()
while len(buffer) <= max_size:
chunk = self._read_some()
if not chunk:
# EOF is only clean if no part of the line has arrived.
if allow_empty_eof and not buffer:
raise EOFError
# stream closed before marker found.
raise ProtocolError(f"Stream closed early {exc_text}.")
start_search = max(len(buffer) - len(marker), 0)
buffer.extend(chunk)
index = buffer.find(marker, start_search)

if index > max_size:
# marker was found, though 'max_size' exceeded.
raise ProtocolError(f"Exceeded maximum size {exc_text}")
elif index >= 0:
endindex = index + len(marker)
self._push_back(bytes(buffer[endindex:]))
return bytes(buffer[:index])

raise ProtocolError(f"Exceeded maximum size {exc_text}")


def bounded_int(intstr: bytes, max_digits: int, exc_text: str):
if len(intstr) > max_digits:
# Length of bytestring exceeds maximum.
Expand Down
81 changes: 81 additions & 0 deletions src/hip/parsers/readahead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from .._streams import Stream
from .exceptions import ProtocolError


class ReadAheadParser:
"""
A buffered I/O stream, with methods for read-ahead parsing.
"""

def __init__(self, stream: Stream) -> None:
self._buffer = b""
self._stream = stream
self._chunk_size = 4096

def _read_some(self) -> bytes:
if self._buffer:
ret, self._buffer = self._buffer, b""
return ret
return self._stream.read(self._chunk_size)

def _push_back(self, buffer):
assert self._buffer == b""
self._buffer = buffer

def read(self, size: int) -> bytes:
"""
Read and return up to 'size' bytes from the stream, with I/O buffering provided.

* Returns b'' to indicate connection close.
"""
buffer = bytearray()
while len(buffer) < size:
chunk = self._read_some()
if not chunk:
break
buffer.extend(chunk)

if len(buffer) > size:
buffer, push_back = buffer[:size], buffer[size:]
self._push_back(bytes(push_back))
return bytes(buffer)

def read_until(
self,
marker: bytes,
max_size: int,
exc_text: str,
allow_empty_eof: bool = False,
) -> bytes:
"""
Read and return bytes from the stream, delimited by marker.

* The marker is not included in the return bytes.
* The marker is consumed from the I/O stream.
* Raises `EOFError` if the stream closes before any bytes are read and
`allow_empty_eof` is set.
* Raises `ProtocolError` if the stream closes before the marker otherwise.
* Raises `ProtocolError` if marker did not occur within 'max_size + len(marker)' bytes.
"""
buffer = bytearray()
while len(buffer) <= max_size:
chunk = self._read_some()
if not chunk:
# EOF is only clean if no part of the line has arrived.
if allow_empty_eof and not buffer:
raise EOFError
# stream closed before marker found.
raise ProtocolError(f"Stream closed early {exc_text}.")
start_search = max(len(buffer) - len(marker), 0)
buffer.extend(chunk)
index = buffer.find(marker, start_search)

if index > max_size:
# marker was found, though 'max_size' exceeded.
raise ProtocolError(f"Exceeded maximum size {exc_text}")
elif index >= 0:
endindex = index + len(marker)
self._push_back(bytes(buffer[endindex:]))
return bytes(buffer[:index])

raise ProtocolError(f"Exceeded maximum size {exc_text}")
2 changes: 1 addition & 1 deletion src/hip/transports/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from .._body import GZipStream, HTTPStream
from .._content import Content
from .._headers import Headers
from .._parsers import HTTPParser
from .._request import Method, Request
from .._response import Response, StatusCode
from .._streams import Stream
from .._urls import URL
from ..backends.sync import Lock, NetworkBackend
from ..parsers import HTTPParser
from .base import SendContext, Transport

__all__ = ["Connection", "open_connection"]
Expand Down
Loading