Middlewares

Resumen

Esta página explica los middlewares: la lógica que se ejecuta antes y después de cada solicitud, sus hooks, cómo escribir uno y registrarlo en la clase Application o como middleware global, y cómo leer anotaciones personalizadas. Está dirigida a quienes desarrollan apps.

Fuente: documentación de Nextcloud, «developer_manual/basics/middlewares», versión 3ad9158, licencia CC BY 3.0; traducida y adaptada para APS Conecta Gestión.

Un middleware es lógica que se ejecuta antes y después de cada solicitud, y está modelado a partir del sistema de middleware de Django. Ofrece los siguientes hooks:

  • beforeController: se ejecuta antes de que se ejecute un método del controlador. Permite conectar comprobaciones o lógica adicionales antes de ese método, como, por ejemplo, comprobaciones de seguridad

  • afterException: se ejecuta cuando el método beforeController o el propio método del controlador lanza una excepción. Se pide a los middlewares, en orden inverso, que gestionen la excepción y devuelvan una respuesta. Si el middleware no puede gestionar la excepción, la vuelve a lanzar

  • afterController: se ejecuta después de una llamada correcta a un método del controlador y permite manipular un objeto Response. Los middlewares se ejecutan en orden inverso

  • beforeOutput: se ejecuta después de que el objeto de respuesta se haya renderizado y permite manipular el texto de salida. Los middlewares se ejecutan en orden inverso

Para generar un middleware propio, basta con heredar de la clase Middleware y sobrescribir los métodos que se quieran usar.

<?php

namespace OCA\MyApp\Middleware;

use \OCP\AppFramework\Middleware;


class CensorMiddleware extends Middleware {

    /**
     * this replaces "bad words" with "********" in the output
     */
    public function beforeOutput($controller, $methodName, $output): string {
        return str_replace('bad words', '********', $output);
    }

}

El middleware puede registrarse en la clase Application de la app:

Archivo lib/AppInfo/Application.php:

<?php

declare(strict_types=1);

namespace OCA\MyApp\AppInfo;

use OCA\MyApp\Middleware\CensorMiddleware;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;

class Application extends App implements IBootstrap {

    public function __construct() {
        parent::__construct('myapp');
    }

    public function register(IRegistrationContext $context): void {
        $context->registerMiddleware(CensorMiddleware::class);
    }

    public function boot(IBootContext $context): void {}

}

Middlewares globales

Nuevo en la versión 26.

De forma predeterminada, un middleware registrado solo intercepta las solicitudes de su propia app. Para que un middleware sea global y se active también para el middleware de otras apps, se agrega true como segundo argumento de la llamada a registerMiddleware:

Archivo lib/AppInfo/Application.php:

<?php

declare(strict_types=1);

namespace OCA\MyApp\AppInfo;

use OCA\MyApp\Middleware\MonitoringMiddleware;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;

class Application extends App implements IBootstrap {

    public function __construct() {
        parent::__construct('myapp');
    }

    public function register(IRegistrationContext $context): void {
        $context->registerMiddleware(MonitoringMiddleware::class, true);
    }

    public function boot(IBootContext $context): void {}

}

Registro en el contenedor de inyección de dependencias

Obsoleto desde la versión 20.

El middleware también puede agregarse con el método registerMiddleware del contenedor:

Archivo lib/AppInfo/Application.php:

<?php

namespace OCA\MyApp\AppInfo;

use OCP\AppFramework\App;
use OCP\IServerContainer;
use OCA\MyApp\Middleware\CensorMiddleware;

class MyApp extends App {

    public function __construct(array $urlParams = []) {
        parent::__construct('myapp', $urlParams);

        $container = $this->getContainer();

        // executed in the order that it is registered
        $container->registerMiddleware(CensorMiddleware::class);
    }
}

Nota

¡El orden es importante! El middleware que se registra primero se ejecuta primero en el método beforeController. Para todos los demás hooks, el orden se invierte; es decir, si un middleware se registra primero, se ejecuta último.

Analizar anotaciones

A veces es útil ejecutar código de forma condicional antes o después de un método del controlador. Esto puede hacerse definiendo anotaciones personalizadas. Un ejemplo sería agregar un método de autenticación personalizado o simplemente agregar una cabecera adicional a la respuesta. Para acceder a las anotaciones analizadas, se inyecta la clase ControllerMethodReflector:

<?php

namespace OCA\MyApp\Middleware;

use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\AppFramework\Http\Response;

class HeaderMiddleware extends Middleware {

  private $reflector;

  public function __construct(IControllerMethodReflector $reflector) {
      $this->reflector = $reflector;
  }

  /**
   * Add custom header if @MyHeader is used
   */
  public function afterController($controller, $methodName, Response $response): Response {
      if($this->reflector->hasAnnotation('MyHeader')) {
          $response->addHeader('My-Header', 3);
      }
      return $response;
  }
}

Nota

Una anotación siempre empieza con una letra mayúscula