`BaseTestServer.start_server()` changes `host` value to an IP address
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
- [X] I agree to follow the aio-libs Code of Conduct
NOTE: to workaround the issue, you can replace the _root URL of the TestServer, like this:
client._server._root = URL(f'http://{host}:{port}')
` 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