Skip to content

Commit

Permalink
feat: expose user agent
Browse files Browse the repository at this point in the history
  • Loading branch information
hartym committed Mar 22, 2024
1 parent ddad8d3 commit 38c7d01
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 6 deletions.
18 changes: 18 additions & 0 deletions harp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@
import os
from subprocess import check_output

from packaging.version import InvalidVersion, Version

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def _parse_version(version: str, /, *, default=None) -> Version:
try:
return Version(version)
except InvalidVersion:
if "-" in version:
return _parse_version(version.rsplit("-", 1)[0], default=default)
return default


# last release
__title__ = "Core"
__version__ = "0.4.0"
Expand All @@ -42,13 +54,16 @@
with open(os.path.join(ROOT_DIR, "version.txt")) as f:
__version__ = f.read().strip()

__parsed_version__ = _parse_version(__version__)

# override with current development version/revision if available (disabled in CI, for docs)
if not os.environ.get("CI", False) and os.path.exists(os.path.join(ROOT_DIR, ".git")):
__revision__ = check_output(["git", "rev-parse", "HEAD"], cwd=ROOT_DIR).decode("utf-8").strip()
try:
__version__ = (
check_output(["git", "describe", "--tags", "--always", "--dirty"], cwd=ROOT_DIR).decode("utf-8").strip()
)
__parsed_version__ = _parse_version(__version__, default=__parsed_version__)
except Exception:
__version__ = __revision__[:7]

Expand All @@ -75,11 +90,14 @@ def run(config: Config):
asyncio.run(server.serve())


print(__parsed_version__)

__all__ = [
"Config",
"ROOT_DIR",
"__revision__",
"__version__",
"__parsed_version__",
"get_logger",
"run",
]
13 changes: 12 additions & 1 deletion harp_apps/proxy/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from httpx import AsyncClient, codes
from whistle import IAsyncEventDispatcher

from harp import get_logger
from harp import __parsed_version__, get_logger
from harp.asgi.events import MessageEvent, TransactionEvent
from harp.http import HttpRequest, HttpResponse
from harp.http.requests import WrappedHttpRequest
Expand All @@ -24,6 +24,9 @@ class HttpProxyController:
"""Controller name, also refered as endpoint name (for example in
:class:`Transaction <harp.models.Transaction>`)."""

user_agent: str = None
"""User agent to use when proxying requests (will default to harp/<version>)."""

_dispatcher: Optional[IAsyncEventDispatcher] = None
"""Event dispatcher for this controller."""

Expand All @@ -43,6 +46,10 @@ def __init__(self, url, *, http_client: AsyncClient, dispatcher=None, name=None)

self.parsed_url = urlparse(self.url)

# we only expose minimal information about the exact version
if not self.user_agent:
self.user_agent = f"harp/{__parsed_version__.major}.{__parsed_version__.minor}"

async def adispatch(self, event_id, event=None):
"""
Shortcut method to dispatch an event using the controller's dispatcher, if there is one.
Expand Down Expand Up @@ -74,6 +81,10 @@ async def __call__(self, request: HttpRequest):
if header.startswith("x-harp-"):
tags[header[7:]] = request.headers.pop(header)

# override user agent (later, may be customizable behavior)
if self.user_agent:
request.headers["user-agent"] = self.user_agent

transaction = await self._create_transaction_from_request(request, tags=tags)
await request.join()
url = urljoin(self.url, request.path) + (f"?{urlencode(request.query)}" if request.query else "")
Expand Down
16 changes: 11 additions & 5 deletions harp_apps/proxy/tests/test_controllers_http_proxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import ANY, AsyncMock
from unittest.mock import ANY, AsyncMock, patch

import pytest
import respx
Expand All @@ -22,6 +22,12 @@ def dispatcher(self):
class HttpProxyControllerTestFixtureMixin(ControllerTestFixtureMixin):
ControllerType = HttpProxyController

@pytest.fixture(autouse=True)
def setup(self):
# forces the user agent to be a known value, even if versions are incremented
with patch("harp_apps.proxy.controllers.HttpProxyController.user_agent", "test/1.0"):
yield

def mock_http_endpoint(self, url, /, *, status=200, content=""):
"""Make sure you decorate your tests function using this with respx.mock decorator, otherwise the real network
will be called and you may have some headaches..."""
Expand Down Expand Up @@ -117,14 +123,14 @@ async def test_basic_get(self, database_url, dispatcher: AsyncEventDispatcher):
}

# request
assert (await storage.get_blob(request.headers)).data == b"host: example.com"
assert (await storage.get_blob(request.headers)).data == b"host: example.com\nuser-agent: test/1.0"
assert (await storage.get_blob(request.body)).data == b""
assert request.to_dict(omit_none=False) == {
"id": 1,
"transaction_id": ANY,
"kind": "request",
"summary": "GET / HTTP/1.1",
"headers": "5b5fa853da2315f3639fd85d183307b0db6fcfa9",
"headers": "ecd46dd3023926417e1205b219aec4c2b9e0dab0",
"body": "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
"created_at": ANY,
}
Expand Down Expand Up @@ -175,15 +181,15 @@ async def test_get_with_tags(self, database_url, dispatcher: AsyncEventDispatche
}

assert (await storage.get_blob(request.headers)).data == (
b"accept: application/json\nvary: custom\nhost: example.com"
b"accept: application/json\nvary: custom\nhost: example.com\nuser-agent: test/1.0"
)
assert (await storage.get_blob(request.body)).data == b""
assert request.to_dict(omit_none=False) == {
"id": 1,
"transaction_id": ANY,
"kind": "request",
"summary": "GET / HTTP/1.1",
"headers": "3215728a5aed81014fd90307f48fe439e785e64a",
"headers": "885ad9e4633a3c038f13c32e4ee48fc9221c0152",
"body": "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
"created_at": ANY,
}
Expand Down

0 comments on commit 38c7d01

Please sign in to comment.