Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get middlewares stack in ErrorHandler #2999

Closed
ybelenko opened this issue Sep 16, 2020 · 10 comments
Closed

Get middlewares stack in ErrorHandler #2999

ybelenko opened this issue Sep 16, 2020 · 10 comments

Comments

@ybelenko
Copy link

Is there any elegant way to retrieve middleware stack or at least container in ErrorHandler?
It's related to CORS middleware, it needs to be applied after exception catch. I want to override respond() method of ErrorHandler retrieve middlewares array, find CORS middleware and apply it to $request.

Of course I can add $container argument to extended ErrorHandler class, then I can retrieve CORS settings, instantiate new CORS middleware handler and execute it, but it seems like not a proper way to achieve this.

I think that ErrorHandler should have access to middlewares as it important part of the application and business logic.

@l0gicgate
Copy link
Member

I don't think that the middleware stack should be accessed by anything else but the MiddlewareDispatcher.

The solution to your problem is to create a custom ResponseEmitter as I did in Slim-Skeleton.

This emitter can be modularly used in multiple places within your application. I'm aware that the typical way to handle CORS is through middleware but that doesn't make it the only way.

You can see the usage in the index.php from Slim-Skeleton.

Hope that helps.

@ybelenko
Copy link
Author

Hmmm, what about an option in next major release to apply middleware even when exception catched? Something like:

$app->add($middleware, $applyOnExceptionCatch);

@l0gicgate
Copy link
Member

@ybelenko it's not the right way to achieve what you're trying to achieve. Again, a custom ResponseEmitter is your best bet.

@ybelenko
Copy link
Author

I've checked ResponseEmitter and maybe you right. As soon as CORS are headers which should be applied absolutely in all cases so it's kind of middleware for all routes even when exception catched. The only inconvenient thing that I cannot reach app $container in ResponseEmitter. And I guess users expects to store CORS config in settings($container).

@RyanNerd
Copy link
Contributor

CORS is a pain -- I've put my CORS code directly in index.php before Slim is invoked. See: https://github.com/RyanNerd/rxchart-app/blob/master/public/index.php

        $requestMethod = $_SERVER['REQUEST_METHOD'];

        // Is this a pre-flight request (the request method is OPTIONS)? Then start output buffering.
        if ($requestMethod === 'OPTIONS') {
            ob_start();
        }

        // Allow for all origins and credentials. Also allow GET, POST, PATCH, DELETE and OPTIONS request verbs
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Allow-Headers: Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
        header('Access-Control-Allow-Methods: GET, POST, PATCH, OPTIONS, DELETE');

        // If this is a pre-flight request (the request method is OPTIONS)? Then flush the output buffer and exit.
        if ($requestMethod === 'OPTIONS') {
            ob_end_flush();
            exit();
        }

       // Code that starts the Slim app.

HTH

@ybelenko
Copy link
Author

@RyanNerd thanks, it's obvious solution. I was looking for more elegant way... And I don't want to reinvent the wheel when I can use stable CORS package, unfortunately the most popular one implemented as middleware - tuupola/cors-middleware.

@jacekkarczmarczyk
Copy link

It's just a wrapper for neomerx/cors-psr7

@ybelenko
Copy link
Author

That's my custom emitter(not really elegant) which solves CORS with neomerx/cors-psr7 v2.0.2:

<?php declare(strict_types=1);

namespace App;

use Neomerx\Cors\Contracts\AnalysisResultInterface;
use Neomerx\Cors\Contracts\AnalyzerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Exception\HttpBadRequestException;
use Slim\Middleware\ErrorMiddleware;
use Slim\ResponseEmitter as SlimResponseEmitter;

/**
 * @link https://github.com/slimphp/Slim-Skeleton/blob/master/src/Application/ResponseEmitter/ResponseEmitter.php
 */
class ResponseEmitter extends SlimResponseEmitter
{
    /**
     * @var ServerRequestInterface
     */
    protected $request;

    /**
     * @var ErrorMiddleware
     */
    protected $errorMiddleware;

    /**
     * @var AnalyzerInterface
     */
    protected $analyzer;

    /**
     * Custom emitter
     * 
     * @param ServerRequestInterface $request           Request
     * @param ErrorMiddleware        $errorMiddleware   Error middleware
     * @param AnalyzerInterface      $analyzer          Cors analyzer
     * @param int                    $responseChunkSize
     */
    public function __construct(
        ServerRequestInterface $request,
        ErrorMiddleware $errorMiddleware,
        AnalyzerInterface $analyzer,
        int $responseChunkSize = 4096
    ) {
        parent::__construct($responseChunkSize);
        $this->request = $request;
        $this->errorMiddleware = $errorMiddleware;
        $this->analyzer = $analyzer;
    }

    /**
     * {@inheritDoc}
     */
    public function emit(ResponseInterface $response): void
    {
        // slightly modified CORS handler from package documentation example
        // @link https://github.com/neomerx/cors-psr7#sample-usage
        $cors = $this->analyzer->analyze($this->request);
        $exception = null;

        switch ($cors->getRequestType()) {
            case AnalysisResultInterface::ERR_NO_HOST_HEADER:
                $exception = new HttpBadRequestException($this->request, 'CORS: No Host header in request');
                break;
            case AnalysisResultInterface::ERR_ORIGIN_NOT_ALLOWED:
                $exception = new HttpBadRequestException($this->request, 'CORS: Request origin is not allowed');
                break;
            case AnalysisResultInterface::ERR_METHOD_NOT_SUPPORTED:
                $exception = new HttpBadRequestException($this->request, 'CORS: Request method is not supported');
                break;
            case AnalysisResultInterface::ERR_HEADERS_NOT_SUPPORTED:
                $exception =  new HttpBadRequestException($this->request, 'CORS: Request headers are not supported');
                break;
            case AnalysisResultInterface::TYPE_REQUEST_OUT_OF_CORS_SCOPE:
                // do nothing
                break;
            case AnalysisResultInterface::TYPE_PRE_FLIGHT_REQUEST:
            default:
                // actual CORS request
                $corsHeaders = $cors->getResponseHeaders();

                // add CORS headers to Response $response
                foreach ($corsHeaders as $name => $value) {
                    $response = $response->withHeader($name, $value);
                }
        }

        if ($exception instanceof \Throwable) {
            $response = $this->errorMiddleware->handleException($this->request, $exception);
        }

        parent::emit($response);
    }
}

I don't like that I have to pass $request and $errorMiddleware into constructor, but don't see any workarounds yet.

@ybelenko
Copy link
Author

Sorry to bothering you guys again. I'm a bit confused right now. Just noticed this Cook Book - Setting up CORS. It looks fresh to me. Can we say that handling CORS via middleware is recommended way right now? Will your doc example add CORS headers when exception thrown?

@l0gicgate
Copy link
Member

@ybelenko the best way to do it is via using a custom ResponseEmitter like in the skeleton:
https://github.com/slimphp/Slim-Skeleton/blob/master/src/Application/ResponseEmitter/ResponseEmitter.php

https://github.com/slimphp/Slim-Skeleton/blob/master/public/index.php#L79-L82

ybelenko added a commit to ybelenko/openapi-generator that referenced this issue Mar 22, 2022
While Slim4 doc applies CORS headers via middleware but their code team
member recommends to use custom response emitter.

Ref: slimphp/Slim#2999 (comment)
wing328 pushed a commit to OpenAPITools/openapi-generator that referenced this issue Mar 23, 2022
* Add lazy CORS implementation

While Slim4 doc applies CORS headers via middleware but their code team
member recommends to use custom response emitter.

Ref: slimphp/Slim#2999 (comment)

* Refresh samples
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants