如何在文件上传至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
相关产品推荐
相关产品推荐

