【问题标题】:Slim php: How to handle CORS request without adding "options" routesSlim php:如何在不添加“选项”路由的情况下处理 CORS 请求
【发布时间】:2023-04-09 19:07:01
【问题描述】:

我有一个纤薄的应用程序,我需要配置 CORS,但是当我检查 CORS chapter in the documentation 时,如果我想处理请求方法,我必须向每个端点添加一个新的 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;
});

如何避免每次都创建新路线的痛苦?

【问题讨论】:

    标签: php cors slim


    【解决方案1】:

    经过一番挖掘,我能够通过创建自定义路由中间件来避免为每个路由添加新的处理程序。这个想法是它检查路由是否使用Access-Control-Request-Method 标头中发送的方法解析,如果有,则更改可调用的路由以避免调用实际的处理程序。

    class MyRoutingMiddleware extends \Slim\Middleware\RoutingMiddleware
    {
    
        /**
         * @var \Psr\Http\Message\ResponseFactoryInterface
         */
        protected $responseFactory;
    
        public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
        {
            $request = $this->performRouting($request);
            if ($this->isPreflight($request)) {
                return new Response(200);
            }
            return $handler->handle($request);
        }
    
        protected function isPreflight(ServerRequestInterface $request): bool
        {
            return $request->getMethod() === "OPTIONS" &&
                $request->getHeaderLine("Access-Control-Request-Method") !== '';
        }
    
        protected function resolveRoutingResultsFromRequest(ServerRequestInterface $request): RoutingResults
        {
            $accessControlRequestMethod = $request->getHeaderLine("Access-Control-Request-Method");
            $isPreflight = $this->isPreflight($request);
            return $this->routeResolver->computeRoutingResults(
                $request->getUri()->getPath(),
                $isPreflight ? $accessControlRequestMethod : $request->getMethod()
            );
        }
    }
    
    

    然后我不再使用默认的 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()));
    
    

    更新

    如果在您的代码中抛出异常(例如HttpNotFoundException),则不会进行 CORS 处理,这可能是个问题,尤其是对于 GET 请求(没有预检,但会检查标头),所以我不'不再使用 slim 文档中提供的中间件,而是创建了一个工厂,允许您在路由上单独处理 cors 并使用 ErrorHandler 处理错误(因此无论是否使用 CORS,您的错误输出仍然相同):

    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;
            };
        }
    }
    

    然后你只需要在你的应用程序中添加你的中间件(全局或在单个路由上):

    // 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, [], []));
    
    

    【讨论】:

    • 您是否还测试了 CORS 响应标头中的 NOT_FOUND 和 METHOD_NOT_ALLOWED 错误?
    • 在这种情况下,“OPTIONS”调用返回的是 404 或 415
    • 我猜你的意思是我可以为方法不允许的情况返回允许的方法。不确定 not_found ... @odan
    • 嗨@odan,我更新了我的答案以处理用户生成的HttpException(对于非预检Get请求)。但我还是不明白你对NOT_FOUND 和METHOD_NOT_ALLOWED 的意思。
    猜你喜欢
    • 1970-01-01
    • 2018-07-24
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 2022-08-19
    • 2018-12-13
    • 2014-08-16
    • 2018-03-26
    相关资源
    最近更新 更多