Skip to content

Commit

Permalink
Add ability to generate asynchronously using webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
maelanleborgne committed Jul 12, 2024
1 parent daec5e1 commit a2ad358
Show file tree
Hide file tree
Showing 20 changed files with 728 additions and 27 deletions.
7 changes: 7 additions & 0 deletions config/builder_pdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Sensiolabs\GotenbergBundle\Builder\Pdf\MarkdownPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MergePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\UrlPdfBuilder;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistryInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

Expand All @@ -23,6 +24,7 @@
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('request_stack'),
service('twig')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
Expand All @@ -36,6 +38,7 @@
service('request_stack'),
service('twig')->nullOnInvalid(),
service('router')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->call('setRequestContext', [service('.sensiolabs_gotenberg.request_context')->nullOnInvalid()])
Expand All @@ -49,6 +52,7 @@
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('request_stack'),
service('twig')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
Expand All @@ -59,6 +63,7 @@
->args([
service('sensiolabs_gotenberg.client'),
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
Expand All @@ -69,6 +74,7 @@
->args([
service('sensiolabs_gotenberg.client'),
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
Expand All @@ -79,6 +85,7 @@
->args([
service('sensiolabs_gotenberg.client'),
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
Expand Down
4 changes: 4 additions & 0 deletions config/builder_screenshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Sensiolabs\GotenbergBundle\Builder\Screenshot\HtmlScreenshotBuilder;
use Sensiolabs\GotenbergBundle\Builder\Screenshot\MarkdownScreenshotBuilder;
use Sensiolabs\GotenbergBundle\Builder\Screenshot\UrlScreenshotBuilder;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistryInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

Expand All @@ -20,6 +21,7 @@
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('request_stack'),
service('twig')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.screenshot_builder')
Expand All @@ -33,6 +35,7 @@
service('request_stack'),
service('twig')->nullOnInvalid(),
service('router')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->call('setRequestContext', [service('.sensiolabs_gotenberg.request_context')->nullOnInvalid()])
Expand All @@ -46,6 +49,7 @@
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
service('request_stack'),
service('twig')->nullOnInvalid(),
service('.sensiolabs_gotenberg.webhook_configuration_registry')->nullOnInvalid(),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.screenshot_builder')
Expand Down
11 changes: 11 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use Sensiolabs\GotenbergBundle\Client\GotenbergClient;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistry;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistryInterface;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Gotenberg;
use Sensiolabs\GotenbergBundle\GotenbergInterface;
Expand Down Expand Up @@ -63,4 +65,13 @@
])
->alias(GotenbergInterface::class, 'sensiolabs_gotenberg')
;

$services->set('.sensiolabs_gotenberg.webhook_configuration_registry', WebhookConfigurationRegistry::class)
->args([
service('router'),
service('.sensiolabs_gotenberg.request_context')->nullOnInvalid(),
])
->tag('sensiolabs_gotenberg.webhook_configuration_registry')
->alias(WebhookConfigurationRegistryInterface::class, '.sensiolabs_gotenberg.webhook_configuration_registry')
;
};
11 changes: 11 additions & 0 deletions src/Builder/AsyncBuilderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Sensiolabs\GotenbergBundle\Builder;

interface AsyncBuilderInterface
{
/**
* Generates a file asynchronously.
*/
public function generateAsync(): string;
}
5 changes: 4 additions & 1 deletion src/Builder/Pdf/AbstractChromiumPdfBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Sensiolabs\GotenbergBundle\Builder\CookieAwareTrait;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistry;
use Sensiolabs\GotenbergBundle\Enumeration\EmulatedMediaType;
use Sensiolabs\GotenbergBundle\Enumeration\PaperSizeInterface;
use Sensiolabs\GotenbergBundle\Enumeration\Part;
Expand All @@ -27,8 +28,9 @@ public function __construct(
AssetBaseDirFormatter $asset,
private readonly RequestStack $requestStack,
private readonly Environment|null $twig = null,
WebhookConfigurationRegistry|null $webhookConfigurationRegistry = null,
) {
parent::__construct($gotenbergClient, $asset);
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry);

$normalizers = [
'extraHttpHeaders' => function (mixed $value): array {
Expand Down Expand Up @@ -570,6 +572,7 @@ protected function addConfiguration(string $configurationName, mixed $value): vo
'fail_on_console_exceptions' => $this->failOnConsoleExceptions($value),
'skip_network_idle_event' => $this->skipNetworkIdleEvent($value),
'metadata' => $this->metadata($value),
'webhook' => null,
default => throw new InvalidBuilderConfiguration(sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, static::class)),
};
}
Expand Down
73 changes: 72 additions & 1 deletion src/Builder/Pdf/AbstractPdfBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@

namespace Sensiolabs\GotenbergBundle\Builder\Pdf;

use Sensiolabs\GotenbergBundle\Builder\AsyncBuilderInterface;
use Sensiolabs\GotenbergBundle\Builder\DefaultBuilderTrait;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\Client\GotenbergResponse;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistry;
use Sensiolabs\GotenbergBundle\Exception\WebhookConfigurationException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;

abstract class AbstractPdfBuilder implements PdfBuilderInterface
abstract class AbstractPdfBuilder implements PdfBuilderInterface, AsyncBuilderInterface
{
use DefaultBuilderTrait;

private string $webhookUrl;
private string $errorWebhookUrl;
/**
* @var array<string, mixed>
*/
private array $webhookExtraHeaders = [];
private \Closure $operationIdGenerator;

public function __construct(
GotenbergClientInterface $gotenbergClient,
AssetBaseDirFormatter $asset,
protected readonly WebhookConfigurationRegistry|null $webhookConfigurationRegistry = null,
) {
$this->client = $gotenbergClient;
$this->asset = $asset;
Expand All @@ -23,10 +35,69 @@ public function __construct(
return $this->encodeData('metadata', $value);
},
];

$this->operationIdGenerator = static fn (): string => 'gotenberg_'.bin2hex(random_bytes(16)).microtime(true);
}

public function generate(): GotenbergResponse
{
return $this->doCall();
}

public function generateAsync(): string
{
$operationId = ($this->operationIdGenerator)();
$this->logger?->debug('Generating PDF file async with operation id {sensiolabs_gotenberg.operation_id} using {sensiolabs_gotenberg.builder} builder.', [
'sensiolabs_gotenberg.operation_id' => $operationId,
'sensiolabs_gotenberg.builder' => $this::class,
]);

$this->webhookExtraHeaders['X-Gotenberg-Operation-Id'] = $operationId;
$headers = [
'Gotenberg-Webhook-Url' => $this->webhookUrl,
'Gotenberg-Webhook-Error-Url' => $this->errorWebhookUrl,
'Gotenberg-Webhook-Extra-Http-Headers' => json_encode($this->webhookExtraHeaders, \JSON_THROW_ON_ERROR),
];
if (null !== $this->fileName) {
$headers['Gotenberg-Output-Filename'] = basename($this->fileName, '.pdf');
}
$this->gotenbergClient->call($this->getEndpoint(), $this->getMultipartFormData(), $headers);

Check failure on line 64 in src/Builder/Pdf/AbstractPdfBuilder.php

View workflow job for this annotation

GitHub Actions / PHPStan

Access to an undefined property Sensiolabs\GotenbergBundle\Builder\Pdf\AbstractPdfBuilder::$gotenbergClient.

return $operationId;
}

public function webhookConfiguration(string $webhook): static
{
if (null === $this->webhookConfigurationRegistry) {
throw new WebhookConfigurationException('The WebhookConfigurationRegistry is not available.');
}
$webhookConfiguration = $this->webhookConfigurationRegistry->get($webhook);

return $this->webhookUrls($webhookConfiguration['success'], $webhookConfiguration['error']);
}

public function webhookUrls(string $successWebhook, string|null $errorWebhook = null): static
{
$this->webhookUrl = $successWebhook;
$this->errorWebhookUrl = $errorWebhook ?? $successWebhook;

return $this;
}

/**
* @param array<string, mixed> $extraHeaders
*/
public function webhookExtraHeaders(array $extraHeaders): static
{
$this->webhookExtraHeaders = array_merge($this->webhookExtraHeaders, $extraHeaders);

return $this;
}

public function operationIdGenerator(\Closure $operationIdGenerator): static
{
$this->operationIdGenerator = $operationIdGenerator;

return $this;
}
}
4 changes: 3 additions & 1 deletion src/Builder/Pdf/UrlPdfBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Sensiolabs\GotenbergBundle\Builder\Pdf;

use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistry;
use Sensiolabs\GotenbergBundle\Exception\MissingRequiredFieldException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Symfony\Component\HttpFoundation\RequestStack;
Expand All @@ -22,8 +23,9 @@ public function __construct(
RequestStack $requestStack,
Environment|null $twig = null,
private readonly UrlGeneratorInterface|null $urlGenerator = null,
WebhookConfigurationRegistry|null $webhookConfigurationRegistry = null,
) {
parent::__construct($gotenbergClient, $asset, $requestStack, $twig);
parent::__construct($gotenbergClient, $asset, $requestStack, $twig, $webhookConfigurationRegistry);

$this->addNormalizer('route', $this->generateUrlFromRoute(...));
}
Expand Down
4 changes: 3 additions & 1 deletion src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Sensiolabs\GotenbergBundle\Builder\CookieAwareTrait;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistryInterface;
use Sensiolabs\GotenbergBundle\Enumeration\EmulatedMediaType;
use Sensiolabs\GotenbergBundle\Enumeration\Part;
use Sensiolabs\GotenbergBundle\Enumeration\ScreenshotFormat;
Expand All @@ -25,8 +26,9 @@ public function __construct(
AssetBaseDirFormatter $asset,
private readonly RequestStack $requestStack,
private readonly Environment|null $twig = null,
WebhookConfigurationRegistryInterface|null $webhookConfigurationRegistry = null,
) {
parent::__construct($gotenbergClient, $asset);
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry);

$normalizers = [
'extraHttpHeaders' => function (mixed $value): array {
Expand Down
73 changes: 72 additions & 1 deletion src/Builder/Screenshot/AbstractScreenshotBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@

namespace Sensiolabs\GotenbergBundle\Builder\Screenshot;

use Sensiolabs\GotenbergBundle\Builder\AsyncBuilderInterface;
use Sensiolabs\GotenbergBundle\Builder\DefaultBuilderTrait;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\Client\GotenbergResponse;
use Sensiolabs\GotenbergBundle\DependencyInjection\WebhookConfiguration\WebhookConfigurationRegistryInterface;
use Sensiolabs\GotenbergBundle\Enumeration\Part;
use Sensiolabs\GotenbergBundle\Exception\WebhookConfigurationException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Symfony\Component\Mime\Part\DataPart;

abstract class AbstractScreenshotBuilder implements ScreenshotBuilderInterface
abstract class AbstractScreenshotBuilder implements ScreenshotBuilderInterface, AsyncBuilderInterface
{
use DefaultBuilderTrait;

private string $webhookUrl;
private string $errorWebhookUrl;
/**
* @var array<string, mixed>
*/
private array $webhookExtraHeaders = [];
private \Closure $operationIdGenerator;

public function __construct(
GotenbergClientInterface $gotenbergClient,
AssetBaseDirFormatter $asset,
protected readonly WebhookConfigurationRegistryInterface|null $webhookConfigurationRegistry = null,
) {
$this->client = $gotenbergClient;
$this->asset = $asset;
Expand All @@ -37,10 +49,69 @@ public function __construct(
return $this->encodeData('cookies', array_values($value));
},
];

$this->operationIdGenerator = static fn (): string => 'gotenberg_'.bin2hex(random_bytes(16)).microtime(true);
}

public function generate(): GotenbergResponse
{
return $this->doCall();
}

public function generateAsync(): string
{
$operationId = ($this->operationIdGenerator)();
$this->logger?->debug('Generating PDF file async with operation id {sensiolabs_gotenberg.operation_id} using {sensiolabs_gotenberg.builder} builder.', [
'sensiolabs_gotenberg.operation_id' => $operationId,
'sensiolabs_gotenberg.builder' => $this::class,
]);

$this->webhookExtraHeaders['X-Gotenberg-Operation-Id'] = $operationId;
$headers = [
'Gotenberg-Webhook-Url' => $this->webhookUrl,
'Gotenberg-Webhook-Error-Url' => $this->errorWebhookUrl,
'Gotenberg-Webhook-Extra-Http-Headers' => json_encode($this->webhookExtraHeaders, \JSON_THROW_ON_ERROR),
];
if (null !== $this->fileName) {
$headers['Gotenberg-Output-Filename'] = basename($this->fileName, '.pdf');
}
$this->gotenbergClient->call($this->getEndpoint(), $this->getMultipartFormData(), $headers);

Check failure on line 78 in src/Builder/Screenshot/AbstractScreenshotBuilder.php

View workflow job for this annotation

GitHub Actions / PHPStan

Access to an undefined property Sensiolabs\GotenbergBundle\Builder\Screenshot\AbstractScreenshotBuilder::$gotenbergClient.

return $operationId;
}

public function webhookConfiguration(string $webhook): static
{
if (null === $this->webhookConfigurationRegistry) {
throw new WebhookConfigurationException('The WebhookConfigurationRegistry is not available.');
}
$webhookConfiguration = $this->webhookConfigurationRegistry->get($webhook);

return $this->webhookUrls($webhookConfiguration['success'], $webhookConfiguration['error']);
}

public function webhookUrls(string $successWebhook, string|null $errorWebhook = null): static
{
$this->webhookUrl = $successWebhook;
$this->errorWebhookUrl = $errorWebhook ?? $successWebhook;

return $this;
}

/**
* @param array<string, mixed> $extraHeaders
*/
public function webhookExtraHeaders(array $extraHeaders): static
{
$this->webhookExtraHeaders = array_merge($this->webhookExtraHeaders, $extraHeaders);

return $this;
}

public function operationIdGenerator(\Closure $operationIdGenerator): static
{
$this->operationIdGenerator = $operationIdGenerator;

return $this;
}
}
Loading

0 comments on commit a2ad358

Please sign in to comment.