File size: 3,391 Bytes
e555dc3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
Shared HTTP client with connection pooling.

Provides a singleton aiohttp ClientSession for efficient connection reuse
across the application.
"""

import aiohttp
import logging
from contextlib import asynccontextmanager

logger = logging.getLogger(__name__)

# Singleton session instance
_session: aiohttp.ClientSession | None = None

# Default timeout configuration
DEFAULT_TIMEOUT = aiohttp.ClientTimeout(total=60, connect=10)

# Default headers
DEFAULT_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
}


async def get_session() -> aiohttp.ClientSession:
    """
    Get or create a shared aiohttp session for connection pooling.

    The session is created lazily on first call and reused for all subsequent requests.
    This provides significant performance improvements by reusing TCP connections.

    Returns:
        A configured aiohttp.ClientSession instance
    """
    global _session
    if _session is None or _session.closed:
        connector = aiohttp.TCPConnector(
            limit=100,  # Max concurrent connections
            limit_per_host=10,  # Max connections per host
            enable_cleanup_closed=True,
        )
        _session = aiohttp.ClientSession(
            connector=connector,
            timeout=DEFAULT_TIMEOUT,
            headers=DEFAULT_HEADERS,
        )
        logger.debug("Created new HTTP session with connection pooling")
    return _session


async def close_session() -> None:
    """
    Close the shared session.

    Should be called during application shutdown to cleanly close all connections.
    """
    global _session
    if _session and not _session.closed:
        await _session.close()
        logger.debug("Closed HTTP session")
        _session = None


@asynccontextmanager
async def http_session():
    """
    Async context manager for the HTTP session.

    Usage:
        async with http_session() as session:
            async with session.get(url) as response:
                data = await response.text()
    """
    session = await get_session()
    try:
        yield session
    finally:
        # Don't close here - session is reused
        pass


async def fetch_url(url: str, **kwargs) -> str:
    """
    Convenience function to fetch content from a URL.

    Args:
        url: The URL to fetch
        **kwargs: Additional arguments to pass to session.get()

    Returns:
        The response text content

    Raises:
        aiohttp.ClientError: If the request fails
    """
    session = await get_session()
    async with session.get(url, **kwargs) as response:
        response.raise_for_status()
        return await response.text()


async def fetch_json(url: str, **kwargs) -> dict:
    """
    Convenience function to fetch JSON from a URL.

    Args:
        url: The URL to fetch
        **kwargs: Additional arguments to pass to session.get()

    Returns:
        The parsed JSON response

    Raises:
        aiohttp.ClientError: If the request fails
    """
    session = await get_session()
    async with session.get(url, **kwargs) as response:
        response.raise_for_status()
        return await response.json()