Handling Connection Errors and Timeouts Across Different Website Modules in Holehe
Holehe handles connection errors and timeouts by configuring a global timeout in the core CLI, applying per-request timeouts in individual modules, and wrapping all HTTP calls in try/except httpx.HTTPError blocks to ensure graceful degradation without crashing the entire scan.
Holehe is an open-source OSINT tool that checks whether an email address is registered across hundreds of websites. Because it interacts with diverse third-party services, handling connection errors and timeouts across different website modules is critical to maintaining reliable operation. This article examines how the megadose/holehe repository implements consistent error handling from the CLI layer down to individual site modules.
Global Timeout Configuration in Core.py
The foundation of Holehe's timeout strategy resides in holehe/core.py, where command-line arguments are parsed and the shared HTTP client is instantiated.
The CLI exposes a --timeout flag with a default value of 10 seconds:
# Lines 194-213 in holehe/core.py
parser.add_argument("--timeout", type=int, default=10, help="Timeout for each request")
args = parser.parse_args()
# The timeout value is passed to httpx.AsyncClient
client = httpx.AsyncClient(timeout=args.timeout)
This httpx.AsyncClient is created once and injected into all module functions. The global timeout establishes a ceiling for all network operations, preventing the entire scan from hanging indefinitely when services become unresponsive.
Per-Module Request Patterns and Exception Handling
Each website module in holehe/modules/ receives the shared client and implements its own request logic with per-call timeout overrides and granular error handling. This two-level timeout system allows modules to specify shorter timeouts for known-fast services while respecting the global maximum.
Standard Error Handling Pattern
The consistent pattern across modules follows this structure:
- Attempt the HTTP request with an explicit timeout parameter
- Catch
httpx.HTTPError(the base class for all httpx exceptions) - Return a normalized result dictionary with
rateLimit: Falseandexists: False
This pattern appears in modules such as social_media/twitter.py, forum/onlinesequencer.py, and medical/caringbridge.py.
Example: Simple GET with Timeout and Error Handling
Below is a representative implementation based on the Twitter module pattern:
# Pattern found in holehe/modules/social_media/twitter.py
import httpx
async def twitter(email: str, client: httpx.AsyncClient, out: list):
try:
r = await client.get(
"https://twitter.com/users/email_available.json",
params={"email": email},
timeout=5, # Per-request override of global timeout
)
data = r.json()
out.append(
{
"name": "twitter",
"rateLimit": False,
"exists": data.get("taken", False),
"emailrecovery": None,
"phoneNumber": None,
"others": None,
}
)
except httpx.HTTPError:
# Catches ConnectTimeout, ReadTimeout, ConnectError, etc.
out.append(
{
"name": "twitter",
"rateLimit": False,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
}
)
The 5-second per-request timeout allows Twitter checks to fail fast if the service stalls, while the except httpx.HTTPError block ensures any transport-level failure produces a valid result rather than an unhandled exception.
Example: POST with Rate-Limit Detection
Some modules extend the pattern to detect rate limiting by inspecting HTTP status codes before falling back to generic error handling:
# Pattern found in holehe/modules/medical/caringbridge.py
async def caringbridge(email: str, client: httpx.AsyncClient, out: list):
try:
r = await client.post(
"https://www.caringbridge.org/signin",
data={"email": email},
timeout=3,
)
if r.status_code == 429:
# Explicit rate-limit detection
out.append(
{
"name": "caringbridge",
"rateLimit": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
}
)
return
# Normal response processing...
except httpx.HTTPError:
# Generic network failure handling
out.append(
{
"name": "caringbridge",
"rateLimit": False,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
}
)
This demonstrates how modules can distinguish rate limiting from connection failures while maintaining the same exception-handling foundation.
Exception Hierarchy and Specific Error Types
Holehe leverages httpx's structured exception hierarchy for precise error classification:
| Exception | Typical Cause | Handling in Modules |
|---|---|---|
httpx.ConnectTimeout |
TCP connection cannot be established within timeout | Caught by httpx.HTTPError base class |
httpx.ReadTimeout |
Server connected but response not received in time | Caught by httpx.HTTPError base class |
httpx.ConnectError |
DNS failure, refused connection, or network unreachable | Caught by httpx.HTTPError base class |
httpx.HTTPStatusError |
4xx/5xx response (when using raise_for_status()) |
Checked via r.status_code before raising |
By catching httpx.HTTPError rather than individual subclasses, modules achieve comprehensive coverage of transport failures without verbose exception lists. When more granular handling is needed (such as the 429 rate-limit check), modules inspect response attributes before the exception propagates.
Result Normalization and Caller Contract
A critical aspect of Holehe's error handling is the guaranteed return format. Every module function populates a result list with dictionaries containing these standardized keys:
name: The service identifierrateLimit: Boolean indicating throttling detectionexists: Boolean indicating account existenceemailrecovery: Recovery email if exposedphoneNumber: Phone number if exposedothers: Additional data
This contract ensures that the aggregation logic in holehe/__init__.py can process results uniformly, regardless of whether the underlying request succeeded, timed out, or encountered a connection error. The graceful degradation approach means a single failing service never corrupts the entire scan output.
Timeout Strategy Benefits
The layered timeout implementation in Holehe provides several operational advantages:
Predictable execution time: The global timeout bounds total scan duration, while per-request timeouts prevent individual slow services from dominating execution.
Resilience to network variability: By catching all httpx.HTTPError subclasses, modules handle transient failures (packet loss, brief outages) without manual retry logic.
Clear operational signals: The rateLimit flag differentiates temporary service restrictions from definitive "account not found" responses, enabling intelligent retry strategies at the application level.
Summary
- Global timeout is configured via CLI in
holehe/core.pyand applied throughhttpx.AsyncClient(timeout=...) - Per-module timeouts override the global value for services with known latency characteristics
- Exception handling uses
try/except httpx.HTTPErrorto catch all transport failures includingConnectTimeout,ReadTimeout, andConnectError - Normalized results ensure every module returns a consistent dictionary structure, preventing cascading failures
- Rate-limit detection extends the base pattern by inspecting HTTP status codes before exception handling
Frequently Asked Questions
What timeout values does Holehe use by default?
Holehe defaults to a 10-second global timeout for all HTTP requests. Individual modules often specify shorter per-request timeouts—commonly 3 to 5 seconds—for services expected to respond quickly. Users can override the global default using the --timeout CLI argument defined in holehe/core.py.
Why does Holehe catch httpx.HTTPError instead of specific exception types?
Catching the base httpx.HTTPError class provides comprehensive protection against all transport-level failures without requiring modules to enumerate every possible exception. This includes ConnectTimeout, ReadTimeout, ConnectError, and other network problems. Modules only need more specific handling when detecting application-level signals like HTTP 429 rate-limit responses.
How does Holehe prevent one slow website from stalling the entire scan?
Holehe uses asynchronous execution with individual timeouts combined with graceful exception handling. Each module runs independently with its own timeout constraints. When a request fails or times out, the module returns a normalized "unknown" result rather than propagating an exception, allowing the aggregation layer to continue processing other services uninterrupted.
Can modules distinguish between rate limiting and server unavailability?
Yes. While the base pattern catches all httpx.HTTPError exceptions as generic failures, modules can inspect HTTP response attributes before an exception occurs. By checking r.status_code == 429 or similar indicators, modules set rateLimit: True in the result dictionary to signal throttling conditions separately from connection timeouts or transport errors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →