Added support to serve redirects with status 301 and Cache-Control

This commit is contained in:
Alejandro Celaya 2020-06-17 19:01:56 +02:00
parent 186168b26c
commit 68db52679b
6 changed files with 66 additions and 8 deletions

View File

@ -2,6 +2,7 @@
declare(strict_types=1);
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
return [
@ -15,6 +16,8 @@ return [
'anonymize_remote_addr' => true,
'visits_webhooks' => [],
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
'redirect_cache_lifetime' => 30,
],
];

View File

@ -4,11 +4,10 @@ declare(strict_types=1);
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Laminas\ServiceManager\ServiceManager;
use Psr\Container\ContainerInterface;
return (function () {
/** @var ContainerInterface|ServiceManager $container */
/** @var ContainerInterface $container */
$container = include __DIR__ . '/container.php';
$em = $container->get(EntityManager::class);

View File

@ -76,6 +76,7 @@ return [
Service\ShortUrl\ShortUrlResolver::class,
Service\VisitsTracker::class,
Options\AppOptions::class,
Options\UrlShortenerOptions::class,
'Logger_Shlink',
],
Action\PixelAction::class => [

View File

@ -6,12 +6,15 @@ namespace Shlinkio\Shlink\Core;
use Cake\Chronos\Chronos;
use DateTimeInterface;
use Fig\Http\Message\StatusCodeInterface;
use PUGX\Shortid\Factory as ShortIdFactory;
use function sprintf;
const DEFAULT_SHORT_CODES_LENGTH = 5;
const MIN_SHORT_CODES_LENGTH = 4;
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
function generateRandomShortCode(int $length): string

View File

@ -4,18 +4,40 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Action;
use Fig\Http\Message\StatusCodeInterface;
use Laminas\Diactoros\Response\RedirectResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Options;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
use function sprintf;
class RedirectAction extends AbstractTrackingAction
class RedirectAction extends AbstractTrackingAction implements StatusCodeInterface
{
private Options\UrlShortenerOptions $urlShortenerOptions;
public function __construct(
ShortUrlResolverInterface $urlResolver,
VisitsTrackerInterface $visitTracker,
Options\AppOptions $appOptions,
Options\UrlShortenerOptions $urlShortenerOptions,
?LoggerInterface $logger = null
) {
parent::__construct($urlResolver, $visitTracker, $appOptions, $logger);
$this->urlShortenerOptions = $urlShortenerOptions;
}
protected function createSuccessResp(string $longUrl): Response
{
// Return a redirect response to the long URL.
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
return new RedirectResponse($longUrl);
$statusCode = $this->urlShortenerOptions->redirectStatusCode();
$headers = $statusCode === self::STATUS_FOUND ? [] : [
'Cache-Control' => sprintf('private,max-age=%s', $this->urlShortenerOptions->redirectCacheLifetime()),
];
return new RedirectResponse($longUrl, $statusCode, $headers);
}
protected function createErrorResp(ServerRequestInterface $request, RequestHandlerInterface $handler): Response

View File

@ -6,20 +6,50 @@ namespace Shlinkio\Shlink\Core\Options;
use Laminas\Stdlib\AbstractOptions;
use function Functional\contains;
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
class UrlShortenerOptions extends AbstractOptions
{
protected $__strictMode__ = false; // phpcs:ignore
private bool $validateUrl = true;
private int $redirectStatusCode = DEFAULT_REDIRECT_STATUS_CODE;
private int $redirectCacheLifetime = 30;
public function isUrlValidationEnabled(): bool
{
return $this->validateUrl;
}
protected function setValidateUrl(bool $validateUrl): self
protected function setValidateUrl(bool $validateUrl): void
{
$this->validateUrl = $validateUrl;
return $this;
}
public function redirectStatusCode(): int
{
return $this->redirectStatusCode;
}
protected function setRedirectStatusCode(int $redirectStatusCode): void
{
$this->redirectStatusCode = $this->normalizeRedirectStatusCode($redirectStatusCode);
}
private function normalizeRedirectStatusCode(int $statusCode): int
{
return contains([301, 302], $statusCode) ? $statusCode : 302;
}
public function redirectCacheLifetime(): int
{
return $this->redirectCacheLifetime;
}
protected function setRedirectCacheLifetime(int $redirectCacheLifetime): void
{
$this->redirectCacheLifetime = $redirectCacheLifetime;
}
}