hyperf跨域问题

2024年4月25日10:11:30

前段时间写完了hyperf的cms之后,回头写hyperf的一些文章或者笔记,发现hyperf和laravel真的很像,又有swoole的协程使用,真的很舒服,还有微服务。

官方推荐的是方式就是使用中间件,但是注意,你在路由上使用的时候,中间件是无法实现跨域的,因为在路由组件会抛出一个HttpException,请求OPTIONS返回405状态码。

方案一:
https://hyperf.wiki/3.1/#/zh-cn/middleware/middleware?id=跨域中间件

<?php

declare(strict_types=1);

namespace App\Middleware;

use Hyperf\Context\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CorsMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
         $response = Context::get(ResponseInterface::class);
        $response = $response->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Credentials', 'true')
            ->withHeader('Access-Control-Allow-Methods', '*')
            ->withHeader('Access-Control-Allow-Headers', '*');

        Context::set(ResponseInterface::class, $response);

        if ($request->getMethod() == 'OPTIONS') {
            return $response->withStatus(204)->withBody(new SwooleStream(''));
        }

        return $handler->handle($request);
    }
}

新建一个CorsMiddleware 放在hyperf/config/autoload/middlewares.php的里面加入中间件

方案二:在nginx的反向代理利加入跨域配置

location / {
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods *;
    add_header Access-Control-Allow-Headers *;

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

方案三:如果你现在路由上使用跨域中间件
新建一个hyperf/app/Exception/Handler/HttpExceptionHandler.php
把文件加入到 hyperf/config/autoload/exceptions.php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
return [
    'handler' => [
        'http' => [
            Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
//            App\Exception\Handler\HttpExceptionHandler::class,
            App\Exception\Handler\AppExceptionHandler::class,
        ],
    ],
];

替换Hyperf的Handler

declare(strict_types=1);

namespace App\Exception\Handler;

use Hyperf\Context\Context;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\HttpMessage\Exception\HttpException;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Swow\Psr7\Message\ResponsePlusInterface;
use Throwable;

class HttpExceptionHandler extends ExceptionHandler
{
    public function __construct(protected StdoutLoggerInterface $logger, protected FormatterInterface $formatter)
    {
    }

    /**
     * Handle the exception, and return the specified result.
     * @param HttpException $throwable
     */
    public function handle(Throwable $throwable, ResponsePlusInterface $response)
    {
        $this->logger->debug($this->formatter->format($throwable));

        $this->stopPropagation();

        $request = Context::get(ServerRequestInterface::class);
        if ($request->getMethod() == 'OPTIONS') {
            return $response->setStatus(204)
                ->withHeader('Access-Control-Allow-Origin', '*')
                ->withHeader('Access-Control-Allow-Credentials', 'true')
                ->withHeader('Access-Control-Allow-Headers', '*')
                ->withHeader('Access-Control-Allow-Methods', '*')
                ->setBody(new SwooleStream(''));
        }

        return $response->setStatus($throwable->getStatusCode())->setBody(new SwooleStream($throwable->getMessage()));
    }

    /**
     * Determine if the current exception handler should handle the exception.
     *
     * @return bool If return true, then this exception handler will handle the exception,
     *              If return false, then delegate to next handler
     */
    public function isValid(Throwable $throwable): bool
    {
        return $throwable instanceof HttpException;
    }
}

这里稍微改一下也可以实现跨域

方案四:在路由上加上OPTIONS,也是需要配合中间件跨域,才能使用的,这样就可以在路由上使用跨域中间件

Router::addGroup('/api/admin', function () {

    Router::post('/login', [\App\Controller\Admin\IndexController::class, 'login']);
    Router::post('/getCaptcha', [\App\Controller\Admin\IndexController::class, 'getCaptcha']);
    Router::post('/uploadPic', [\App\Controller\Admin\IndexController::class, 'uploadPic']);
    Router::post('/uploadFile', [\App\Controller\Admin\IndexController::class, 'uploadFile']);
});

改成:
Router::addRoute(['OPTIONS', 'POST'],'/login',  [\App\Controller\Admin\IndexController::class, 'login']);
虽然看起来有点不舒服,但是也是一个很好方案

个人建议是第一种,简单方便

posted on 2024-04-29 14:37  zh7314  阅读(39)  评论(0编辑  收藏  举报