选项 Preflight 导致 POST 请求挂起

Posted

技术标签:

【中文标题】选项 Preflight 导致 POST 请求挂起【英文标题】:OPTIONS Preflight causing POST request to hang 【发布时间】:2019-09-12 01:28:31 【问题描述】:

我的 Angular 应用程序的 CORS OPTIONS 预检请求一直存在问题。简而言之,我正在尝试实现一个简单的登录,它将一个 POST 请求发送到我的服务器,该服务器有一个使用 php\Slim 构建的后端,带有用户的凭据。 CORS OPTIONS 首先请求,我用 slim 处理它以响应 200 OK,所以当浏览器获得 OK 时,它最终会发送带有凭据的我的 POST,但随后它只是挂起......没有响应代码,什么都没有。

老实说,这让我大吃一惊,我猜我的请求服务有问题,因为这是唯一发送 OPTIONS 请求的服务。

疯狂的是,在我向登录按钮发送垃圾邮件后,它最终会屈服并做出响应。

I checked the network requests and this is the idea of what it looks like:

200 OPTIONS
    POST
200 OPTIONS
    POST

the posts are just blank

这是我的服务。

  signin(user: User): Observable<Result> 
    return this._http.post<Result>( this.base + this.signinURL, user )
      .pipe(catchError(this.error));
  

  private error(error: HttpErrorResponse) 
    return throwError(error);
  

这里是CORS相关的路线

$app->options('/routes:.+', function (Request $request, Response $response, $args) 
    return $response->withStatus(200);
);
$app->add(function (Request $req, Response $res, $next) 
    $response = $next($req, $res);
    return $response
        ->withHeader('Access-Control-Allow-Origin', 'http://localhost:4200')
        ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
);
$app->map(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], '/routes:.+', function(Request $req, Response $res) 
    $handler = $this->notFoundHandler;
    return $handler($req, $res);
);

另外值得一提的是,我在 Angular CLI 服务器上运行前端,在 PHP Server v7.3 上运行 PHP。

如果有人可以向我解释发生了什么以及为什么我会高兴,谢谢你的想法...:)

【问题讨论】:

【参考方案1】:

客户端(浏览器)是发送 OPTIONS 请求的对象;这是在每次请求之前由浏览器自动完成的。一个 OPTIONS 请求只是告诉客户端它允许发出什么样的请求(POST、GET、DELETE 等)。请注意,我假设您的应用程序是 Web 应用程序,因为您的代码没有显示任何明确的 OPTIONS 请求。

至于您在 Chrome 中看不到 POST 正文的原因,与您的服务问题无关。这是特定于 chrome 的东西;它基本上与页面刷新/重定向有关 --- Chrome 不会保留帖子正文。如果您保留 chrome 日志,并在任何重新加载/刷新/重定向发生之前停止网络,您将看到正文。发生这种情况有一个特定的原因,我只是不记得我在哪里读过它。我的建议是改为在本地记录。

至于您的 API,您的 OPTIONS 处理看起来有点滑稽。 $app-&gt;options 是您捕获所有 OPTIONS 请求的地方,您所做的只是返回 200;你没有定义你的标题。未经测试,我假设这是您的问题。

我会说放弃所有这些,而只是做这样的事情:

$app = new \Slim\App();

$app->add(function (Request $request, Response $response, $next) 
    if($request->getMethod() !== 'OPTIONS') 
        return $next($request, $response);
    

    $response = $response->withHeader('Access-Control-Allow-Origin', '*');
    $response = $response->withHeader('Access-Control-Allow-Methods', $request->getHeaderLine('Access-Control-Request-Method'));
    $response = $response->withHeader('Access-Control-Allow-Headers', $request->getHeaderLine('Access-Control-Request-Headers'));

    return $next($request, $response);
);

# ... all your other routes ...

$app->run();

另外,为什么不让您的 http 服务器自己处理 OPTIONS 请求?

在我的情况下,我会注意到我还在 PHP 7.3 中运行基于 Slim 的 API,我不费心处理 OPTIONS 请求,我只是在我的虚拟主机配置 (apache2) 中设置标题。像这样的:

<VirtualHost *:443>

    ...

    <IfModule mod_headers.c>
            # allowable request origins
            Header set Access-Control-Allow-Origin "*"

            # allowable request types
            Header set Access-Control-Allow-Methods "POST, GET, DELETE, OPTIONS"

            # how long to cache results of preflight requests
            Header set Access-Control-Max-Age "1800"

            # allowable headers
            Header set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept"

            # permit preflight response requests using credentials
            Header set Access-Control-Allow-Credentials "true"
    </IfModule>

    ...

</VirtualHost>

【讨论】:

感谢@Crayons,我刚刚为我的超薄文件尝试了您的解决方案,它有效!...如果我可以进一步问,我将如何在超薄使用的 .htaccess 文件中设置配置。我担心将访问控制标头与重写引擎一起添加会破坏这方面的某些东西。 再次感谢@Crayons...我实际上是从firefox运行它,空白的响应是最让我困惑的。 在您的虚拟主机中或通过 .htaccess 文件设置标头不会干扰您的重写规则。我在我的虚拟主机定义中都使用了它们。只需确保在您的 apache 服务器中启用了两个模块:sudo a2enmod headerssudo a2enmod rewrite。您可以在 .htaccess 文件 Header add Access-Control-Allow-Methods "POST, GET, DELETE, OPTIONS" 等中以相同的方式添加标题规则。 知道了!...关键字是“添加”访问控制...而不是“设置”...感谢大佬的帮助,不胜感激 仅供参考:将 Access-Control-Allow-Origin 设置为 * 不是一个好习惯。建议定义真正允许使用 API 的主机。如果它是一个旨在由网络应用程序嵌入的公共 API(不是服务器端消费),那么我认为将它的值设置为 * 是可以的。

以上是关于选项 Preflight 导致 POST 请求挂起的主要内容,如果未能解决你的问题,请参考以下文章

preflight request预检请求

Preflight(options) 请求的正确成功状态代码是啥? [复制]

导致 PREFLIGHT 的 HTTP 标头 - 澄清?

CORS Preflight 请求是不是包含请求的标头?

Spring Boot 和 PreFlight 请求

axios post 请求挂在有效请求上