1 Commits

4 changed files with 75 additions and 11 deletions

54
CLAUDE.md Normal file
View File

@@ -0,0 +1,54 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
http2pic - PHP website renderer that takes screenshots of URLs and returns them as images. Live at https://http2pic.haschek.at/
## Architecture
**Entry point:** `web/index.php` - PSR-4 router with two paths:
- `/api` - Chrome/Chromium screenshot via Selenium WebDriver (php-webdriver). Takes `url`, `viewport` (WIDTHxHEIGHT), `js` (true/false) params. Connects to `localhost:4444` ChromeDriver, sets window size, disables scrollbars, takes screenshot as PNG.
- default - renders `src/templates/index.html.php` (landing page).
**Legacy class:** `src/http2pic.class.php` - Old `wkhtmltoimage`-based renderer (deprecated, not used in production). Supports PNG/JPG, viewport, resize, URL reachability check, file caching.
**Helpers:** `src/helpers.php` - `renderTemplate()`, `addToLog()`, `getUserIP()`.
**Config:** `src/config.inc.php` - set at build time by `start.sh` from `URL` env var.
## Running
**Docker (production):**
```
docker compose up -d
```
Services: Caddy (:80), PHP-FPM, ChromeDriver (:4444). Volumes: `./cache` and `./logs`. Configured via `URL` env var.
**Dev container:** `.devcontainer/` - same stack, run `./devcontainer/start.sh`. Forward port 8080.
**Quick test:**
```
php -S localhost:8080 -t web/
# Then visit http://localhost:8080/api?url=<target>
```
## Key files
| File | Purpose |
|------|---------|
| `web/index.php` | API + template router |
| `src/http2pic.class.php` | Legacy wkhtmltoimage renderer |
| `src/helpers.php` | Template render, logging, IP helper |
| `src/config.inc.php` | Runtime config (URL) |
| `docker/Caddyfile` | Reverse proxy, PHP-FPM, file server |
| `docker/start.sh` | Boots PHP-FPM, ChromeDriver, writes config |
| `docker-compose.yml` | Production compose |
## Caveats
- `web/index.php` has a `var_dump($cmd)` debug statement left in `http2pic.class.php:181` - remove before shipping.
- Legacy `http2pic.class.php` has a variable scoping bug: line 109 uses `$url` instead of `$this->params['url']`.
- Cache dir permissions must be `777` (set by `start.sh`).
- ChromeDriver must be running on `localhost:4444` for the API to work.

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
http2pic: http2pic:
build: build:
@@ -13,6 +12,6 @@ services:
- ./logs:/srv/logs - ./logs:/srv/logs
environment: environment:
- URL=http://localhostxxx:8080 - URL=http://localhost:8080
ports: ports:
- 8080:80 - 8080:80

View File

@@ -178,11 +178,14 @@ class http2pic
$cmd.=' --wait-for-network-idle'; $cmd.=' --wait-for-network-idle';
var_dump($cmd);
$cmd = escapeshellcmd($cmd); $cmd = escapeshellcmd($cmd);
shell_exec($cmd); $output = [];
$rc = 0;
exec($cmd . ' 2>&1', $output, $rc);
$this->params['cmd'] = $cmd; $this->params['cmd'] = $cmd;
if ($rc !== 0) {
$this->params['render_error'] = implode("\n", $output);
}
$this->postRender(); $this->postRender();

View File

@@ -50,8 +50,9 @@ switch ($url[0]) {
$capabilities->setCapability('javascriptEnabled', false); $capabilities->setCapability('javascriptEnabled', false);
$driver = null;
try { try {
$driver = RemoteWebDriver::create($serverUrl, $capabilities); $driver = RemoteWebDriver::create($serverUrl, $capabilities, 30000, 60);
$driver->get($target); $driver->get($target);
//hide scroll bars //hide scroll bars
$driver->executeScript('document.body.style.overflow = "hidden";'); $driver->executeScript('document.body.style.overflow = "hidden";');
@@ -68,18 +69,25 @@ switch ($url[0]) {
} }
$viewportLabel = is_array($viewport) ? implode('x', $viewport) : (string) $viewport; $viewportLabel = is_array($viewport) ? implode('x', $viewport) : (string) $viewport;
addToLog("$ip\tRequested $target with viewport " . $viewportLabel . " and js " . ($js ? 'enabled' : 'disabled')); addToLog("$ip\tRequested $target with viewport " . $viewportLabel . " and js " . ($js ? 'enabled' : 'disabled'));
// take screenshot and send to user
header('Content-Type: image/png');
echo $driver->takeScreenshot();
} catch (Exception $e) { } catch (Exception $e) {
// ensure driver is closed to free ChromeDriver memory
if ($driver instanceof \Facebook\WebDriver\Remote\RemoteWebDriver) {
try { $driver->quit(); } catch (Exception $qe) {}
}
header('HTTP/1.0 500 Internal Server Error'); header('HTTP/1.0 500 Internal Server Error');
addToLog("$ip\tRequested $target but resulted in error:\t" . $e->getMessage()); addToLog("$ip\tRequested $target but resulted in error:\t" . $e->getMessage());
echo 'Error: ' . $e->getMessage(); echo 'Error: ' . $e->getMessage();
exit; exit;
} finally {
if ($driver instanceof \Facebook\WebDriver\Remote\RemoteWebDriver) {
try { $driver->quit(); } catch (Exception $q) {}
}
} }
// take screenshot and save to file
//header for png
header('Content-Type: image/png');
echo $driver->takeScreenshot();
break; break;
default: default:
echo renderTemplate('index.html.php'); echo renderTemplate('index.html.php');