chat-backend/venv/lib/python3.12/site-packages/firecrawl/v2/utils/http_client.py
Django Template a0fce0b484 Initial commit: Django chat backend with enhanced web scraping
Features:
- Django REST API backend
- Multi-strategy web scraping system (Beautiful Soup, Playwright, Firecrawl)
- Anti-detection features (proxy rotation, user-agent rotation)
- Data export functionality (JSON, CSV, TXT)
- Business and CrawledPage models
- Enhanced crawling service with fallback mechanisms

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-21 11:01:50 +05:30

176 lines
5.9 KiB
Python

"""
HTTP client utilities for v2 API.
"""
import time
from typing import Dict, Any, Optional
from urllib.parse import urlparse, urlunparse, urljoin
import requests
from .get_version import get_version
version = get_version()
class HttpClient:
"""HTTP client with retry logic and error handling."""
def __init__(self, api_key: str, api_url: str):
self.api_key = api_key
self.api_url = api_url
def _build_url(self, endpoint: str) -> str:
base = urlparse(self.api_url)
ep = urlparse(endpoint)
# Absolute or protocol-relative (has netloc)
if ep.netloc:
# Different host: keep path/query but force base host/scheme (no token leakage)
path = ep.path or "/"
if (ep.hostname or "") != (base.hostname or ""):
return urlunparse((base.scheme or "https", base.netloc, path, "", ep.query, ""))
# Same host: normalize scheme to base
return urlunparse((base.scheme or "https", base.netloc, path, "", ep.query, ""))
# Relative (including leading slash or not)
base_str = self.api_url if self.api_url.endswith("/") else f"{self.api_url}/"
# Guard protocol-relative like //host/path slipping through as “relative”
if endpoint.startswith("//"):
ep2 = urlparse(f"https:{endpoint}")
path = ep2.path or "/"
return urlunparse((base.scheme or "https", base.netloc, path, "", ep2.query, ""))
return urljoin(base_str, endpoint)
def _prepare_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
"""Prepare headers for API requests."""
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.api_key}',
}
if idempotency_key:
headers['x-idempotency-key'] = idempotency_key
return headers
def post(
self,
endpoint: str,
data: Dict[str, Any],
headers: Optional[Dict[str, str]] = None,
timeout: Optional[float] = None,
retries: int = 3,
backoff_factor: float = 0.5
) -> requests.Response:
"""Make a POST request with retry logic."""
if headers is None:
headers = self._prepare_headers()
data['origin'] = f'python-sdk@{version}'
url = self._build_url(endpoint)
last_exception = None
for attempt in range(retries):
try:
response = requests.post(
url,
headers=headers,
json=data,
timeout=timeout
)
if response.status_code == 502:
if attempt < retries - 1:
time.sleep(backoff_factor * (2 ** attempt))
continue
return response
except requests.RequestException as e:
last_exception = e
if attempt == retries - 1:
raise e
time.sleep(backoff_factor * (2 ** attempt))
# This should never be reached due to the exception handling above
raise last_exception or Exception("Unexpected error in POST request")
def get(
self,
endpoint: str,
headers: Optional[Dict[str, str]] = None,
timeout: Optional[float] = None,
retries: int = 3,
backoff_factor: float = 0.5
) -> requests.Response:
"""Make a GET request with retry logic."""
if headers is None:
headers = self._prepare_headers()
url = self._build_url(endpoint)
last_exception = None
for attempt in range(retries):
try:
response = requests.get(
url,
headers=headers,
timeout=timeout
)
if response.status_code == 502:
if attempt < retries - 1:
time.sleep(backoff_factor * (2 ** attempt))
continue
return response
except requests.RequestException as e:
last_exception = e
if attempt == retries - 1:
raise e
time.sleep(backoff_factor * (2 ** attempt))
# This should never be reached due to the exception handling above
raise last_exception or Exception("Unexpected error in GET request")
def delete(
self,
endpoint: str,
headers: Optional[Dict[str, str]] = None,
timeout: Optional[float] = None,
retries: int = 3,
backoff_factor: float = 0.5
) -> requests.Response:
"""Make a DELETE request with retry logic."""
if headers is None:
headers = self._prepare_headers()
url = self._build_url(endpoint)
last_exception = None
for attempt in range(retries):
try:
response = requests.delete(
url,
headers=headers,
timeout=timeout
)
if response.status_code == 502:
if attempt < retries - 1:
time.sleep(backoff_factor * (2 ** attempt))
continue
return response
except requests.RequestException as e:
last_exception = e
if attempt == retries - 1:
raise e
time.sleep(backoff_factor * (2 ** attempt))
# This should never be reached due to the exception handling above
raise last_exception or Exception("Unexpected error in DELETE request")