在 Spring REST 服务中优雅地处理无效文件上传

Gracefully handle invalid file uploads in Spring REST service

我在尝试验证对用于上传文件的 REST 端点的请求时遇到了障碍。上传工作正常,只要请求正确,现在我想包括检查是否存在必填字段以及它们包含的数据是否符合我的预期。

我正在使用 Spring Boot 1.2.5 和 jersey-media-multipart 2.1.4.

这是服务:

@Service
@Path("/attachments")
public class AttachmentsController {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response upload(@FormDataParam("attachment") InputStream attachmentInputStream,
                           @FormDataParam("attachment") FormDataContentDisposition attachmentFileDetail) {

        if (null == attachmentInputStream
                || null == attachmentFileDetail
                || null == attachmentFileDetail.getFileName()) {
            return Response.status(Response.Status.BAD_REQUEST).build();
        }

        // receiving, storing file, returning 201 Created responses etc.
    }

}

现在,当我测试不同的场景时:

  1. 使用 multipart/form-data 调用端点,在 attachment 字段中包含文件可以正常工作。
  2. 在没有 multipart/form-data 的情况下调用,attachment 字段包含或不包含某些文本,正确输入 if() 检查 null 值,但 HTTP 响应变为404 Not Found 而不是我编码的 400 Bad Request
  3. 调用时没有任何字段(空请求),无论它是否 multipart/form-data,都会触发下面粘贴的 NullPointerException,并且 return 会收到 404 Not Found 响应。

这是 NullPointerException:

Servlet.service() for servlet [jerseyServlet] in context with path [] threw exception [java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
    at org.glassfish.jersey.media.multipart.internal.FormDataParamValueFactoryProvider$FormDataParamValueFactory.provide(FormDataParamValueFactoryProvider.java:205)
    at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:81)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:121)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:152)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:104)
    ...

任何想法如何:

  1. return 情况(2)的正确响应为 400,并且
  2. 优雅地处理案例 (3),避免异常和 returning 代码 400?

关于场景 3:

也许您可以将 NullPointerException 配置为产生 400?

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(NullPointerException .class)
    public void handleNull() {
        // Nothing to do
    }
}

最终我们找到了方案 2 的解决方案和方案 3 的解决方法。

场景 2

我们收到的 404 Not Found 响应是由于 Jersey 试图显示一个错误页面,该页面在服务中不存在。通过提供以下配置,可以关闭该默认行为,以支持简单地 return 将所需的错误代码与编码响应一起使用:

@Component
public class WebServiceConfig extends ResourceConfig {

    public WebServiceConfig() {
        property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, "true");
    }
}

场景 3

我们通过为 POST 请求(下面命名为 uploadBodyMissing)添加第二个服务方法来解决这个问题,不需要任何参数。因此,当没有参数 POST 进入时,将调用此参数并且 return 一个 400 Bad Request 响应:

@Service
@Path("/attachments")
public class AttachmentsController {

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    public Response uploadBodyMissing() {
        return Response.status(Response.Status.BAD_REQUEST).build();
    }

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response upload(@FormDataParam("attachment") InputStream attachmentInputStream,
                           @FormDataParam("attachment") FormDataContentDisposition attachmentFileDetail) {

        if (null == attachmentInputStream
                || null == attachmentFileDetail
                || null == attachmentFileDetail.getFileName()) {
            return Response.status(Response.Status.BAD_REQUEST).build();
        }

        // receiving, storing file, returning 201 Created responses etc.
    }

}