This commit is contained in:
+17
-100
@@ -4,12 +4,6 @@ import os
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
import logging
|
|
||||||
import gc
|
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@@ -18,68 +12,9 @@ API_KEY = os.getenv('API_KEY')
|
|||||||
if not API_KEY:
|
if not API_KEY:
|
||||||
raise ValueError("API_KEY environment variable must be set")
|
raise ValueError("API_KEY environment variable must be set")
|
||||||
|
|
||||||
# Browser pool
|
|
||||||
browser = None
|
|
||||||
|
|
||||||
# Browser configuration
|
|
||||||
BROWSER_ARGS = [
|
|
||||||
'--no-sandbox',
|
|
||||||
'--disable-setuid-sandbox',
|
|
||||||
'--disable-dev-shm-usage', # Overcome limited resource problems
|
|
||||||
'--disable-gpu', # Disable GPU hardware acceleration
|
|
||||||
'--disable-extensions', # Disable extensions
|
|
||||||
'--disable-audio-output', # Disable audio
|
|
||||||
'--disable-background-networking',
|
|
||||||
'--disable-background-timer-throttling',
|
|
||||||
'--disable-backgrounding-occluded-windows',
|
|
||||||
'--disable-breakpad',
|
|
||||||
'--disable-component-extensions-with-background-pages',
|
|
||||||
'--disable-features=TranslateUI,BlinkGenPropertyTrees',
|
|
||||||
'--disable-ipc-flooding-protection',
|
|
||||||
'--disable-renderer-backgrounding',
|
|
||||||
'--enable-features=NetworkService,NetworkServiceInProcess',
|
|
||||||
'--mute-audio',
|
|
||||||
'--memory-pressure-off',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Browser launch options
|
|
||||||
BROWSER_OPTIONS = {
|
|
||||||
'headless': True,
|
|
||||||
'executablePath': '/usr/bin/google-chrome',
|
|
||||||
'args': BROWSER_ARGS,
|
|
||||||
'handleSIGINT': False,
|
|
||||||
'handleSIGTERM': False,
|
|
||||||
'handleSIGHUP': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
async def get_browser():
|
|
||||||
"""Get or create a browser instance"""
|
|
||||||
global browser
|
|
||||||
if browser is None or not browser.isConnected():
|
|
||||||
logger.info("Launching new browser instance")
|
|
||||||
browser = await launch(**BROWSER_OPTIONS)
|
|
||||||
return browser
|
|
||||||
|
|
||||||
async def wait_for_network_idle(page):
|
async def wait_for_network_idle(page):
|
||||||
"""Wait until no network requests are in flight"""
|
"""Wait until no network requests are in flight"""
|
||||||
try:
|
await page.waitForNetworkIdle(idleTime=500, timeout=30000)
|
||||||
await page.waitForNetworkIdle(idleTime=500, timeout=30000)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Network idle timeout: {str(e)}")
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
|
||||||
async def startup_event():
|
|
||||||
"""Initialize browser on startup"""
|
|
||||||
await get_browser()
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
|
||||||
async def shutdown_event():
|
|
||||||
"""Close browser on shutdown"""
|
|
||||||
global browser
|
|
||||||
if browser:
|
|
||||||
logger.info("Closing browser on shutdown")
|
|
||||||
await browser.close()
|
|
||||||
browser = None
|
|
||||||
|
|
||||||
@app.head("/")
|
@app.head("/")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
@@ -91,58 +26,40 @@ async def visit_url(url: str, x_api_key: Optional[str] = Header(None)):
|
|||||||
if not x_api_key or x_api_key != API_KEY:
|
if not x_api_key or x_api_key != API_KEY:
|
||||||
raise HTTPException(status_code=401, detail="Invalid API key")
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||||
|
|
||||||
page = None
|
browser = None
|
||||||
try:
|
try:
|
||||||
# Decode URL if it's encoded
|
# Decode URL if it's encoded
|
||||||
decoded_url = unquote(url)
|
decoded_url = unquote(url)
|
||||||
logger.info(f"Visiting URL: {decoded_url}")
|
print(decoded_url)
|
||||||
|
|
||||||
# Get browser instance
|
# Launch browser using installed Chrome
|
||||||
browser_instance = await get_browser()
|
browser = await launch(
|
||||||
|
headless=True,
|
||||||
|
executablePath='/usr/bin/google-chrome',
|
||||||
|
args=['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
)
|
||||||
|
|
||||||
# Create new page with timeout
|
# Create new page
|
||||||
page = await browser_instance.newPage()
|
page = await browser.newPage()
|
||||||
|
|
||||||
# Set page timeout and resource limits
|
|
||||||
await page.setDefaultNavigationTimeout(60000) # 60 seconds timeout
|
|
||||||
await page.setRequestInterception(True)
|
|
||||||
|
|
||||||
# Block unnecessary resources
|
|
||||||
async def intercept_request(request):
|
|
||||||
if request.resourceType in ['image', 'media', 'font']:
|
|
||||||
await request.abort()
|
|
||||||
else:
|
|
||||||
await request.continue_()
|
|
||||||
|
|
||||||
page.on('request', intercept_request)
|
|
||||||
|
|
||||||
# Navigate to URL and wait for network idle
|
# Navigate to URL and wait for network idle
|
||||||
await page.goto(decoded_url, waitUntil='networkidle2', timeout=60000)
|
await page.goto(decoded_url, waitUntil='networkidle2')
|
||||||
|
|
||||||
# Get page content
|
# Get page content
|
||||||
content = await page.content()
|
content = await page.content()
|
||||||
|
|
||||||
# Close page to free up resources
|
# Close page
|
||||||
await page.close()
|
await page.close()
|
||||||
|
|
||||||
# Force garbage collection
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
return {"status": "success", "content": content}
|
return {"status": "success", "content": content}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing request: {str(e)}")
|
|
||||||
# Ensure browser is still running
|
|
||||||
if browser is None or not browser.isConnected():
|
|
||||||
await get_browser()
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Always ensure page is closed
|
# Ensure browser is closed even if an error occurs
|
||||||
if page:
|
if browser:
|
||||||
try:
|
await browser.close()
|
||||||
await page.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
Reference in New Issue
Block a user