简体   繁体   English

Slim php:如何在不添加“选项”路由的情况下处理 CORS 请求

[英]Slim php: How to handle CORS request without adding "options" routes

I have a slim application and I need to configure CORS, but when I check theCORS chapter in the documentation , if I want to handle request methods, I have to add a new options route to every endpoint (excerpt from the docs):我有一个苗条的应用程序,我需要配置 CORS,但是当我查看文档中的 CORS 章节时,如果我想处理请求方法,我必须添加一个新的options路由到每个端点(摘自文档):

$app->add(function (Request $request, RequestHandlerInterface $handler): Response {
    $routeContext = RouteContext::fromRequest($request);
    $routingResults = $routeContext->getRoutingResults();
    $methods = $routingResults->getAllowedMethods();
    $requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');

    $response = $handler->handle($request);

    $response = $response->withHeader('Access-Control-Allow-Origin', '*');
    $response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
    $response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);

    // Optional: Allow Ajax CORS requests with Authorization header
    // $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');

    return $response;
});

// The RoutingMiddleware should be added after our CORS middleware so routing is performed first
$app->addRoutingMiddleware();

// The routes
$app->get('/api/v0/users', function (Request $request, Response $response): Response {
    $response->getBody()->write('List all users');

    return $response;
});

// Allow preflight requests
// Due to the behaviour of browsers when sending a request,
// you must add the OPTIONS method. Read about preflight.
$app->options('/api/v0/users', function (Request $request, Response $response): Response {
    // Do nothing here. Just return the response.
    return $response;
});

How can I avoid the pain of creating a new route everytime?如何避免每次创建新路线的痛苦?

After a bit of digging I was able to avoid adding a new handler for every route by creating a custom routing middleware.经过一番挖掘,我能够通过创建自定义路由中间件来避免为每条路由添加新的处理程序。 The idea is that it checks if the route resolves with the method sent in the Access-Control-Request-Method header, and if any, change the route callable to avoid calling the actual handler.这个想法是它检查路由是否使用Access-Control-Request-Method header 中发送的方法解析,如果有,更改可调用的路由以避免调用实际的处理程序。

class MyRoutingMiddleware extends \Slim\Middleware\RoutingMiddleware
{
    public function performRouting(ServerRequestInterface $request): ServerRequestInterface
    {
        $request = $request->withAttribute(RouteContext::ROUTE_PARSER, $this->routeParser);
        $accessControlRequestMethod = $request->getHeaderLine("Access-Control-Request-Method");
        $isPreflight = $request->getMethod() === "OPTIONS" && $accessControlRequestMethod !== "";
        $routingResults = $this->resolveRoutingResultsFromRequest($request);
        $routeStatus = $routingResults->getRouteStatus();
        $request = $request->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults);
        switch ($routeStatus) {
            case RoutingResults::FOUND:
                $routeArguments = $routingResults->getRouteArguments();
                $routeIdentifier = $routingResults->getRouteIdentifier() ?? '';
                $route = $this->routeResolver
                    ->resolveRoute($routeIdentifier)
                    ->prepare($routeArguments);
                if ($isPreflight) {
                    $route->setCallable(function (ServerRequestInterface $request, ResponseInterface $response, array $arguments) {
                        return $response;
                    });
                }
                return $request->withAttribute(RouteContext::ROUTE, $route);

            case RoutingResults::NOT_FOUND:
                throw new HttpNotFoundException($request);

            case RoutingResults::METHOD_NOT_ALLOWED:
                $exception = new HttpMethodNotAllowedException($request);
                $exception->setAllowedMethods($routingResults->getAllowedMethods());
                throw $exception;

            default:
                throw new RuntimeException('An unexpected error occurred while performing routing.');
        }
    }

    protected function resolveRoutingResultsFromRequest(ServerRequestInterface $request): RoutingResults
    {
        $accessControlRequestMethod = $request->getHeaderLine("Access-Control-Request-Method");
        $isPreflight = $request->getMethod() === "OPTIONS" && $accessControlRequestMethod !== "";
        return $this->routeResolver->computeRoutingResults(
            $request->getUri()->getPath(),
            $isPreflight ? $accessControlRequestMethod : $request->getMethod()
        );
    }

}

And then I don't use the default RoutingMiddleware anymore:然后我不再使用默认的 RoutingMiddleware:

