Skip to content

Conversation

@whummer
Copy link
Member

@whummer whummer commented Dec 16, 2025

Minor fixes and polishing for TypeDB extension. Follow-up from #108

  • add missing self parameter to lambda expression for received_from_http2_client call (was causing errors when running and testing the extension locally)
  • minor fixes in ForwardingBuffer.received_from_http2_client(..) to fix connection handling for non-TypeDB HTTP2 requests - was previously getting connection timeouts, this can be tested by interacting with the emulator from the Web app resource browser using Chrome browser (which sends HTTP2 requests by default, at least for me under macOS)
  • add make lint target, and add to CI build config
  • format the codebase
  • add change log to README
  • replace non-stdlib ArgumentError with ValueError
  • add some missing assertions to integration tests
  • bump version to 0.1.1
  • update CODEOWNERS file and add @purcell for this extension 🙌

@whummer whummer requested a review from purcell December 16, 2025 17:11
@whummer whummer marked this pull request as ready for review December 16, 2025 17:11

def received_from_http2_client(self, data, default_handler):
def received_from_http2_client(self, data, default_handler: Callable):
if self.proxying is False:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Had to slightly adjust the logic in here, to match the logic of what we had prior to the last refactoring round. We need to ensure that non-TypeDB requests sent via HTTP2 are not proxied and forwarded to the target properly.

The challenge is that this function may be called multiple times (for multiple HTTP2 frames) until the headers are fully received, hence we distinguish between the value of proxying being None (connection not fully initialized yet, still waiting for headers), and being False (connection initialized, headers indicate that the request is not in scope for the proxy).

This behavior can be tested by using the resource browser in the LocalStack Web application in Chrome browser, which sends connections via HTTP2 by default. (This logic is now also covered by a new integration test as part of this PR.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that was the challenge — it was hard to follow the rationale originally. Thanks for fixing it here and adding the failing test! When extracting the logic it seemed there was ternary logic involved, and I don't like bool | None for that, so I started to use an Enum initially with match, to make this state machine more explicit. Then at some point I wrongly concluded that there were only two possible states, and reverted to a bool.

Copy link
Contributor

@purcell purcell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these! 🚀


def received_from_http2_client(self, data, default_handler):
def received_from_http2_client(self, data, default_handler: Callable):
if self.proxying is False:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that was the challenge — it was hard to follow the rationale originally. Thanks for fixing it here and adding the failing test! When extracting the logic it seemed there was ternary logic involved, and I don't like bool | None for that, so I started to use an Enum initially with match, to make this state machine more explicit. Then at some point I wrongly concluded that there were only two possible states, and reverted to a bool.

pip install localstack
make install
make lint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 Thanks! I missed linting/formatting manually, doh!

def _dataReceived(fn, self, data, *args, **kwargs):
self._ls_forwarding_buffer.received_from_http2_client(data, lambda d: fn(d, *args, **kwargs))
self._ls_forwarding_buffer.received_from_http2_client(
data, lambda d: fn(self, d, *args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh!

"""Parse the data from an HTTP2 stream into an iterable of frames"""
buffer = FrameBuffer(server=True)
buffer.max_frame_size = 16384
buffer.add_data(data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about this one — did it fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, great catch - it does actually fail for certain HTTP bytes that are not representing a proper HTTP/2 preamble:

...
  File "/opt/code/extensions/typedb/localstack_typedb/utils/h2_proxy.py", line 128, in get_headers_from_data_stream
    return get_headers_from_frames(get_frames_from_http2_stream(stream))
  File "/opt/code/extensions/typedb/localstack_typedb/utils/h2_proxy.py", line 135, in get_headers_from_frames
    for frame in frames:
  File "/opt/code/extensions/typedb/localstack_typedb/utils/h2_proxy.py", line 149, in get_frames_from_http2_stream
    buffer.add_data(data)
  File "/opt/code/localstack/.venv/lib/python3.13/site-packages/h2/frame_buffer.py", line 51, in add_data
    raise ProtocolError(msg)
h2.exceptions.ProtocolError: Invalid HTTP/2 preamble.

Don't have a reproducible test case for this one (will try to pull something together!), but seen this in the logs a couple of times, hence adding the additional guard here..

Comment on lines +82 to +96
# make an HTTP/2 request to the LocalStack health endpoint
with httpx.Client(http2=True, verify=False, trust_env=False) as client:
health_url = f"{url}/_localstack/health"
response = client.get(health_url)

assert response.status_code == 200
assert response.http_version == "HTTP/2"
assert '"services":' in response.text

# make an HTTP/2 request to a LocalStack endpoint outside the extension (S3 list buckets)
headers = {
"Authorization": "AWS4-HMAC-SHA256 Credential=000000000000/20250101/us-east-1/s3/aws4_request, ..."
}
with httpx.Client(http2=True, verify=False, trust_env=False) as client:
response = client.get(url, headers=headers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! If I had poked around a bit more with LS alongside the extension, the need for this would quickly have become more obvious.

@whummer whummer merged commit a21e460 into main Dec 17, 2025
1 check passed
@whummer whummer deleted the typedb-fixes branch December 17, 2025 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants