Apitte – Middlewares – logování request a repsponse

Croc
Člen | 270
+
0
-

Zdravím,
mám takový problém. Vytvářím nové REST API pomocí Apitte s použitím Contributte\Middlewares.
Jen poznámka, middleware používám pouze pro toto API. V aplikaci ne.

Middleware mám pro authentizaci pomocí apiKey a chci ho také použít pro logování každého requestu a response.
Pro logování requestů mi funguje, ale mám problém s logováním response.

config.neon

middlewares:
	middlewares:
		- Contributte\Middlewares\TracyMiddleware
		- Contributte\Middlewares\AutoBasePathMiddleware
		- Apitte\Middlewares\ApiMiddleware
		- App\Middlewares\ApiKeyAuthenticationMiddleware
		- App\Middlewares\LogApiRequestMiddleware
		- App\Middlewares\LogApiResponseMiddleware

Aktuální stav:

  1. Provolání API
  2. ApiKeyAuthenticationMiddleware
  3. LogApiRequestMiddleware
  4. LogApiResponseMiddleware
  5. zpracování požadavku
  6. vrácení odpovědi

Požadovaný stav:

  1. Provolání API
  2. ApiKeyAuthenticationMiddleware
  3. LogApiRequestMiddleware
  4. zpracování požadavku
  5. LogApiResponseMiddleware
  6. vrácení odpovědi

Problém je, že nevím jak nadefinovat LogApiResponseMiddleware, aby se volal pouze pro response. Je to vůbec možné? Nejsem si jistý, jestli jsem funci Middleware pochopil správně, ale podle obrázku v dokumentaci Contributte\Middlewares by to mělo jít.

Případně jaká je jiná alternativa? Cílem je, aby se logování provádělo automaticky pro všechny API requesty a response.

Felix
Nette Core | 1189
+
0
-

Ahoj, ukaz jak vypada logovaci middleware.

Croc
Člen | 270
+
0
-

REST API controller

<?php

namespace App\ApiRest\V1\Controllers;

use Apitte\Core\Annotation\Controller\ControllerPath;
use Apitte\Core\Annotation\Controller\Method;
use Apitte\Core\Annotation\Controller\Path;
use Apitte\Core\Annotation\Controller\RequestParameter;
use Apitte\Core\Annotation\Controller\RequestParameters;
use Apitte\Core\Annotation\Controller\Negotiations;
use Apitte\Core\Annotation\Controller\Negotiation;
use Apitte\Negotiation\SuffixNegotiator;
use Apitte\Negotiation\DefaultNegotiator;
use Apitte\Core\Annotation\Controller\Responses;
use Apitte\Core\Annotation\Controller\Response;
use Apitte\Core\Annotation\Controller\Tag;
use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use Apitte\Negotiation\Http\ArrayEntity;
use Apitte\OpenApi\ISchemaBuilder;
use App\Exception\LookupNotFoundOrEmptyException;
use App\Model\ApiModel;
use App\Services\LookupService;
use Log\LogService;
use Psr\Http\Message\ServerRequestInterface;

/**
 * Class LookupController
 * @package App\ApiRest\V1\Controllers
 *
 * @ControllerPath("/lookup")
 * @Tag(name="Lookup")
 */
class LookupController extends BaseV1Controller
{

    /**
     * @var ISchemaBuilder
     */
    private $schemaBuilder;

    /**
     * @var LookupService
     */
    private $lookupService;

    /**
     * @var LogService
     */
    private $logService;

    /**
     * @var ApiModel
     */
    private $apiManager;

    /* ----------------- */

    /**
     * LookupController constructor.
     * @param ISchemaBuilder $schemaBuilder
     * @param LookupService  $lookupService
     * @param LogService     $logService
     * @param ApiModel       $apiManager
     */
    public function __construct(ISchemaBuilder $schemaBuilder, LookupService $lookupService, LogService $logService, ApiModel $apiManager)
    {
        $this->schemaBuilder = $schemaBuilder;
        $this->lookupService = $lookupService;
        $this->logService = $logService;
        $this->apiManager = $apiManager;
    }

    /**
     * @param ApiRequest  $request
     * @param ApiResponse $response
     * @return ApiResponse
     *
     * @Path("/{lookup}")
     * @Method("GET")
     * @RequestParameters({
     *     @RequestParameter(name="lookup", type="string", in="path", required=true, description="Name of lookup."),
     *     @RequestParameter(name="enabled-only", type="bool", in="query", required=false, description="Get only enabled values from lookup."),
     *     @RequestParameter(name="language", type="string", in="header", required=false, description="Language for description lookup values.")
     * }),
     * @Negotiations({
     * 		@Negotiation(default = true, suffix = "json"),
     * 		@Negotiation(default = false, suffix = "xml")
     * }),
     * @Responses({
     *     @Response(code="200", description="Success"),
     *     @Response(code="404", description="Not found")
     * })
     *
     */
    public function lookup(ApiRequest $request, ApiResponse $response): ApiResponse
    {
        $params = $request->getParameters();
        $lookup = $params['lookup'];

        if (strpos(mb_strtolower($lookup), 'lookup') === false) {
            return $response
                ->withStatus(ApiResponse::S404_NOT_FOUND)
                ->withEntity(ArrayEntity::from(['Lookup not found']));
        }

        try {
            $data = $this->lookupService->getLookup($lookup)->getItems();

        } catch (LookupNotFoundOrEmptyException $exception) {
            return $response
                ->withStatus(ApiResponse::S404_NOT_FOUND)
                ->withEntity(ArrayEntity::from(['Lookup not found']));

        } catch (\Exception $exception) {
            $this->logService->saveExceptionToFile($exception);
            return $response
                ->withStatus(ApiResponse::S500_INTERNAL_SERVER_ERROR)
                ->withEntity(ArrayEntity::from(['Unknown server error']));
        }

        return $response
            ->withStatus(ApiResponse::S200_OK)
            ->withEntity(ArrayEntity::from($data));
    }
}

Middleware pro logování request

<?php declare(strict_types = 1);

namespace App\Middlewares;

use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use App\Model\ApiModel;
use App\Model\NoUserException;
use App\Services\LookupService;
use Contributte\Middlewares\IMiddleware;
use Log\LogService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use stdClass;

/**
 * Class LogApiRequestMiddleware
 * @package App\Middlewares
 */
final class LogApiRequestMiddleware implements IMiddleware
{

    /**
     * @var ApiModel
     */
    private $apiModel;

    /**
     * @var LogService
     */
    private $logService;

    /**
     * ApiKeyAuthenticationMiddleware constructor.
     * @param ApiModel   $apiModel
     * @param LogService $logService
     */
    public function __construct(ApiModel $apiModel, LogService $logService)
    {
        $this->apiModel = $apiModel;
        $this->logService = $logService;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface      $response
     * @param callable               $next
     * @return ResponseInterface
     * @throws \App\Repository\TransactionFailException
     * @throws \Dibi\DriverException
     * @throws \Dibi\Exception
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
    {
        if ($request->getUri()->getPath() === '/api-rest/v1/') {
            return $next($request, $response);
        }

        $id = $this->apiModel->logRestApiRequest($request);
        return $next($request->withAttribute('id_request', $id), $response);

    }
}

Middleware pro logování response

<?php declare(strict_types = 1);

namespace App\Middlewares;

use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use App\Model\ApiModel;
use Contributte\Middlewares\IMiddleware;
use Log\LogService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
 * Class LogApiResponseMiddleware
 * @package App\Middlewares
 */
final class LogApiResponseMiddleware implements IMiddleware
{

    /**
     * @var ApiModel
     */
    private $apiModel;

    /**
     * @var LogService
     */
    private $logService;

    /**
     * ApiKeyAuthenticationMiddleware constructor.
     * @param ApiModel   $apiModel
     * @param LogService $logService
     */
    public function __construct(ApiModel $apiModel, LogService $logService)
    {
        $this->apiModel = $apiModel;
        $this->logService = $logService;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface      $response
     * @param callable               $next
     * @return ResponseInterface
     * @throws \Dibi\Exception
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
    {
        $this->apiModel->logRestApiResponse($request, $response);
        return $next($request, $response);
    }
}
public function logRestApiResponse(ServerRequestInterface $request, ResponseInterface $response): void
{
    $body = json_encode(json_decode($response->getBody()->getContents(), true));

    $values = [
        'CODE' => $response->getStatusCode(),
        'BODY_1' => mb_substr($body, 0, 3999),
        'BODY_2' => mb_substr($body, 4000, 3999),
        'ID_LOG_REST_API_REQUEST' => $request->getAttributes()['id_request'],
    ];

    $this->db->query("INSERT INTO LOG_REST_API_RESPONSE %v", $values);
}

Moc děkuju

Editoval Croc (26. 7. 2019 10:07)

Croc
Člen | 270
+
0
-

Někdo nějaký nápad? Moc děkuju

EDIT: Doplnil jsem v předešlém příspěvku celé API a middleware pro logování request a response. Config v prvním příspěvku platí.

Editoval Croc (26. 7. 2019 10:10)

Felix
Nette Core | 1189
+
+1
-

Ahoj, zkus tohle:

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
{
    if ($request->getUri()->getPath() === '/api-rest/v1/') {
        return $next($request, $response);
    }

    $id = $this->apiModel->logRestApiRequest($request);
    $res = $next($request->withAttribute('id_request', $id), $response);
		$this->apiModel->logRestApiResponse($res);

		return $res;
}
Croc
Člen | 270
+
+1
-

@Felix Ahá… Super, díky moc :) Už to loguje ve správné posloupnosti.