Prechádzať zdrojové kódy

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

Alejandro Celaya 3 rokov pred
rodič
commit
68db52679b

+ 3 - 0
config/autoload/url-shortener.global.php

@@ -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,
     ],
 
 ];

+ 1 - 2
config/cli-config.php

@@ -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);
 

+ 1 - 0
module/Core/config/dependencies.config.php

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

+ 3 - 0
module/Core/functions/functions.php

@@ -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

+ 26 - 4
module/Core/src/Action/RedirectAction.php

@@ -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

+ 32 - 2
module/Core/src/Options/UrlShortenerOptions.php

@@ -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;
     }
 }