mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-27 16:26:37 +03:00
commit
e775b0f12f
14 changed files with 118 additions and 152 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
|
## 2.1.3 - 2020-04-09
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
#### Deprecated
|
||||||
|
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
#### Removed
|
||||||
|
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
|
||||||
|
* [#712](https://github.com/shlinkio/shlink/issues/712) Fixed app set-up not clearing entities metadata cache.
|
||||||
|
* [#711](https://github.com/shlinkio/shlink/issues/711) Fixed `HEAD` requests returning a duplicated `Content-Length` header.
|
||||||
|
* [#716](https://github.com/shlinkio/shlink/issues/716) Fixed Twitter not properly displaying preview for final long URL.
|
||||||
|
* [#717](https://github.com/shlinkio/shlink/issues/717) Fixed DB connection expiring on task workers when using swoole.
|
||||||
|
* [#705](https://github.com/shlinkio/shlink/issues/705) Fixed how the short URL domain is inferred when generating QR codes, making sure the configured domain is respected even if the request is performed using a different one, and only when a custom domain is used, then that one is used instead.
|
||||||
|
|
||||||
|
|
||||||
## 2.1.2 - 2020-03-29
|
## 2.1.2 - 2020-03-29
|
||||||
|
|
||||||
#### Added
|
#### Added
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
"shlinkio/shlink-common": "^3.0",
|
"shlinkio/shlink-common": "^3.0",
|
||||||
"shlinkio/shlink-config": "^1.0",
|
"shlinkio/shlink-config": "^1.0",
|
||||||
"shlinkio/shlink-event-dispatcher": "^1.4",
|
"shlinkio/shlink-event-dispatcher": "^1.4",
|
||||||
"shlinkio/shlink-installer": "^4.3.1",
|
"shlinkio/shlink-installer": "^4.3.2",
|
||||||
"shlinkio/shlink-ip-geolocation": "^1.4",
|
"shlinkio/shlink-ip-geolocation": "^1.4",
|
||||||
"symfony/console": "^5.0",
|
"symfony/console": "^5.0",
|
||||||
"symfony/filesystem": "^5.0",
|
"symfony/filesystem": "^5.0",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"eaglewu/swoole-ide-helper": "dev-master",
|
"eaglewu/swoole-ide-helper": "dev-master",
|
||||||
"infection/infection": "^0.16.1",
|
"infection/infection": "^0.16.1",
|
||||||
"phpstan/phpstan": "^0.12.18",
|
"phpstan/phpstan": "^0.12.18",
|
||||||
"phpunit/phpunit": "^9.0.1",
|
"phpunit/phpunit": "~9.0.1",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"shlinkio/php-coding-standard": "~2.1.0",
|
"shlinkio/php-coding-standard": "~2.1.0",
|
||||||
"shlinkio/shlink-test-utils": "^1.4",
|
"shlinkio/shlink-test-utils": "^1.4",
|
||||||
|
|
|
@ -5,8 +5,9 @@ declare(strict_types=1);
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||||
use Mezzio;
|
use Mezzio\Helper;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -14,7 +15,7 @@ return [
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Mezzio\Helper\ContentLengthMiddleware::class,
|
Helper\ContentLengthMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -35,14 +36,15 @@ return [
|
||||||
|
|
||||||
'routing' => [
|
'routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Mezzio\Router\Middleware\RouteMiddleware::class,
|
Router\Middleware\RouteMiddleware::class,
|
||||||
|
Router\Middleware\ImplicitHeadMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'rest' => [
|
'rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Mezzio\Router\Middleware\ImplicitOptionsMiddleware::class,
|
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
Rest\Middleware\BodyParserMiddleware::class,
|
Rest\Middleware\BodyParserMiddleware::class,
|
||||||
Rest\Middleware\AuthenticationMiddleware::class,
|
Rest\Middleware\AuthenticationMiddleware::class,
|
||||||
],
|
],
|
||||||
|
@ -50,7 +52,7 @@ return [
|
||||||
|
|
||||||
'dispatch' => [
|
'dispatch' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Mezzio\Router\Middleware\DispatchMiddleware::class,
|
Router\Middleware\DispatchMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -67,4 +69,5 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,6 +12,9 @@ php bin/cli db:migrate -n -q
|
||||||
echo "Generating proxies..."
|
echo "Generating proxies..."
|
||||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
||||||
|
|
||||||
|
echo "Clearing entities cache..."
|
||||||
|
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n -q
|
||||||
|
|
||||||
# When restarting the container, swoole might think it is already in execution
|
# When restarting the container, swoole might think it is already in execution
|
||||||
# This forces the app to be started every second until the exit code is 0
|
# This forces the app to be started every second until the exit code is 0
|
||||||
until php vendor/mezzio/mezzio-swoole/bin/mezzio-swoole start; do sleep 1 ; done
|
until php vendor/mezzio/mezzio-swoole/bin/mezzio-swoole start; do sleep 1 ; done
|
||||||
|
|
|
@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Core;
|
namespace Shlinkio\Shlink\Core;
|
||||||
|
|
||||||
use Doctrine\Common\Cache\Cache;
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Mezzio\Router\RouterInterface;
|
|
||||||
use Mezzio\Template\TemplateRendererInterface;
|
use Mezzio\Template\TemplateRendererInterface;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Shlinkio\Shlink\Core\Domain\Resolver;
|
use Shlinkio\Shlink\Core\Domain\Resolver;
|
||||||
|
@ -39,8 +37,6 @@ return [
|
||||||
Action\PixelAction::class => ConfigAbstractFactory::class,
|
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||||
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
|
||||||
|
|
||||||
Resolver\PersistenceDomainResolver::class => ConfigAbstractFactory::class,
|
Resolver\PersistenceDomainResolver::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -81,13 +77,11 @@ return [
|
||||||
'Logger_Shlink',
|
'Logger_Shlink',
|
||||||
],
|
],
|
||||||
Action\QrCodeAction::class => [
|
Action\QrCodeAction::class => [
|
||||||
RouterInterface::class,
|
|
||||||
Service\ShortUrl\ShortUrlResolver::class,
|
Service\ShortUrl\ShortUrlResolver::class,
|
||||||
|
'config.url_shortener.domain',
|
||||||
'Logger_Shlink',
|
'Logger_Shlink',
|
||||||
],
|
],
|
||||||
|
|
||||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
|
||||||
|
|
||||||
Resolver\PersistenceDomainResolver::class => ['em'],
|
Resolver\PersistenceDomainResolver::class => ['em'],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
|
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
|
||||||
use RKA\Middleware\IpAddress;
|
use RKA\Middleware\IpAddress;
|
||||||
use Shlinkio\Shlink\Core\Action;
|
use Shlinkio\Shlink\Core\Action;
|
||||||
use Shlinkio\Shlink\Core\Middleware;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
@ -32,7 +31,6 @@ return [
|
||||||
'name' => Action\QrCodeAction::class,
|
'name' => Action\QrCodeAction::class,
|
||||||
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Middleware\QrCodeCacheMiddleware::class,
|
|
||||||
Action\QrCodeAction::class,
|
Action\QrCodeAction::class,
|
||||||
],
|
],
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||||
|
|
|
@ -4,7 +4,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Action;
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
use Laminas\Diactoros\Uri;
|
use Laminas\Diactoros\Uri;
|
||||||
|
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
@ -24,7 +26,7 @@ use function array_merge;
|
||||||
use function GuzzleHttp\Psr7\build_query;
|
use function GuzzleHttp\Psr7\build_query;
|
||||||
use function GuzzleHttp\Psr7\parse_query;
|
use function GuzzleHttp\Psr7\parse_query;
|
||||||
|
|
||||||
abstract class AbstractTrackingAction implements MiddlewareInterface
|
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
|
||||||
{
|
{
|
||||||
private ShortUrlResolverInterface $urlResolver;
|
private ShortUrlResolverInterface $urlResolver;
|
||||||
private VisitsTrackerInterface $visitTracker;
|
private VisitsTrackerInterface $visitTracker;
|
||||||
|
@ -50,14 +52,13 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$url = $this->urlResolver->resolveEnabledShortUrl($identifier);
|
$shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier);
|
||||||
|
|
||||||
// Track visit to this short code
|
if ($this->shouldTrackRequest($request, $query, $disableTrackParam)) {
|
||||||
if ($disableTrackParam === null || ! array_key_exists($disableTrackParam, $query)) {
|
$this->visitTracker->track($shortUrl, Visitor::fromRequest($request));
|
||||||
$this->visitTracker->track($url, Visitor::fromRequest($request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->createSuccessResp($this->buildUrlToRedirectTo($url, $query, $disableTrackParam));
|
return $this->createSuccessResp($this->buildUrlToRedirectTo($shortUrl, $query, $disableTrackParam));
|
||||||
} catch (ShortUrlNotFoundException $e) {
|
} catch (ShortUrlNotFoundException $e) {
|
||||||
$this->logger->warning('An error occurred while tracking short code. {e}', ['e' => $e]);
|
$this->logger->warning('An error occurred while tracking short code. {e}', ['e' => $e]);
|
||||||
return $this->createErrorResp($request, $handler);
|
return $this->createErrorResp($request, $handler);
|
||||||
|
@ -76,6 +77,16 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||||
return (string) $uri->withQuery(build_query($mergedQuery));
|
return (string) $uri->withQuery(build_query($mergedQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function shouldTrackRequest(ServerRequestInterface $request, array $query, ?string $disableTrackParam): bool
|
||||||
|
{
|
||||||
|
$forwardedMethod = $request->getAttribute(ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE);
|
||||||
|
if ($forwardedMethod === self::METHOD_HEAD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $disableTrackParam === null || ! array_key_exists($disableTrackParam, $query);
|
||||||
|
}
|
||||||
|
|
||||||
abstract protected function createSuccessResp(string $longUrl): ResponseInterface;
|
abstract protected function createSuccessResp(string $longUrl): ResponseInterface;
|
||||||
|
|
||||||
abstract protected function createErrorResp(
|
abstract protected function createErrorResp(
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace Shlinkio\Shlink\Core\Action;
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
use Endroid\QrCode\QrCode;
|
use Endroid\QrCode\QrCode;
|
||||||
use Mezzio\Router\RouterInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
@ -23,17 +22,17 @@ class QrCodeAction implements MiddlewareInterface
|
||||||
private const MIN_SIZE = 50;
|
private const MIN_SIZE = 50;
|
||||||
private const MAX_SIZE = 1000;
|
private const MAX_SIZE = 1000;
|
||||||
|
|
||||||
private RouterInterface $router;
|
|
||||||
private ShortUrlResolverInterface $urlResolver;
|
private ShortUrlResolverInterface $urlResolver;
|
||||||
|
private array $domainConfig;
|
||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
RouterInterface $router,
|
|
||||||
ShortUrlResolverInterface $urlResolver,
|
ShortUrlResolverInterface $urlResolver,
|
||||||
|
array $domainConfig,
|
||||||
?LoggerInterface $logger = null
|
?LoggerInterface $logger = null
|
||||||
) {
|
) {
|
||||||
$this->router = $router;
|
|
||||||
$this->urlResolver = $urlResolver;
|
$this->urlResolver = $urlResolver;
|
||||||
|
$this->domainConfig = $domainConfig;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,23 +41,19 @@ class QrCodeAction implements MiddlewareInterface
|
||||||
$identifier = ShortUrlIdentifier::fromRedirectRequest($request);
|
$identifier = ShortUrlIdentifier::fromRedirectRequest($request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->urlResolver->resolveEnabledShortUrl($identifier);
|
$shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier);
|
||||||
} catch (ShortUrlNotFoundException $e) {
|
} catch (ShortUrlNotFoundException $e) {
|
||||||
$this->logger->warning('An error occurred while creating QR code. {e}', ['e' => $e]);
|
$this->logger->warning('An error occurred while creating QR code. {e}', ['e' => $e]);
|
||||||
return $handler->handle($request);
|
return $handler->handle($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $this->router->generateUri(RedirectAction::class, ['shortCode' => $identifier->shortCode()]);
|
$qrCode = new QrCode($shortUrl->toString($this->domainConfig));
|
||||||
$size = $this->getSizeParam($request);
|
$qrCode->setSize($this->getSizeParam($request));
|
||||||
|
|
||||||
$qrCode = new QrCode((string) $request->getUri()->withPath($path)->withQuery(''));
|
|
||||||
$qrCode->setSize($size);
|
|
||||||
$qrCode->setMargin(0);
|
$qrCode->setMargin(0);
|
||||||
|
|
||||||
return new QrCodeResponse($qrCode);
|
return new QrCodeResponse($qrCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
private function getSizeParam(Request $request): int
|
private function getSizeParam(Request $request): int
|
||||||
{
|
{
|
||||||
$size = (int) $request->getAttribute('size', self::DEFAULT_SIZE);
|
$size = (int) $request->getAttribute('size', self::DEFAULT_SIZE);
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManager;
|
||||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||||
|
@ -41,8 +42,15 @@ class LocateShortUrlVisit
|
||||||
|
|
||||||
public function __invoke(ShortUrlVisited $shortUrlVisited): void
|
public function __invoke(ShortUrlVisited $shortUrlVisited): void
|
||||||
{
|
{
|
||||||
|
// FIXME Temporarily handling DB connection reset here to fix https://github.com/shlinkio/shlink/issues/717
|
||||||
|
// Remove when https://github.com/shlinkio/shlink-event-dispatcher/issues/23 is implemented
|
||||||
|
if ($this->em instanceof ReopeningEntityManager) {
|
||||||
|
$this->em->open();
|
||||||
|
}
|
||||||
|
|
||||||
$visitId = $shortUrlVisited->visitId();
|
$visitId = $shortUrlVisited->visitId();
|
||||||
|
|
||||||
|
try {
|
||||||
/** @var Visit|null $visit */
|
/** @var Visit|null $visit */
|
||||||
$visit = $this->em->find(Visit::class, $visitId);
|
$visit = $this->em->find(Visit::class, $visitId);
|
||||||
if ($visit === null) {
|
if ($visit === null) {
|
||||||
|
@ -57,6 +65,12 @@ class LocateShortUrlVisit
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->eventDispatcher->dispatch(new VisitLocated($visitId));
|
$this->eventDispatcher->dispatch(new VisitLocated($visitId));
|
||||||
|
} finally {
|
||||||
|
// FIXME Temporarily handling DB connection reset here to fix https://github.com/shlinkio/shlink/issues/717
|
||||||
|
// Remove when https://github.com/shlinkio/shlink-event-dispatcher/issues/23 is implemented
|
||||||
|
$this->em->getConnection()->close();
|
||||||
|
$this->em->clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function downloadOrUpdateGeoLiteDb(string $visitId): bool
|
private function downloadOrUpdateGeoLiteDb(string $visitId): bool
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Middleware;
|
|
||||||
|
|
||||||
use Doctrine\Common\Cache\Cache;
|
|
||||||
use Laminas\Diactoros\Response as DiactResp;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
|
|
||||||
class QrCodeCacheMiddleware implements MiddlewareInterface
|
|
||||||
{
|
|
||||||
private Cache $cache;
|
|
||||||
|
|
||||||
public function __construct(Cache $cache)
|
|
||||||
{
|
|
||||||
$this->cache = $cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process an incoming server request and return a response, optionally delegating
|
|
||||||
* to the next middleware component to create the response.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
|
||||||
{
|
|
||||||
$cacheKey = $request->getUri()->getPath();
|
|
||||||
|
|
||||||
// If this QR code is already cached, just return it
|
|
||||||
if ($this->cache->contains($cacheKey)) {
|
|
||||||
$qrData = $this->cache->fetch($cacheKey);
|
|
||||||
$response = new DiactResp();
|
|
||||||
$response->getBody()->write($qrData['body']);
|
|
||||||
return $response->withHeader('Content-Type', $qrData['content-type']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not, call the next middleware and cache it
|
|
||||||
/** @var Response $resp */
|
|
||||||
$resp = $handler->handle($request);
|
|
||||||
$this->cache->save($cacheKey, [
|
|
||||||
'body' => $resp->getBody()->__toString(),
|
|
||||||
'content-type' => $resp->getHeaderLine('Content-Type'),
|
|
||||||
]);
|
|
||||||
return $resp;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,7 +30,7 @@ class QrCodeActionTest extends TestCase
|
||||||
|
|
||||||
$this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class);
|
$this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class);
|
||||||
|
|
||||||
$this->action = new QrCodeAction($router->reveal(), $this->urlResolver->reveal());
|
$this->action = new QrCodeAction($this->urlResolver->reveal(), ['domain' => 'doma.in']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
|
@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Core\Action;
|
namespace ShlinkioTest\Shlink\Core\Action;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
use Laminas\Diactoros\Response;
|
use Laminas\Diactoros\Response;
|
||||||
use Laminas\Diactoros\ServerRequest;
|
use Laminas\Diactoros\ServerRequest;
|
||||||
|
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
@ -89,4 +91,23 @@ class RedirectActionTest extends TestCase
|
||||||
|
|
||||||
$handle->shouldHaveBeenCalledOnce();
|
$handle->shouldHaveBeenCalledOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function trackingIsDisabledWhenRequestIsForwardedFromHead(): void
|
||||||
|
{
|
||||||
|
$shortCode = 'abc123';
|
||||||
|
$shortUrl = new ShortUrl('http://domain.com/foo/bar?some=thing');
|
||||||
|
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))->willReturn($shortUrl);
|
||||||
|
$track = $this->visitTracker->track(Argument::cetera())->will(function (): void {
|
||||||
|
});
|
||||||
|
|
||||||
|
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode)
|
||||||
|
->withAttribute(
|
||||||
|
ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE,
|
||||||
|
RequestMethodInterface::METHOD_HEAD,
|
||||||
|
);
|
||||||
|
$this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
|
||||||
|
|
||||||
|
$track->shouldNotHaveBeenCalled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Core\EventDispatcher;
|
namespace ShlinkioTest\Shlink\Core\EventDispatcher;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
|
@ -37,6 +38,10 @@ class LocateShortUrlVisitTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->ipLocationResolver = $this->prophesize(IpLocationResolverInterface::class);
|
$this->ipLocationResolver = $this->prophesize(IpLocationResolverInterface::class);
|
||||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$conn = $this->prophesize(Connection::class);
|
||||||
|
$this->em->getConnection()->willReturn($conn->reveal());
|
||||||
|
$this->em->clear()->will(function (): void {
|
||||||
|
});
|
||||||
$this->logger = $this->prophesize(LoggerInterface::class);
|
$this->logger = $this->prophesize(LoggerInterface::class);
|
||||||
$this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class);
|
$this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class);
|
||||||
$this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
$this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Core\Middleware;
|
|
||||||
|
|
||||||
use Doctrine\Common\Cache\ArrayCache;
|
|
||||||
use Doctrine\Common\Cache\Cache;
|
|
||||||
use Laminas\Diactoros\Response;
|
|
||||||
use Laminas\Diactoros\ServerRequest;
|
|
||||||
use Laminas\Diactoros\Uri;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
use Shlinkio\Shlink\Core\Middleware\QrCodeCacheMiddleware;
|
|
||||||
|
|
||||||
class QrCodeCacheMiddlewareTest extends TestCase
|
|
||||||
{
|
|
||||||
private QrCodeCacheMiddleware $middleware;
|
|
||||||
private Cache $cache;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->cache = new ArrayCache();
|
|
||||||
$this->middleware = new QrCodeCacheMiddleware($this->cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function noCachedPathFallsBackToNextMiddleware(): void
|
|
||||||
{
|
|
||||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
|
||||||
$delegate->handle(Argument::any())->willReturn(new Response())->shouldBeCalledOnce();
|
|
||||||
|
|
||||||
$this->middleware->process((new ServerRequest())->withUri(new Uri('/foo/bar')), $delegate->reveal());
|
|
||||||
|
|
||||||
$this->assertTrue($this->cache->contains('/foo/bar'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function cachedPathReturnsCacheContent(): void
|
|
||||||
{
|
|
||||||
$isCalled = false;
|
|
||||||
$uri = (new Uri())->withPath('/foo');
|
|
||||||
$this->cache->save('/foo', ['body' => 'the body', 'content-type' => 'image/png']);
|
|
||||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
|
||||||
|
|
||||||
$resp = $this->middleware->process((new ServerRequest())->withUri($uri), $delegate->reveal());
|
|
||||||
|
|
||||||
$this->assertFalse($isCalled);
|
|
||||||
$resp->getBody()->rewind();
|
|
||||||
$this->assertEquals('the body', $resp->getBody()->getContents());
|
|
||||||
$this->assertEquals('image/png', $resp->getHeaderLine('Content-Type'));
|
|
||||||
$delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue