Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BaseTestServer.start_server() changes host value to an IP address #7166

Open
1 task done
ppena-LiveData opened this issue Jan 12, 2023 · 2 comments
Open
1 task done
Labels

Comments

@ppena-LiveData
Copy link

ppena-LiveData commented Jan 12, 2023

Describe the bug

For some reason, BaseTestServer.start_server() changes self.host to an IP address, which is a problem if someone is expecting a host name, e.g. localhost. I am using aioresponses in addition to pytest-aiohttp, the latter of which creates a aiohttp.test_utils.TestServer. The problem is that if I pass http://localhost:{port} to aioresponses(passthrough=[...]) and then try to do a relative client.get('/'), it uses 127.0.0.1 instead of localhost, so aioresponses does not try to pass that through to aiohttp to handle it.

I see that @webknjaz made the commit that added code to start_server() to change self.host to an IP address, but the commit message doesn't say why that is being done.

To Reproduce

This pytest reproduction of the problem is just a slight variation of the example code from https://pypi.org/project/pytest-aiohttp/:

from aiohttp import web
from aioresponses import aioresponses


async def hello(request):
    return web.Response(body=b'Hello, world')


def create_app():
    app = web.Application()
    app.router.add_route('GET', '/', hello)
    return app


async def test_hello(aiohttp_client):
    host, port = 'localhost', 54321
    with aioresponses(passthrough=[f'http://{host}:{port}']):
        opts = {'host': host, 'port': port}
        client = await aiohttp_client(create_app(), server_kwargs=opts)
        resp = await client.get('/')
        assert resp.status == 200
        text = await resp.text()
        assert 'Hello, world' in text

Expected behavior

Relative paths should work when using an aiohttp_client with a host value of localhost, even when using aioresponses.

Logs/tracebacks

The pytest reproduction above causes `aioresponses/core.py` to throw this exception:

aiohttp.client_exceptions.ClientConnectionError: Connection refused: GET http://127.0.0.1:54321/

Python Version

$ python --version
Python 3.9.13

aiohttp Version

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.8.3
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
...

multidict Version

$ python -m pip show multidict
Name: multidict
Version: 6.0.4
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
...

yarl Version

$ python -m pip show yarl
Name: yarl
Version: 1.8.2
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl/

OS

Windows 11 but also in Linux:

$ uname -a
Linux hippy 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Related component

Server, Client

Additional context

No response

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct
@ppena-LiveData
Copy link
Author

NOTE: to workaround the issue, you can replace the _root URL of the TestServer, like this:

client._server._root = URL(f'http://{host}:{port}')

@a-shahov
Copy link

a-shahov commented Jan 23, 2024

`
class BaseTestServer(ABC):
__test__ = False

def __init__(
    self,
    *,
    scheme: Union[str, object] = sentinel,
    loop: Optional[asyncio.AbstractEventLoop] = None,
    host: str = "127.0.0.1",
    port: Optional[int] = None,
    skip_url_asserts: bool = False,
    socket_factory: Callable[
        [str, int, socket.AddressFamily], socket.socket
    ] = get_port_socket,
    **kwargs: Any,
) -> None:
    self._loop = loop
    self.runner: Optional[BaseRunner] = None
    self._root: Optional[URL] = None
    self.host = host
    self.port = port
    self._closed = False
    self.scheme = scheme
    self.skip_url_asserts = skip_url_asserts
    self.socket_factory = socket_factory

async def start_server(
    self, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any
) -> None:
    if self.runner:
        return
    self._loop = loop
    self._ssl = kwargs.pop("ssl", None)
    self.runner = await self._make_runner(handler_cancellation=True, **kwargs)
    await self.runner.setup()
    if not self.port:
        self.port = 0
    try:
        version = ipaddress.ip_address(self.host).version
    except ValueError:
        version = 4
    family = socket.AF_INET6 if version == 6 else socket.AF_INET
    _sock = self.socket_factory(self.host, self.port, family)
    self.host, self.port = _sock.getsockname()[:2]
    site = SockSite(self.runner, sock=_sock, ssl_context=self._ssl)
    await site.start()
    server = site._server
    assert server is not None
    sockets = server.sockets  # type: ignore[attr-defined]
    assert sockets is not None
    self.port = sockets[0].getsockname()[1]
    if self.scheme is sentinel:
        if self._ssl:
            scheme = "https"
        else:
            scheme = "http"
        self.scheme = scheme
    self._root = URL(f"{self.scheme}://{self.host}:{self.port}")`

This happened because kwargs into BaseTestServer just remain forgotten into __init__ method and not transmitted into start_server when aiohttp_client making TestServer instance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants