preparations and testing for a rewrite, ditching wkhtmltopdf for chrome-driver

This commit is contained in:
Christian Haschek 2025-01-12 20:13:47 +00:00
parent cf07363a8d
commit f54d35c312
18 changed files with 516 additions and 77 deletions

7
.devcontainer/Caddyfile Normal file
View File

@ -0,0 +1,7 @@
:8080 {
root * xxxxxxxxxx
php_fastcgi 127.0.0.1:9000
file_server
try_files {path} {path}/ /index.php?{query}
}

23
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM alpine:3.21
RUN apk add --no-cache git curl php83 php83-fpm php83-opcache caddy php83-curl php83-xdebug chromium-chromedriver
ADD .devcontainer/start.sh /start.sh
RUN chmod +x /start.sh
RUN apk add --no-cache php83-ctype php83-dom php83-fileinfo php83-gd php83-iconv php83-simplexml php83-xml php83-xmlreader php83-xmlwriter php83-zip php83-phar php83-openssl
RUN curl -sS https://getcomposer.org/installer | /usr/bin/php83 -- --install-dir=/usr/bin --filename=composer
# caddy stuff
ADD .devcontainer/Caddyfile /etc/caddy/Caddyfile
RUN sed -i 's/nobody/caddy/g' /etc/php83/php-fpm.d/www.conf
RUN sed -i 's/E_ALL \& ~E_DEPRECATED \& ~E_STRICT/E_ALL \& ~E_DEPRECATED \& ~E_STRICT \& ~E_NOTICE \& ~E_WARNING/g' /etc/php83/php.ini
# configure xdebug
RUN echo "zend_extension=xdebug.so" > /etc/php83/conf.d/xdebug.ini
RUN echo "xdebug.mode=debug" >> /etc/php83/conf.d/xdebug.ini
RUN echo "xdebug.start_with_request=yes" >> /etc/php83/conf.d/xdebug.ini
RUN echo "xdebug.client_host=127.0.0.1" >> /etc/php83/conf.d/xdebug.ini
RUN echo "xdebug.client_port=9003" >> /etc/php83/conf.d/xdebug.ini
RUN echo "xdebug.idekey=VSCODE" >> /etc/php83/conf.d/xdebug.ini

View File

@ -0,0 +1,35 @@
{
"name": "Web Project Dev Container",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"forwardPorts": [8080],
"postCreateCommand": "/start.sh",
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.shell.linux": "/bin/ash",
"php.validate.executablePath": "/usr/bin/php",
"php.debug.listenPort": 9003,
"php.debug.log": true
},
"extensions": [
"ms-azuretools.vscode-docker",
"felixfbecker.php-debug",
"bmewburn.vscode-intelephense-client",
"otovo-oss.htmx-tags",
"devsense.phptools-vscode",
"bmewburn.vscode-intelephense-client",
"github.copilot",
"github.copilot-chat",
"anbuselvanrocky.bootstrap5-vscode",
"hansuxdev.bootstrap5-snippets",
"alefragnani.Bookmarks",
"eamodio.gitlens"
]
}
},
"remoteUser": "root"
}

23
.devcontainer/start.sh Normal file
View File

@ -0,0 +1,23 @@
WORKSPACE_PATH=$( pwd )
# Escape the path for safe use in sed
ESCAPED_PATH=$(echo "$WORKSPACE_PATH" | sed 's/\//\\\//g')
echo "[!] Folder is $WORKSPACE_PATH"
echo ' [+] Starting php'
php-fpm83
echo ' [+] Starting Caddy'
sed -i "s|xxxxxxxxxx|$ESCAPED_PATH\/web|g" /etc/caddy/Caddyfile
cd ${WORKSPACE_PATH}/src
composer install
echo ' [+] Starting Chrome'
chromedriver --port=4444 &
chmod 777 ${WORKSPACE_PATH}/cache
chmod 777 ${WORKSPACE_PATH}/logs
caddy run --config /etc/caddy/Caddyfile

59
.github/workflows/build-docker.yml vendored Normal file
View File

@ -0,0 +1,59 @@
name: ci
on:
push:
tags:
- "v*.*.*"
pull_request:
branches:
- "master"
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
hascheksolutions/http2pic
ghcr.io/hascheksolutions/http2pic
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

14
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003
}
]
}

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
version: '3.3'
services:
http2pic:
image: ghcr.io/hascheksolutions/http2pic:2
restart: unless-stopped
environment:
- URL=http://localhost:8080
ports:
- 8080:80

View File

@ -1,5 +1,7 @@
:80 {
root * /srv
root * /srv/web
php_fastcgi 127.0.0.1:9000
file_server
try_files {path} {path}/ /index.php?{query}
}

View File

@ -1,16 +1,29 @@
FROM ghcr.io/surnet/alpine-wkhtmltopdf:3.20.3-0.12.6-full
FROM alpine:3.21
# Install PHP and necessary extensions
RUN apk add --no-cache php83 php83-fpm php83-opcache caddy php83-curl
RUN apk add --no-cache curl php83 php83-fpm php83-opcache caddy php83-curl php83-xdebug chromium-chromedriver
RUN sed -i 's/nobody/caddy/g' /etc/php83/php-fpm.d/www.conf
RUN sed -i 's/E_ALL \& ~E_DEPRECATED \& ~E_STRICT/E_ALL \& ~E_DEPRECATED \& ~E_STRICT \& ~E_NOTICE \& ~E_WARNING/g' /etc/php83/php.ini
# Install additional PHP extensions
RUN apk add --no-cache php83-ctype php83-dom php83-fileinfo php83-gd php83-iconv php83-simplexml php83-xml php83-xmlreader php83-xmlwriter php83-zip php83-phar php83-openssl
RUN curl -sS https://getcomposer.org/installer | /usr/bin/php83 -- --install-dir=/usr/bin --filename=composer
ADD docker/start.sh /start.sh
RUN chmod +x /start.sh
# Copy the contents of the web/ directory to /srv
COPY web/ /srv
ADD . /srv
# Cleanup
RUN rm -rf /srv/.git
RUN rm -rf /srv/.github
RUN rm -rf /srv/.vscode
# Copy the Caddyfile to the container
COPY docker/Caddyfile /etc/caddy/Caddyfile
# Set the default command to start PHP-FPM and Caddy
CMD ["sh", "-c", "php-fpm83 && caddy run --config /etc/caddy/Caddyfile"]
# Run start script
CMD ["sh", "-c", "/start.sh"]

25
docker/start.sh Normal file
View File

@ -0,0 +1,25 @@
echo ' [+] Starting php'
php-fpm83
cd /srv/src
composer install
echo ' [+] Starting Chrome'
chromedriver --port=4444 &
chmod 777 /srv/cache
chmod 777 /srv/logs
echo ' [+] Building config'
_buildConfig() {
echo "<?php"
echo "date_default_timezone_set('Europe/Vienna');"
echo "define('URL','${URL:-http://localhost:8080}');"
echo ""
}
_buildConfig > src/inc/config.inc.php
caddy run --config /etc/caddy/Caddyfile

2
logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

1
src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor/

5
src/composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"php-webdriver/webdriver": "^1.15"
}
}

226
src/composer.lock generated Normal file
View File

@ -0,0 +1,226 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9c8ecf7ef31de7c4a3608d683b57a301",
"packages": [
{
"name": "php-webdriver/webdriver",
"version": "1.15.2",
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf",
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"php": "^7.3 || ^8.0",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^5.0 || ^6.0 || ^7.0"
},
"replace": {
"facebook/webdriver": "*"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.20.0",
"ondram/ci-detector": "^4.0",
"php-coveralls/php-coveralls": "^2.4",
"php-mock/php-mock-phpunit": "^2.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.0 || ^6.0 || ^7.0"
},
"suggest": {
"ext-SimpleXML": "For Firefox profile creation"
},
"type": "library",
"autoload": {
"files": [
"lib/Exception/TimeoutException.php"
],
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"homepage": "https://github.com/php-webdriver/php-webdriver",
"keywords": [
"Chromedriver",
"geckodriver",
"php",
"selenium",
"webdriver"
],
"support": {
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2"
},
"time": "2024-11-21T15:12:59+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/process",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"shasum": ""
},
"require": {
"php": ">=8.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-11-06T14:24:19+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

@ -1,3 +1,3 @@
<?php
define('URL','http://localhost:8081');
define('URL','http://localhost:8080');

View File

@ -27,7 +27,7 @@ define('ONDOMAINFAILIMAGE', __DIR__.'/img/domainfailed.jpg');
define('RENDERINGENGINE','wkhtmltoimage');
//location of wkhtmltoimage
define('WKHTMLTOIMAGEPATH','/usr/sbin/wkhtmltoimage');
define('WKHTMLTOIMAGEPATH','wkhtmltoimage');
//location of phantomJS
define('PHANTOMJSPATH',__DIR__.'/phantomjs');
@ -143,46 +143,7 @@ class http2pic
function render()
{
//if phantomjs is selected and installed
if(RENDERINGENGINE=='phantomjs' && file_exists(PHANTOMJSPATH))
return $this->renderPagePHANTOMJS();
//no? well ok how about WKHTMLToImage?
else if(RENDERINGENGINE=='wkhtmltoimage' && file_exists(WKHTMLTOIMAGEPATH))
return $this->renderPageWKHTMLTOIMAGE();
//you're fucked
else
throw new Exception('No valid rendering engine found');
}
/**
* Render using PhantomJS
**/
function renderPagePHANTOMJS()
{
$cmd = 'timeout '.$this->params['timeout'].' '.PHANTOMJSPATH;
$cmd.= ' --ignore-ssl-errors=yes --ssl-protocol=any '.__DIR__.'/phantom.js ';
$cmd.= ($this->params['url']);
$cmd.= ','.($this->params['file']);
$cmd.= ','.$this->params['vp_w'];
$cmd.= ','.$this->params['vp_h'];
$cmd.= ','.$this->params['js'];
$cmd = escapeshellcmd($cmd);
shell_exec($cmd);
$this->params['cmd'] = $cmd;
$this->postRender();
if(DEBUG)
{
$fp = fopen('debug.log', 'a');
fwrite($fp, $cmd."\n");
fclose($fp);
}
return $cmd;
return $this->renderPageWKHTMLTOIMAGE();
}
/**
@ -214,6 +175,10 @@ class http2pic
//add storage path to cmd
$cmd.=' '.escapeshellarg($this->params['file']);
$cmd.=' --wait-for-network-idle';
var_dump($cmd);
$cmd = escapeshellcmd($cmd);
shell_exec($cmd);
@ -241,7 +206,8 @@ class http2pic
// resize if necessary
if($this->params['resizewidth'])
$this->resizeImage($this->params['file']);
if(!file_exists($this->params['file'])) exit("Error: File not found");
//print image to user
if ($this->params['type'] === 'png') {

View File

@ -1,23 +0,0 @@
<?php
include_once(ROOT.DS.'src'.DS.'http2pic.class.php');
$url = $_GET['url'];
$type = $_GET['type'];
$timeout = $_GET['timeout'];
$viewport = $_GET['viewport'];
$js = $_GET['js'];
$resizewidth = $_GET['width'];
$cache = $_GET['cache'];
$onfail = rawurldecode($_GET['onfail']);
$params = array('url'=>trim($url),
'type'=>$type,
'timeout'=>$timeout,
'viewport'=>$viewport,
'js'=>$js,
'resizewidth'=>$resizewidth,
'cache'=>$cache,
'onfail'=>$onfail);
$http2pic = new http2pic($params);
//echo nl2br(print_r($http2pic->debug(),true));

View File

@ -1,10 +1,14 @@
<?php
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
require_once(ROOT.DS.'src'.DS.'config.inc.php');
require_once(ROOT.DS.'src'.DS.'helpers.php');
require_once(ROOT.DS.'src'.DS.'http2pic.class.php');
require_once(ROOT.DS.'src'.DS.'vendor'.DS.'autoload.php');
$url = array_filter(explode('/',ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),'/')));
@ -15,13 +19,60 @@ if(php_sapi_name()=='cli-server' && file_exists(ROOT.DS.'web'.DS.implode('/',$ur
switch($url[0])
{
case 'test':
$target = $_GET['url'];
$type = $_GET['type'];
$timeout = $_GET['timeout'];
$viewport = $_GET['viewport'];
$js = $_GET['js']=='false'?false:true;
$resizewidth = $_GET['width'];
$serverUrl = 'http://localhost:4444';
$options = new \Facebook\WebDriver\Chrome\ChromeOptions();
$options->addArguments(['--headless', '--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']);
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability(\Facebook\WebDriver\Chrome\ChromeOptions::CAPABILITY, $options);
//disable javascript if $js is false
if(!$js)
$capabilities->setCapability('javascriptEnabled', false);
$driver = RemoteWebDriver::create($serverUrl, $capabilities);
$driver->get($target);
//hide scroll bars
$driver->executeScript('document.body.style.overflow = "hidden";');
//set screenshot size to 1920x1080
//$driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension(1024, 768));
//if $viewport is set, set window size
if($viewport)
{
$viewport = explode('x',$viewport);
$driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension($viewport[0], $viewport[1]));
}
else
{
$driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension(1024, 768));
}
// take screenshot and save to file
//header for png
header('Content-Type: image/png');
echo $driver->takeScreenshot();
break;
case 'api':
$url = $_GET['url'];
$type = $_GET['type'];
$timeout = $_GET['timeout'];
$viewport = $_GET['viewport'];
$js = $_GET['js'];
$resizewidth = $_GET['width'];
$type = $_GET['type'];
$timeout = $_GET['timeout'];
$viewport = $_GET['viewport'];
$js = $_GET['js'];
$resizewidth = $_GET['width'];
$cache = $_GET['cache'];
$onfail = rawurldecode($_GET['onfail']);