Skip to content

Commit 94611d4

Browse files
744 dns resolution normalization fix (#745)
* Fix #744: Preserve domain names while preventing localhost duplicates The normalize_url method was resolving ALL domain names to IP addresses, which breaks for services behind CDNs, load balancers, or reverse proxies. This fix: - Preserves domain names for external services (CDN/load balancer support) - Only normalizes 127.0.0.1 to localhost to prevent local duplicates - Maintains fix for issue #649 for localhost/127.0.0.1 duplicates - Keeps compatibility with PR #712's duplicate prevention for local services Changes: - Removed socket.gethostbyname() for external domains - Added special case to convert 127.0.0.1 to localhost - Updated tests to verify both behaviors - Added regression test for duplicate prevention This balances the needs of both issues: - #649: Prevent localhost/127.0.0.1 duplicates (still fixed) - #744: Support services behind CDNs/load balancers (now fixed) Signed-off-by: Mihai Criveti <[email protected]> * Fix localhost resolution Signed-off-by: Mihai Criveti <[email protected]> * fix pylint Signed-off-by: RAKHI DUTTA <[email protected]> --------- Signed-off-by: Mihai Criveti <[email protected]> Signed-off-by: RAKHI DUTTA <[email protected]> Co-authored-by: RAKHI DUTTA <[email protected]>
1 parent da2e25b commit 94611d4

File tree

2 files changed

+64
-13
lines changed

2 files changed

+64
-13
lines changed

mcpgateway/services/gateway_service.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
from datetime import datetime, timezone
4343
import logging
4444
import os
45-
import socket
4645
import tempfile
4746
import time
4847
from typing import Any, AsyncGenerator, Dict, List, Optional, Set, TYPE_CHECKING
@@ -257,29 +256,40 @@ def __init__(self) -> None:
257256
@staticmethod
258257
def normalize_url(url: str) -> str:
259258
"""
260-
Normalize a URL by resolving the hostname to its IP address.
259+
Normalize a URL by ensuring it's properly formatted.
260+
261+
Special handling for localhost to prevent duplicates:
262+
- Converts 127.0.0.1 to localhost for consistency
263+
- Preserves all other domain names as-is for CDN/load balancer support
261264
262265
Args:
263266
url (str): The URL to normalize.
264267
265268
Returns:
266-
str: The normalized URL with the hostname replaced by its IP address.
269+
str: The normalized URL.
267270
268271
Examples:
269272
>>> GatewayService.normalize_url('http://localhost:8080/path')
270-
'http://127.0.0.1:8080/path'
273+
'http://localhost:8080/path'
274+
>>> GatewayService.normalize_url('http://127.0.0.1:8080/path')
275+
'http://localhost:8080/path'
276+
>>> GatewayService.normalize_url('https://example.com/api')
277+
'https://example.com/api'
271278
"""
272279
parsed = urlparse(url)
273280
hostname = parsed.hostname
274-
try:
275-
ip = socket.gethostbyname(hostname)
276-
except Exception:
277-
ip = hostname
278-
netloc = ip
279-
if parsed.port:
280-
netloc += f":{parsed.port}"
281-
normalized = parsed._replace(netloc=netloc)
282-
return urlunparse(normalized)
281+
282+
# Special case: normalize 127.0.0.1 to localhost to prevent duplicates
283+
# but preserve all other domains as-is for CDN/load balancer support
284+
if hostname == "127.0.0.1":
285+
netloc = "localhost"
286+
if parsed.port:
287+
netloc += f":{parsed.port}"
288+
normalized = parsed._replace(netloc=netloc)
289+
return urlunparse(normalized)
290+
291+
# For all other URLs, preserve the domain name
292+
return url
283293

284294
async def _validate_gateway_url(self, url: str, headers: dict, transport_type: str, timeout: Optional[int] = None):
285295
"""

tests/unit/mcpgateway/services/test_gateway_service.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,47 @@ async def test_update_gateway_integrity_error(self, gateway_service, mock_gatewa
11341134
with pytest.raises(SQLIntegrityError):
11351135
await gateway_service.update_gateway(test_db, 1, gateway_update)
11361136

1137+
def test_normalize_url_preserves_domain(self):
1138+
"""Test that normalize_url preserves domain names but normalizes localhost."""
1139+
# Test with various domain formats
1140+
test_cases = [
1141+
# Regular domains should be preserved as-is
1142+
("http://example.com", "http://example.com"),
1143+
("https://api.example.com:8080/path", "https://api.example.com:8080/path"),
1144+
("https://my-app.cloud-provider.region.example.com/sse",
1145+
"https://my-app.cloud-provider.region.example.com/sse"),
1146+
("https://cdn.service.com/api/v1", "https://cdn.service.com/api/v1"),
1147+
1148+
# localhost should remain localhost
1149+
("http://localhost:8000", "http://localhost:8000"),
1150+
("https://localhost/api", "https://localhost/api"),
1151+
1152+
# 127.0.0.1 should be normalized to localhost to prevent duplicates
1153+
("http://127.0.0.1:8080/path", "http://localhost:8080/path"),
1154+
("https://127.0.0.1/sse", "https://localhost/sse"),
1155+
]
1156+
1157+
for input_url, expected in test_cases:
1158+
result = GatewayService.normalize_url(input_url)
1159+
assert result == expected, f"normalize_url({input_url}) should return {expected}, got {result}"
1160+
1161+
def test_normalize_url_prevents_localhost_duplicates(self):
1162+
"""Test that normalization prevents localhost/127.0.0.1 duplicates."""
1163+
# These URLs should all normalize to the same value
1164+
equivalent_urls = [
1165+
"http://127.0.0.1:8080/sse",
1166+
"http://localhost:8080/sse",
1167+
]
1168+
1169+
normalized = [GatewayService.normalize_url(url) for url in equivalent_urls]
1170+
1171+
# All should normalize to localhost version
1172+
assert all(n == "http://localhost:8080/sse" for n in normalized), \
1173+
f"All localhost variants should normalize to same URL, got: {normalized}"
1174+
1175+
# They should all be the same (no duplicates possible)
1176+
assert len(set(normalized)) == 1, "All localhost variants should produce identical normalized URLs"
1177+
11371178
@pytest.mark.asyncio
11381179
async def test_update_gateway_with_transport_change(self, gateway_service, mock_gateway, test_db):
11391180
"""Test updating gateway transport type."""

0 commit comments

Comments
 (0)