//add cors middleware globally
$app->add(function (Request $request, RequestHandlerInterface $handler): Response {
    $routeContext = RouteContext::fromRequest($request);
    $routingResults = $routeContext->getRoutingResults();
    $methods = $routingResults->getAllowedMethods();
    $requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');

    $response = $handler->handle($request);

    $response = $response->withHeader('Access-Control-Allow-Origin', '*');
    $response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
    $response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);

    // Optional: Allow Ajax CORS requests with Authorization header
    // $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');

    return $response;
});
// The routes
$app->get('/api/v0/users', function (Request $request, Response $response): Response {
    $response->getBody()->write('List all users');

    return $response;
});
// No need to add the $app->options(...) anymore !
//use custom routing middleware
$app->add(new MyRoutingMiddleware($app->getRouteResolver(), $app->getRouteCollector()->getRouteParser()));

Update更新

There is no CORS handling if an exception is thrown in your code ( HttpNotFoundException , for instance) and that can be a problem, especially with GET requests (no preflight, but headers are checked), so I don't use the middleware provided in the slim documentation anymore but instead I created a factory that allows you to handle cors individually on your routes and handle error using the ErrorHandler (so your error output is still the same with or without CORS):如果在您的代码中抛出异常(例如HttpNotFoundException ),则没有 CORS 处理,这可能是一个问题,尤其是GET请求(没有预检,但检查了标头),所以我不使用提供的中间件不再使用纤薄的文档,而是创建了一个工厂,允许您在路线上单独处理 cors 并使用 ErrorHandler 处理错误(因此,无论是否使用 CORS,您的错误 output 仍然相同):

class CorsMiddlewareFactory
{
    /**
     * @var \Slim\Handlers\ErrorHandler
     */
    protected $errorHandler;

    /**
     * @var bool
     */
    protected $logErrorDetails;

    /**
     * @var bool
     */
    protected $logErrors;

    /**
     * @var bool
     */
    protected $displayErrorDetails;

    /**
     * CorsMiddlewareFactory constructor.
     * @param \Slim\Handlers\ErrorHandler $errorHandler
     * @param bool $logErrorDetails
     * @param bool $logErrors
     * @param bool $displayErrorDetails
     */
    public function __construct(ErrorHandler $errorHandler, bool $logErrorDetails, bool $logErrors, bool $displayErrorDetails)
    {
        $this->errorHandler = $errorHandler;
        $this->logErrorDetails = $logErrorDetails;
        $this->logErrors = $logErrors;
        $this->displayErrorDetails = $displayErrorDetails;
    }


    public function create(array $origin = [], bool $allowCredentials = false, ?array $allowedHeaders = [], array $exposedHeaders = [])
    {
        $errorHandler = $this->errorHandler;
        $logErrorDetails = $this->logErrorDetails;
        $logErrors = $this->logErrors;
        $displayErrorDetails = $this->displayErrorDetails;
        return function (ServerRequestInterface $request, RequestHandlerInterface $handler)
        use ($allowCredentials, $allowedHeaders, $origin, $exposedHeaders, $errorHandler, $logErrorDetails, $logErrors, $displayErrorDetails): ResponseInterface {
            $routeContext = RouteContext::fromRequest($request);
            $routingResults = $routeContext->getRoutingResults();
            $methods = $routingResults->getAllowedMethods();
            if ($allowedHeaders === null) {
                $allowedHeaders = $request->getHeader('Access-Control-Request-Headers');
            }
            $isPreflight = $request->getMethod() === "OPTIONS" && !empty($request->getHeader("Access-Control-Request-Method"));
            try {
                $response = $handler->handle($request);
            } catch (\Throwable $exception) {
                $response = ($errorHandler)($request, $exception, $displayErrorDetails, $logErrors, $logErrorDetails);
            }
            $headers=[
                'Access-Control-Allow-Origin'=> implode(',', $origin),
                'Access-Control-Allow-Methods'=> implode(',', $methods),
                'Access-Control-Allow-Headers'=> implode(',', $allowedHeaders),
                'Access-Control-Allow-Credentials'=> $allowCredentials ? 'true' : 'false'
            ];
            foreach ($headers as $name => $value) {
                if(!$response->hasHeader($name)){
                    $response=$response->withHeader($name,$value);
                }
            }
            if ($isPreflight && !$response->hasHeader('Access-Control-Exposed-Headers')) {
                $response = $response->withHeader('Access-Control-Exposed-Headers', implode(',', $exposedHeaders));
            }
            return $response;
        };
    }
}

Then you just need to add your middlewares in your application (globally or on individual routes):然后你只需要在你的应用程序中添加你的中间件(全局或在单个路由上):

// Create Error Handler
$errorHandler = new ErrorHandler($app->getCallableResolver(), $$app->getResponseFactory());
// handle CORS requests
$corsFactory = new CorsMiddlewareFactory($errorHandler, false, true, true);
// allow all by default, YOLO !
$app->add($corsFactory->create(['*'], true, ['*'], ['*']));
// can be overriden on invidual routes as well
$app->get("/my/route",function($request,$response){
    return $response;
})->add($corsFactory->create(['http://google.fr'], false, [], []));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM