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

Posted

技术标签:

【中文标题】Slim php:如何在不添加“选项”路由的情况下处理 CORS 请求【英文标题】:Slim php: How to handle CORS request without adding "options" routes 【发布时间】:2021-08-14 14:15:44 【问题描述】:

我有一个纤薄的应用程序,我需要配置 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;
);

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

【问题讨论】:

【参考方案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_FOUNDMETHOD_NOT_ALLOWED 的意思。

以上是关于Slim php:如何在不添加“选项”路由的情况下处理 CORS 请求的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Slim框架支持API版本控制

如何在不从当前活动选项卡中获取焦点的情况下将子窗口添加到 QMdiArea(设置为 TAB 模式)?

如何在不使用循环的情况下在 PHP 中向多维添加新的键和值......?

如何在不为 Java Bridge 添加 tomcat 服务器的情况下将 Java 后端添加到现有 PHP 站点?

如何在不使用 Mailparse PHP 扩展的情况下解析电子邮件

如何在不修改 HTML 的情况下在 jQuery 中添加指向伪元素的链接? - 更新 > Functions.php