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

如何在文件上传至Jetty前验证文件上传请求?

问题分析

Jetty 默认会在调用 Filter、Servlet 或 JAX-RS 端点代码前,将整个请求体(包括大文件)缓存到内存或磁盘中,导致 token 验证逻辑被延后执行。当大文件上传耗时超过 token 有效期(5分钟)时,就会出现验证超时失败的问题。

可行解决思路

核心是让 Jetty 不要提前缓存完整请求体,先完成 token 验证,再通过流式方式处理文件上传。以下是具体实现方案:

方案1:Servlet + Filter 流式处理

步骤1:修改 AuthFilter,先验证token并禁止提前解析请求体

@Priority(Priorities.AUTHENTICATION)
public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 从Cookie或Header获取token并验证
        String token = req.getCookie("token") != null ? req.getCookie("token").getValue() : req.getHeader("Authorization");
        if (token == null || !validateToken(token)) {
            resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid or expired token");
            return;
        }

        // 告诉Jetty不要提前解析multipart/form-data请求体
        req.setAttribute("org.eclipse.jetty.server.Request.extractFormParameters", Boolean.FALSE);
        
        chain.doFilter(request, response);
    }

    private boolean validateToken(String token) {
        // 实现你的token验证逻辑
        return token != null && token.length() > 0;
    }
}

步骤2:改写Servlet,用流式API处理上传

借助 Apache Commons FileUpload 的流式迭代器,避免一次性加载整个文件:

@WebServlet("/file")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String contentType = request.getContentType();
        if (contentType == null) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        if (contentType.startsWith("multipart/form-data")) {
            handleMultipartUpload(request);
        } else if (contentType.equals("application/octet-stream")) {
            handleOctetStreamUpload(request);
        }

        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().write("{\"status\":\"success\"}");
    }

    private void handleMultipartUpload(HttpServletRequest request) throws IOException, ServletException {
        ServletFileUpload upload = new ServletFileUpload();
        upload.setSizeMax(-1); // 不限制文件大小
        try {
            FileItemIterator itemIterator = upload.getItemIterator(request);
            while (itemIterator.hasNext()) {
                FileItemStream item = itemIterator.next();
                if (!item.isFormField() && "file".equals(item.getFieldName())) {
                    // 流式读取文件内容
                    try (InputStream inputStream = item.openStream()) {
                        writeToStorage(inputStream, item.getName());
                    }
                }
            }
        } catch (FileUploadException e) {
            throw new ServletException("File upload failed", e);
        }
    }

    private void handleOctetStreamUpload(HttpServletRequest request) throws IOException {
        try (InputStream inputStream = request.getInputStream()) {
            writeToStorage(inputStream, "uploaded_file");
        }
    }

    private void writeToStorage(InputStream inputStream, String fileName) throws IOException {
        // 替换为你的存储逻辑(比如写入磁盘、云存储等)
        try (FileOutputStream outputStream = new FileOutputStream("/tmp/upload/" + fileName)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        }
    }
}

方案2:JAX-RS 流式处理

如果继续使用JAX-RS,避免使用MultipartFormDataInput(会触发全量缓存),改用HttpServletRequest手动处理流式上传,同时保留PreMatching Filter验证token:

步骤1:PreMatching Filter验证token

@Priority(Priorities.AUTHENTICATION)
@PreMatching
public class AuthFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // 从Cookie或Header获取token
        String token = null;
        Cookie[] cookies = ((ServletRequest) requestContext.getRequest()).getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("token".equals(cookie.getName())) {
                    token = cookie.getValue();
                    break;
                }
            }
        }
        if (token == null) {
            token = requestContext.getHeaderString("Authorization");
        }

        if (token == null || !validateToken(token)) {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
    }

    private boolean validateToken(String token) {
        // 实现token验证逻辑
        return token != null && token.length() > 0;
    }
}

步骤2:JAX-RS端点流式处理上传

@POST
@Path("/file")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response fileUpload(@Context HttpServletRequest request) {
    try {
        ServletFileUpload upload = new ServletFileUpload();
        FileItemIterator itemIterator = upload.getItemIterator(request);
        while (itemIterator.hasNext()) {
            FileItemStream item = itemIterator.next();
            if (!item.isFormField() && "file".equals(item.getFieldName())) {
                try (InputStream inputStream = item.openStream()) {
                    writeToStorage(inputStream, item.getName());
                }
            }
        }
        return Response.ok().entity("{\"status\":\"success\"}").build();
    } catch (Exception e) {
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
}

private void writeToStorage(InputStream inputStream, String fileName) throws IOException {
    // 存储逻辑同Servlet方案
    try (FileOutputStream outputStream = new FileOutputStream("/tmp/upload/" + fileName)) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    }
}

关键配置说明

  • 禁止Jetty提前解析请求体:通过设置org.eclipse.jetty.server.Request.extractFormParameters为false,避免Jetty在调用业务代码前缓存整个multipart请求体。
  • 流式处理:使用FileItemIterator和InputStream逐块读取文件内容,不会一次性加载整个文件到内存或磁盘。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:17:34