Angular中HttpClient请求被调用两次问题求助
我在Angular项目里尝试调用API,但发现每次都会触发两次请求:第一次是OPTIONS方法,第二次才是POST。以下是我的代码:
MyComponent.ts
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { ActivatedRoute } from '@angular/router'; import { Api } from '../../services/Api'; import { Account } from '../../models/Account'; @Component({ selector: 'app-account-individual', templateUrl: './account-individual.component.html', styleUrls: ['./account-individual.component.css'] }) export class AccountIndividualComponent implements OnInit { account_id: number; account: Account = new Account(); constructor(private route: ActivatedRoute, private proxy: Api) { this.proxy.postClient('api-get?call=get-account', { "id": 1 }).subscribe( res => { console.log(res); } ); } ngOnInit() { this.route.params.subscribe(params => { this.account_id = params['id']; }); } }
Api.ts
import { Injectable } from '@angular/core'; import { Http, Headers, Response, RequestOptions } from '@angular/http'; import { Observable } from 'rxjs'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; import * as Globals from 'Globals'; import { HttpClient } from '@angular/common/http'; @Injectable() export class Api { api_url = "http://localhost/MyProject/"; constructor(private http: Http, private httpClient: HttpClient) { } fetchData(url) { return this.http.get(this.api_url + url).map(this.extractData).catch(this.handleErrorPromise); } postClient(url: string, data?: any){ return this.httpClient.post(this.api_url + url, data); } private extractData(res: Response) { let body = res.json(); return body||{}; } private handleErrorObservable(error: Response | any) { console.error(error.message || error); return Observable.throw(error.message || error); } private handleErrorPromise(error: Response | any) { console.error(error.message || error); return Promise.reject(error.message || error); } }
服务端CORS处理代码(PHP)
public function actionApiGet($call){ header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: x-test-header, Origin, X-Requested-With, Content-Type, Accept'); $data = json_decode(file_get_contents('php://input'), true); ... }
现在想知道怎么解决这种两次请求的问题?
先给你理清楚哈:这个OPTIONS请求不是代码出bug了,它是浏览器自动发起的预请求(Preflight Request),属于CORS跨域规范里的正常操作。当你发起的跨域请求是非简单请求时(比如像你这样用POST发送JSON数据,Content-Type是application/json),浏览器会先给服务器发个OPTIONS请求,确认服务器允许这个跨域请求后,才会发送真正的POST请求。
不过我们可以通过优化服务器端的处理,避免不必要的资源消耗,同时让请求更高效:
单独拦截OPTIONS请求,直接返回成功响应
你现在的服务端代码会在处理业务逻辑的同一个方法里处理OPTIONS请求,可能会白白执行后续的解析请求体、处理业务的代码,完全没必要。你可以先判断请求方法,如果是OPTIONS,直接返回200状态码然后退出,不执行后续逻辑:public function actionApiGet($call){ // 优先处理OPTIONS预请求 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: x-test-header, Origin, X-Requested-With, Content-Type, Accept'); http_response_code(200); exit; // 直接退出,跳过后续业务逻辑 } // 处理实际的POST/GET业务请求 header('Access-Control-Allow-Origin: *'); $data = json_decode(file_get_contents('php://input'), true); // ... 你的业务代码 }给预请求添加缓存,减少重复OPTIONS请求
你可以在OPTIONS的响应头里加上Access-Control-Max-Age,指定预请求结果的缓存时长(单位是秒)。这样浏览器在缓存有效期内,访问同一个接口时就不会重复发OPTIONS请求了:if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: x-test-header, Origin, X-Requested-With, Content-Type, Accept'); header('Access-Control-Max-Age: 86400'); // 缓存1天,可根据需求调整时长 http_response_code(200); exit; }看看能不能转为简单请求(可选)
如果你的请求能符合"简单请求"的标准,浏览器就不会发预请求。简单请求的要求是:- 请求方法只能是GET、HEAD或POST
- 请求头只能包含Accept、Accept-Language、Content-Language、Content-Type(且值只能是application/x-www-form-urlencoded、multipart/form-data或text/plain)
不过你的请求是发送JSON数据,Content-Type是application/json,不符合简单请求的要求,所以这种情况下OPTIONS预请求是没法避免的,只能用前面两种方法优化。
总的来说,OPTIONS请求是跨域非简单请求的正常流程,我们没法完全消除它,但通过服务器端的优化,可以让它不影响业务逻辑,同时减少重复请求的次数。
内容的提问来源于stack exchange,提问作者Mohammad

