Angular Delete请求报400错误但Postman可正常调用,求排查
问题描述
Angular项目中调用删除接口返回400 Bad Request错误,请求已携带Access Token等认证信息,但使用相同API和Access Token在Postman中可正常调用。
相关代码
HttpService
delete<T>(url: string): Observable<T> { return this.httpClient.delete<T>(`${url}`); }
SettingsService
deleteTeamMember(companyId: number, userId: number): Observable<void> { return this.httpService.delete<void>( `${environment.urls.api}/company/${companyId}/team/${userId}` ); }
组件调用代码(component.ts)
this.settingsService .deleteTeamMember(this.company.companyId, teamMember.userId) .subscribe(); //api call
API响应结果
{ "code": "BadRequest", "title": "Bad Request", "message": "Expected one of: <<{,[>> but got: <<EOF>>", "type": "tag:oracle.com,2020:error/BadRequest", "instance": "tag:oracle.com,2020:ecid/XgKKiZB497hJdOcC1jQxOQ" }
AuthInterceptor代码
@Injectable() export class AuthInterceptor implements HttpInterceptor { private isRefreshing = false; private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>( null ); constructor( private authService: AuthService, private userDataService: UserDataService ) {} intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { let authRequest = request; const accessToken = this.userDataService.getAccessToken(); if (accessToken != null) { authRequest = this.addTokenHeader(request, accessToken); } return next.handle(authRequest).pipe( catchError((error) => { if ( error instanceof HttpErrorResponse && !authRequest.url.includes('auth/sign-in') && error.status === 401 ) { return this.handle401Error(authRequest, next); } return throwError(error); }) ); } private handle401Error( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); const refreshToken = this.userDataService.getRefreshToken(); if (refreshToken) { return this.authService.refreshToken(refreshToken).pipe( switchMap((token: TokenModel) => { this.isRefreshing = false; this.userDataService.setAccessToken(token?.accessToken); this.userDataService.setRefreshToken(token?.refreshToken); this.refreshTokenSubject.next(token.accessToken); //从refreshToken()调用中获取accessToken return next.handle(this.addTokenHeader(request, token.accessToken)); }), catchError((err) => { this.isRefreshing = false; this.userDataService.setIsSignedOut(true); //退出应用 return throwError(err); }) ); } } return this.refreshTokenSubject.pipe( filter((accessToken) => accessToken !== null), take(1), switchMap((accessToken) => next.handle(this.addTokenHeader(request, accessToken)) ) ); } private addTokenHeader( request: HttpRequest<any>, accessToken: string ): HttpRequest<any> { return request.clone({ setHeaders: { 'Content-Type': 'application/json', 'Access-Token': accessToken, }, }); } }
原因分析
API返回的错误信息Expected one of: <<{,[>> but got: <<EOF>>表明:后端期望请求体是JSON对象或数组,但实际收到的请求体为空(EOF代表请求体结束,无内容)。
问题根源在AuthInterceptor的addTokenHeader方法:它强制给所有请求添加Content-Type: application/json请求头,但Angular的HttpClient.delete()默认不会发送请求体。后端看到该请求头后,会尝试解析请求体,却发现请求体为空,因此抛出400错误。
而Postman请求时,大概率没有设置Content-Type: application/json请求头,或者Postman自动处理了空请求体的兼容逻辑,所以后端能正常响应。
解决方案
修改AuthInterceptor的addTokenHeader方法,仅在请求存在请求体时设置Content-Type: application/json,或直接排除DELETE请求:
private addTokenHeader( request: HttpRequest<any>, accessToken: string ): HttpRequest<any> { const headers: Record<string, string> = { 'Access-Token': accessToken, }; // 方案1:仅当请求有body时,设置Content-Type if (request.body !== null && request.body !== undefined) { headers['Content-Type'] = 'application/json'; } // 方案2:直接排除DELETE请求 // if (request.method !== 'DELETE') { // headers['Content-Type'] = 'application/json'; // } return request.clone({ setHeaders: headers }); }
内容的提问来源于stack exchange,提问作者Sampath
相关产品推荐
相关产品推荐

