You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:40:38