在Chrome中取消了飞行前OPTIONS请求(已支持CORS)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Chrome中取消了飞行前OPTIONS请求(已支持CORS)相关的知识,希望对你有一定的参考价值。

我正在开发一个使用Angular + Django(Django Rest Framework)的项目。在开发过程中,CORS支持是通过使用django-cors-headersCORS_ORIGIN_ALLOW_ALL = TrueCORS_ALLOW_CREDENTIALS = True来完成的。

当我尝试发送POST请求以在前端(Angular)中创建一些资源时,Chrome会发送一些飞行前OPTIONS请求并由后端服务器(python manage.py runserver)成功响应,但其他请求则不然。由于未知原因,这些请求被取消,后端服务器日志表明服务器已接收并接受请求,详细信息如下图所示。

Some OPTIONS requests are failed

Backend server logs

失败请求的标头如下所示。

Details of failed request

但是,如果复制标题的内容并尝试使用curl发送它,它会按预期工作。

$ curl -v -X OPTIONS -H "Access-Control-Request-Headers: authorization,content-type" -H "Access-Control-Request-Method: POST" -H "DNS: 1" -H "Origin: http://localhost:4200" -H "Referer: http://localhost:4200" -H "User-Agent: Mozilla/5.0" http:/localhost:8000/api/user-permissions/
* Unwillingly accepted illegal URL using 1 slash!
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> OPTIONS /api/user-permissions/ HTTP/1.1
> Host: localhost:8000
> Accept: */*
> Access-Control-Request-Headers: authorization,content-type
> Access-Control-Request-Method: POST
> DNS: 1
> Origin: http://localhost:4200
> Referer: http://localhost:4200
> User-Agent: Mozilla/5.0
>
< HTTP/1.1 200 OK
< Date: Wed, 20 Feb 2019 02:47:39 GMT
< Server: WSGIServer/0.2 CPython/3.7.1
< Content-Type: text/html; charset=utf-8
< Content-Length: 0
< Vary: Origin
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with
< Access-Control-Allow-Methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
< Access-Control-Max-Age: 86400
<
* Connection #0 to host localhost left intact

任何想法如何发生?谢谢。


示例代码:

// The method of the component that invokes the methods of PermissionService.
  /** Update selected user's permissions. */
  updatePermissions() {
    const diff = this.diffPermissions();
    const toBeCreated = diff[0];
    const toBeDeleted = diff[1];
    this.isLoading = true;
    zip(
      this.permissionService.createUserPermissions(toBeCreated),
      this.permissionService.deleteUserPermissions(toBeDeleted),
    ).pipe(
      map(() => true),
      catchError((err: HttpErrorResponse) => {
        alert(err.message);
        return observableOf(false);
      }),
    ).subscribe(succeed => {
      this.isLoading = false;
    });
  }

// The methods of PermissionService that issue the HTTP requests.

  createUserPermission(req: UserPermissionRequest) {
    return this.http.post(`${environment.API_URL}/user-permissions/`, req);
  }

  createUserPermissions(reqs: UserPermissionRequest[]) {
    // TODO(youchen): Evaluate the performance cost.
    return forkJoin(reqs.map(req => this.createUserPermission(req)));
  }

  deleteUserPermission(permissionId: number) {
    return this.http.delete(`${environment.API_URL}/user-permissions/${permissionId}/`);
  }

  deleteUserPermissions(permissionIds: number[]) {
    // TODO(youchen): Evaluate the performance cost.
    return forkJoin(permissionIds.map(id => this.deleteUserPermission(id)));
  }
答案

找到原因:没有参数的zip()

就我而言,我使用zip来组合创作和删除,请参阅:

const createRequests = [c1, c2];
const deleteRequests = [d1, d2];

zip(
  this.service.create(createRequests),
  this.service.delete(deleteRequests),
)....

---
service.ts

create(reqs: CreateRequest[]) {
  return zip(...reqs.map(req => this.createSingle(req));
}

delete(reqs: DeleteRequest[]) {
  return zip(...reqs.map(req => this.deleteSingle(req));
}

但如果其中一个createRequestsdeleteRequests是一个空列表,那么这个逻辑就会出错。例如,如果createRequests为空而deleteRequests不为,则this.service.delete(deleteRequests)触发的所有HTTP请求都将被取消,因为zip()返回空this.service.create(createRequests)

解:

这个问题的解决方案是我们检查reqs的长度并返回其他可观察的instaned。

固定代码:

create(reqs: CreateRequest[]) {
  if (reqs.length === 0) return of([]);
  return zip(...reqs.map(req => this.createSingle(req));
}

delete(reqs: DeleteRequest[]) {
  if (reqs.length === 0) return of([]);
  return zip(...reqs.map(req => this.deleteSingle(req));
}

以上是关于在Chrome中取消了飞行前OPTIONS请求(已支持CORS)的主要内容,如果未能解决你的问题,请参考以下文章

如何在浏览器的控制台中查看 CORS 飞行前 OPTIONS 请求?

在 IdentityServer3 中处理 CORS 之前发送的飞行前 OPTIONS 请求

AngularJS 的 Preflight OPTIONS 请求不适用于 Chrome?

Tomcat Jersey 在“OPTIONS”飞行前请求中阻止服务器

带有飞行前请求的 Angularjs $q.all 在 OPTIONS 请求后解析

AugularJS 中的飞行前 OPTIONS 请求未通过授权标头