Wrapped logic to track requests to a new RequestTracker service

This commit is contained in:
Alejandro Celaya
2021-07-15 17:23:09 +02:00
parent 32f7b4fbf6
commit 050f83e3bb
10 changed files with 194 additions and 163 deletions

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Action;
use Fig\Http\Message\RequestMethodInterface;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface;
@@ -14,33 +13,24 @@ use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\TrackingOptions;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
use function array_key_exists;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
{
public function __construct(
private ShortUrlResolverInterface $urlResolver,
private VisitsTrackerInterface $visitTracker,
private TrackingOptions $trackingOptions,
private RequestTrackerInterface $requestTracker,
) {
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$identifier = ShortUrlIdentifier::fromRedirectRequest($request);
$query = $request->getQueryParams();
try {
$shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier);
if ($this->shouldTrackRequest($request, $query)) {
$this->visitTracker->track($shortUrl, Visitor::fromRequest($request));
}
$this->requestTracker->trackIfApplicable($shortUrl, $request);
return $this->createSuccessResp($shortUrl, $request);
} catch (ShortUrlNotFoundException) {
@@ -48,17 +38,6 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
}
}
private function shouldTrackRequest(ServerRequestInterface $request, array $query): bool
{
$disableTrackParam = $this->trackingOptions->getDisableTrackParam();
$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(
ShortUrl $shortUrl,
ServerRequestInterface $request,

View File

@@ -8,22 +8,20 @@ use Fig\Http\Message\StatusCodeInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Options;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlRedirectionBuilderInterface;
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
class RedirectAction extends AbstractTrackingAction implements StatusCodeInterface
{
public function __construct(
ShortUrlResolverInterface $urlResolver,
VisitsTrackerInterface $visitTracker,
Options\TrackingOptions $trackingOptions,
RequestTrackerInterface $requestTracker,
private ShortUrlRedirectionBuilderInterface $redirectionBuilder,
private RedirectResponseHelperInterface $redirectResponseHelper,
) {
parent::__construct($urlResolver, $visitTracker, $trackingOptions);
parent::__construct($urlResolver, $requestTracker);
}
protected function createSuccessResp(ShortUrl $shortUrl, ServerRequestInterface $request): Response

View File

@@ -8,30 +8,17 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
class NotFoundTrackerMiddleware implements MiddlewareInterface
{
public function __construct(private VisitsTrackerInterface $visitsTracker)
public function __construct(private RequestTrackerInterface $requestTracker)
{
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var NotFoundType $notFoundType */
$notFoundType = $request->getAttribute(NotFoundType::class);
$visitor = Visitor::fromRequest($request);
if ($notFoundType->isBaseUrl()) {
$this->visitsTracker->trackBaseUrlVisit($visitor);
} elseif ($notFoundType->isRegularNotFound()) {
$this->visitsTracker->trackRegularNotFoundVisit($visitor);
} elseif ($notFoundType->isInvalidShortUrl()) {
$this->visitsTracker->trackInvalidShortUrlVisit($visitor);
}
$this->requestTracker->trackNotFoundIfApplicable($request);
return $handler->handle($request);
}
}

View File

@@ -16,7 +16,7 @@ use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlRedirectionBuilderInterface;
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
use function array_pad;
use function explode;
@@ -27,7 +27,7 @@ class ExtraPathRedirectMiddleware implements MiddlewareInterface
{
public function __construct(
private ShortUrlResolverInterface $resolver,
private VisitsTrackerInterface $visitTracker,
private RequestTrackerInterface $requestTracker,
private ShortUrlRedirectionBuilderInterface $redirectionBuilder,
private RedirectResponseHelperInterface $redirectResponseHelper,
private UrlShortenerOptions $urlShortenerOptions,
@@ -51,8 +51,7 @@ class ExtraPathRedirectMiddleware implements MiddlewareInterface
try {
$shortUrl = $this->resolver->resolveEnabledShortUrl($identifier);
// TODO Track visit
$this->requestTracker->trackIfApplicable($shortUrl, $request);
$longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $query, $extraPath);
return $this->redirectResponseHelper->buildRedirectResponse($longUrl);

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Visit;
use Fig\Http\Message\RequestMethodInterface;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\TrackingOptions;
use function array_key_exists;
class RequestTracker implements RequestTrackerInterface, RequestMethodInterface
{
public function __construct(private VisitsTrackerInterface $visitsTracker, private TrackingOptions $trackingOptions)
{
}
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): void
{
if ($this->shouldTrackRequest($request)) {
$this->visitsTracker->track($shortUrl, Visitor::fromRequest($request));
}
}
public function trackNotFoundIfApplicable(ServerRequestInterface $request): void
{
if (! $this->shouldTrackRequest($request)) {
return;
}
/** @var NotFoundType|null $notFoundType */
$notFoundType = $request->getAttribute(NotFoundType::class);
$visitor = Visitor::fromRequest($request);
if ($notFoundType?->isBaseUrl()) {
$this->visitsTracker->trackBaseUrlVisit($visitor);
} elseif ($notFoundType?->isRegularNotFound()) {
$this->visitsTracker->trackRegularNotFoundVisit($visitor);
} elseif ($notFoundType?->isInvalidShortUrl()) {
$this->visitsTracker->trackInvalidShortUrlVisit($visitor);
}
}
private function shouldTrackRequest(ServerRequestInterface $request): bool
{
$query = $request->getQueryParams();
$disableTrackParam = $this->trackingOptions->getDisableTrackParam();
$forwardedMethod = $request->getAttribute(ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE);
if ($forwardedMethod === self::METHOD_HEAD) {
return false;
}
return $disableTrackParam === null || ! array_key_exists($disableTrackParam, $query);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Visit;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
interface RequestTrackerInterface
{
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): void;
public function trackNotFoundIfApplicable(ServerRequestInterface $request): void;
}