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

Angular POST multipart/form-data请求无multipart边界报错排查

Angular提交FormData请求提示无multipart边界问题排查

问题现象

  • 项目基于Angular框架开发,通过HttpClient提交FormData格式的带附件请求,全局HttpInterceptor会为匹配接口统一追加自定义请求头
  • 相同参数通过Postman调用接口可正常返回,Angular侧发起请求时服务端抛出错误:org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
  • 尝试手动移除请求中的Content-Type头后,又触发unsupported media type错误,无法定位问题根因

关联代码

HttpInterceptor实现

if (request.url.includes(baseUrl)) {
        request = request.clone({
          headers: new HttpHeaders({
            'Request-Date': currentDate.toString(),
            'Request-Id': requestId,                
            "Accept": "application/json" ,
            param1: environment.encriptionEnabled
              ? requestData['param1']
              : 'null',
            locale: 'en',
          }),
          body: environment.encriptionEnabled
            ? requestData.ciphertext
            : request.body
        });
      }

后端接口定义

@PostMapping(value = RequestURIConstant.SEND_EMAIL_WITH_ATTACHMENT, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE })
public void sendEmailWithAttachment(@RequestPart("request") String request, @RequestPart("file") MultipartFile file) {
  EmailNotificationRequest emailRequest = externalNotification.getJson(request);
  LOG.info("Request received to send email");
  externalNotification.sendMail(emailRequest, file);
}

报错截图

请求响应报错截图
请求头信息截图
控制台报错截图

根因与修复方案

  • 核心问题出在拦截器的请求头覆写逻辑:使用new HttpHeaders()传入固定配置对象时,会完全清空请求原有头再写入新配置。Angular的HttpClient在识别到请求体是FormData类型时,会自动生成带随机boundary参数的Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxx头,这个boundary是服务端拆分解析multipart请求体的唯一标识,被覆写逻辑覆盖后服务端找不到边界标识,自然抛出对应错误。
  • 手动删除Content-Type后触发媒体类型不支持错误,是因为直接移除该头后服务端无法识别请求体格式,匹配不到接口的consumes配置。
  • 修复逻辑:不要直接实例化全新的HttpHeaders对象替换原有头,在原有请求头的基础上追加自定义头,保留Angular自动生成的Content-Type配置;同时针对FormData类型请求跳过加密逻辑的body替换,避免破坏FormData结构:
if (request.url.includes(baseUrl)) {
  const isFormDataReq = request.body instanceof FormData;
  // 在原有请求头基础上追加自定义头,不覆盖自动生成的Content-Type
  const updatedHeaders = request.headers
    .set('Request-Date', currentDate.toString())
    .set('Request-Id', requestId)
    .set('Accept', 'application/json')
    .set('param1', environment.encriptionEnabled ? requestData['param1'] : 'null')
    .set('locale', 'en');

  // FormData请求不替换body,避免破坏 multipart 结构
  const updatedBody = environment.encriptionEnabled && !isFormDataReq
    ? requestData.ciphertext
    : request.body;

  request = request.clone({
    headers: updatedHeaders,
    body: updatedBody
  });
}
  • 注意:业务代码发起FormData请求时,禁止手动在请求配置中指定Content-Type,完全交给HttpClient自动生成即可。手动写入的头不会携带和请求体匹配的boundary参数,依然会触发服务端解析错误。
  • 验证方法:修改完成后打开浏览器网络面板,查看对应请求的Content-Type头是否携带自动生成的boundary参数,确认后发起请求即可正常解析。

内容的提问来源于stack exchange,提问作者Amit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:01:00