问题:
struts2 使用jakarta 上传文件时,如果上传文件的大小超出commons fileupload(jakarta上传文件还是依赖commons-fileupload)设置的大小就会在进入action以前抛出异常.
如果想返回用户的输入界面(input),那么页面原来的参数会丢失。
首先看一下struts2 执行一个action的过程
1. 将用户请求发给org.apache.struts2.dispatcher.Dispatcher,
wrapRequest(HttpServletRequest request, ServletContext servletContext) 方法会判断是否"multipart/form-data",如果是建立一个multiPartRequest 的实例,并且建立MultiPartRequestWrapper
MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
} else {
request = new StrutsRequestWrapper(request);
}
2. 建立 MultiPartRequestWrapper 时解析(parse) request,
- public void parse(HttpServletRequest servletRequest, String saveDir)
- throws IOException {
- DiskFileItemFactory fac = new DiskFileItemFactory();
- // Make sure that the data is written to file
- fac.setSizeThreshold(0);
- if (saveDir != null) {
- fac.setRepository(new File(saveDir));
- }
- // Parse the request
- try {
- ServletFileUpload upload = new ServletFileUpload(fac);
- upload.setSizeMax(maxSize);
- //upload 解析request并取得页面参数
- List items = upload.parseRequest(createRequestContext(servletRequest));
- ......
public void parse(HttpServletRequest servletRequest, String saveDir) throws IOException { DiskFileItemFactory fac = new DiskFileItemFactory(); // Make sure that the data is written to file fac.setSizeThreshold(0); if (saveDir != null) { fac.setRepository(new File(saveDir)); } // Parse the request try { ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); //upload 解析request并取得页面参数 List items = upload.parseRequest(createRequestContext(servletRequest)); ......
3.我们看一下ServletFileUpload(commons-fileupload v1.1.1) 的parseRequest做了什么
- public List /* FileItem */ parseRequest(RequestContext ctx)
- throws FileUploadException {
- if (ctx == null) {
- throw new NullPointerException("ctx parameter");
- }
- ArrayList items = new ArrayList();
- String contentType = ctx.getContentType();
- if ((null == contentType)
- || (!contentType.toLowerCase().startsWith(MULTIPART))) {
- throw new InvalidContentTypeException(
- "the request doesn't contain a "
- + MULTIPART_FORM_DATA
- + " or "
- + MULTIPART_MIXED
- + " stream, content type header is "
- + contentType);
- }
- int requestSize = ctx.getContentLength();
- if (requestSize == -1) {
- throw new UnknownSizeException(
- "the request was rejected because its size is unknown");
- }
- //关键就这里了,大小超出的异常,这里是所有上传文件合计的大小,如果超出就抛出异常
- //这时上层是拿不到保存参数的items的
- if (sizeMax >= 0 && requestSize > sizeMax) {
- throw new SizeLimitExceededException(
- "the request was rejected because its size (" + requestSize
- + ") exceeds the configured maximum (" + sizeMax + ")",
- requestSize, sizeMax);
- }
- String charEncoding = headerEncoding;
- if (charEncoding == null) {
- charEncoding = ctx.getCharacterEncoding();
- }
- try {
- byte[] boundary = getBoundary(contentType);
- if (boundary == null) {
- throw new FileUploadException(
- "the request was rejected because "
- + "no multipart boundary was found");
- }
- InputStream input = ctx.getInputStream();
- MultipartStream multi = new MultipartStream(input, boundary);
- multi.setHeaderEncoding(charEncoding);
- boolean nextPart = multi.skipPreamble();
- while (nextPart) {
- Map headers = parseHeaders(multi.readHeaders());
- String fieldName = getFieldName(headers);
- if (fieldName != null) {
- String subContentType = getHeader(headers, CONTENT_TYPE);
- if (subContentType != null && subContentType
- .toLowerCase().startsWith(MULTIPART_MIXED)) {
- // Multiple files.
- byte[] subBoundary = getBoundary(subContentType);
- multi.setBoundary(subBoundary);
- boolean nextSubPart = multi.skipPreamble();
- while (nextSubPart) {
- headers = parseHeaders(multi.readHeaders());
- if (getFileName(headers) != null) {
- FileItem item =
- createItem(headers, false);
- OutputStream os = item.getOutputStream();
- try {
- multi.readBodyData(os);
- } finally {
- os.close();
- }
- items.add(item);
- } else {
- // Ignore anything but files inside
- // multipart/mixed.
- multi.discardBodyData();
- }
- nextSubPart = multi.readBoundary();
- }
- multi.setBoundary(boundary);
- } else {
- FileItem item = createItem(headers,
- getFileName(headers) == null);
- OutputStream os = item.getOutputStream();
- try {
- multi.readBodyData(os);
- } finally {
- os.close();
- }
- items.add(item);
- }
- } else {
- // Skip this part.
- multi.discardBodyData();
- }
- nextPart = multi.readBoundary();
- }
- } catch (IOException e) {
- throw new FileUploadException(
- "Processing of " + MULTIPART_FORM_DATA
- + " request failed. " + e.getMessage());
- }
- return items;
- }
public List /* FileItem */ parseRequest(RequestContext ctx) throws FileUploadException { if (ctx == null) { throw new NullPointerException("ctx parameter"); } ArrayList items = new ArrayList(); String contentType = ctx.getContentType(); if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) { throw new InvalidContentTypeException( "the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType); } int requestSize = ctx.getContentLength(); if (requestSize == -1) { throw new UnknownSizeException( "the request was rejected because its size is unknown"); } //关键就这里了,大小超出的异常,这里是所有上传文件合计的大小,如果超出就抛出异常 //这时上层是拿不到保存参数的items的 if (sizeMax >= 0 && requestSize > sizeMax) { throw new SizeLimitExceededException( "the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + sizeMax + ")", requestSize, sizeMax); } String charEncoding = headerEncoding; if (charEncoding == null) { charEncoding = ctx.getCharacterEncoding(); } try { byte[] boundary = getBoundary(contentType); if (boundary == null) { throw new FileUploadException( "the request was rejected because " + "no multipart boundary was found"); } InputStream input = ctx.getInputStream(); MultipartStream multi = new MultipartStream(input, boundary); multi.setHeaderEncoding(charEncoding); boolean nextPart = multi.skipPreamble(); while (nextPart) { Map headers = parseHeaders(multi.readHeaders()); String fieldName = getFieldName(headers); if (fieldName != null) { String subContentType = getHeader(headers, CONTENT_TYPE); if (subContentType != null && subContentType .toLowerCase().startsWith(MULTIPART_MIXED)) { // Multiple files. byte[] subBoundary = getBoundary(subContentType); multi.setBoundary(subBoundary); boolean nextSubPart = multi.skipPreamble(); while (nextSubPart) { headers = parseHeaders(multi.readHeaders()); if (getFileName(headers) != null) { FileItem item = createItem(headers, false); OutputStream os = item.getOutputStream(); try { multi.readBodyData(os); } finally { os.close(); } items.add(item); } else { // Ignore anything but files inside // multipart/mixed. multi.discardBodyData(); } nextSubPart = multi.readBoundary(); } multi.setBoundary(boundary); } else { FileItem item = createItem(headers, getFileName(headers) == null); OutputStream os = item.getOutputStream(); try { multi.readBodyData(os); } finally { os.close(); } items.add(item); } } else { // Skip this part. multi.discardBodyData(); } nextPart = multi.readBoundary(); } } catch (IOException e) { throw new FileUploadException( "Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage()); } return items; }
4.这之后才开始逐个进入interceptor,见DefaultActionInvocation.invoke()
- ....
- //递归interceptor
- if (interceptors.hasNext()) {
- final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
- UtilTimerStack.profile("interceptor: "+interceptor.getName(),
- new UtilTimerStack.ProfilingBlock<String>() {
- public String doProfiling() throws Exception {
- resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
- return null;
- }
- });
- } else {
- //如果有errors,resultCode会得到‘input’
- resultCode = invokeActionOnly();
- }
- ...
.... //递归interceptor if (interceptors.hasNext()) { final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); UtilTimerStack.profile("interceptor: "+interceptor.getName(), new UtilTimerStack.ProfilingBlock<String>() { public String doProfiling() throws Exception { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); return null; } }); } else { //如果有errors,resultCode会得到‘input’ resultCode = invokeActionOnly(); } ...
5.我们的目标就是返回input并且保留页面原来的参数,那么就要不要让ServletFileUpload抛出异常,并且要让strusts使用我们自己的jakart.
6.写自己的ServletFileUpload
- /*
- * Copyright 2001-2005 The Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.infowarelab.newcentury.web.util;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.commons.fileupload.FileItem;
- import org.apache.commons.fileupload.FileItemFactory;
- import org.apache.commons.fileupload.FileUpload;
- import org.apache.commons.fileupload.FileUploadException;
- import org.apache.commons.fileupload.MultipartStream;
- import org.apache.commons.fileupload.RequestContext;
- import org.apache.commons.fileupload.servlet.ServletRequestContext;
- import org.apache.log4j.Logger;
- /**
- * come from commons-fileupload
- * @author alfred
- */
- public class ServletFileUpload extends FileUpload {
- // ---------------------------------------------------------- Class methods
- /**
- * Logger for this class
- */
- private static final Logger logger = Logger.getLogger(ServletFileUpload.class);
- private List<String> errors = new ArrayList<String>();
- /**
- * Constructs an uninitialised instance of this class. A factory must be
- * configured, using <code>setFileItemFactory()</code>, before attempting
- * to parse requests.
- *
- * @see FileUpload#FileUpload(FileItemFactory)
- */
- public ServletFileUpload() {
- super();
- }
- /**
- * Constructs an instance of this class which uses the supplied factory to
- * create <code>FileItem</code> instances.
- *
- * @see FileUpload#FileUpload()
- */
- public ServletFileUpload(FileItemFactory fileItemFactory) {
- super(fileItemFactory);
- }
- /**
- * overide parseRequest
- */
- public List /* FileItem */parseRequest(RequestContext ctx) throws FileUploadException {
- if (ctx == null) {
- throw new NullPointerException("ctx parameter");
- }
- ArrayList items = new ArrayList();
- String contentType = ctx.getContentType();
- if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {
- throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or "
- + MULTIPART_MIXED + " stream, content type header is " + contentType);
- }
- int requestSize = ctx.getContentLength();
- if (requestSize == -1) {
- // throw new UnknownSizeException(
- // "the request was rejected because its size is unknown");
- logger.error("the request was rejected because its size is unknown");
- errors.add("the request was rejected because its size is unknown");
- }
- String charEncoding = getHeaderEncoding();
- if (charEncoding == null) {
- charEncoding = ctx.getCharacterEncoding();
- }
- try {
- byte[] boundary = getBoundary(contentType);
- if (boundary == null) {
- // throw new FileUploadException(
- // "the request was rejected because "
- // + "no multipart boundary was found");
- logger.error("the request was rejected because no multipart boundary was found");
- errors.add("the request was rejected because no multipart boundary was found");
- }
- InputStream input = ctx.getInputStream();
- MultipartStream multi = new MultipartStream(input, boundary);
- multi.setHeaderEncoding(charEncoding);
- boolean nextPart = multi.skipPreamble();
- while (nextPart) {
- Map headers = parseHeaders(multi.readHeaders());
- String fieldName = getFieldName(headers);
- if (fieldName != null) {
- String subContentType = getHeader(headers, CONTENT_TYPE);
- if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {
- // Multiple files.
- byte[] subBoundary = getBoundary(subContentType);
- multi.setBoundary(subBoundary);
- boolean nextSubPart = multi.skipPreamble();
- while (nextSubPart) {
- headers = parseHeaders(multi.readHeaders());
- if (getFileName(headers) != null) {
- FileItem item = createItem(headers, false);
- OutputStream os = item.getOutputStream();
- try {
- multi.readBodyData(os);
- } finally {
- os.close();
- }
- items.add(item);
- } else {
- // Ignore anything but files inside
- // multipart/mixed.
- multi.discardBodyData();
- }
- nextSubPart = multi.readBoundary();
- }
- multi.setBoundary(boundary);
- } else {
- FileItem item = createItem(headers, getFileName(headers) == null);
- OutputStream os = item.getOutputStream();
- try {
- multi.readBodyData(os);
- } finally {
- os.close();
- }
- items.add(item);
- }
- } else {
- // Skip this part.
- multi.discardBodyData();
- }
- nextPart = multi.readBoundary();
- }
- // remove SizeLimitExceededException
- if (getSizeMax() >= 0 && requestSize > getSizeMax()) {
- // throw new SizeLimitExceededException(
- // "the request was rejected because its size (" + requestSize
- // + ") exceeds the configured maximum (" + getSizeMax() + ")",
- // requestSize, getSizeMax());
- logger.error("the request was rejected because its size (" + requestSize
- + ") exceeds the configured maximum (" + getSizeMax() + ")");
- }
- } catch (IOException e) {
- logger.error("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage());
- errors.add("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage());
- // throw new FileUploadException(
- // "Processing of " + MULTIPART_FORM_DATA
- // + " request failed. " + e.getMessage());
- }
- return items;
- }
- /**
- * @return the errors
- */
- public List<String> getErrors() {
- return errors;
- }
- /**
- * @param errors the errors to set
- */
- public void setErrors(List<String> errors) {
- this.errors = errors;
- }
- }
/* * Copyright 2001-2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.infowarelab.newcentury.web.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.MultipartStream; import org.apache.commons.fileupload.RequestContext; import org.apache.commons.fileupload.servlet.ServletRequestContext; import org.apache.log4j.Logger; /** * come from commons-fileupload * @author alfred */ public class ServletFileUpload extends FileUpload { // ---------------------------------------------------------- Class methods /** * Logger for this class */ private static final Logger logger = Logger.getLogger(ServletFileUpload.class); private List<String> errors = new ArrayList<String>(); /** * Constructs an uninitialised instance of this class. A factory must be * configured, using <code>setFileItemFactory()</code>, before attempting * to parse requests. * * @see FileUpload#FileUpload(FileItemFactory) */ public ServletFileUpload() { super(); } /** * Constructs an instance of this class which uses the supplied factory to * create <code>FileItem</code> instances. * * @see FileUpload#FileUpload() */ public ServletFileUpload(FileItemFactory fileItemFactory) { super(fileItemFactory); } /** * overide parseRequest */ public List /* FileItem */parseRequest(RequestContext ctx) throws FileUploadException { if (ctx == null) { throw new NullPointerException("ctx parameter"); } ArrayList items = new ArrayList(); String contentType = ctx.getContentType(); if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) { throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType); } int requestSize = ctx.getContentLength(); if (requestSize == -1) { // throw new UnknownSizeException( // "the request was rejected because its size is unknown"); logger.error("the request was rejected because its size is unknown"); errors.add("the request was rejected because its size is unknown"); } String charEncoding = getHeaderEncoding(); if (charEncoding == null) { charEncoding = ctx.getCharacterEncoding(); } try { byte[] boundary = getBoundary(contentType); if (boundary == null) { // throw new FileUploadException( // "the request was rejected because " // + "no multipart boundary was found"); logger.error("the request was rejected because no multipart boundary was found"); errors.add("the request was rejected because no multipart boundary was found"); } InputStream input = ctx.getInputStream(); MultipartStream multi = new MultipartStream(input, boundary); multi.setHeaderEncoding(charEncoding); boolean nextPart = multi.skipPreamble(); while (nextPart) { Map headers = parseHeaders(multi.readHeaders()); String fieldName = getFieldName(headers); if (fieldName != null) { String subContentType = getHeader(headers, CONTENT_TYPE); if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) { // Multiple files. byte[] subBoundary = getBoundary(subContentType); multi.setBoundary(subBoundary); boolean nextSubPart = multi.skipPreamble(); while (nextSubPart) { headers = parseHeaders(multi.readHeaders()); if (getFileName(headers) != null) { FileItem item = createItem(headers, false); OutputStream os = item.getOutputStream(); try { multi.readBodyData(os); } finally { os.close(); } items.add(item); } else { // Ignore anything but files inside // multipart/mixed. multi.discardBodyData(); } nextSubPart = multi.readBoundary(); } multi.setBoundary(boundary); } else { FileItem item = createItem(headers, getFileName(headers) == null); OutputStream os = item.getOutputStream(); try { multi.readBodyData(os); } finally { os.close(); } items.add(item); } } else { // Skip this part. multi.discardBodyData(); } nextPart = multi.readBoundary(); } // remove SizeLimitExceededException if (getSizeMax() >= 0 && requestSize > getSizeMax()) { // throw new SizeLimitExceededException( // "the request was rejected because its size (" + requestSize // + ") exceeds the configured maximum (" + getSizeMax() + ")", // requestSize, getSizeMax()); logger.error("the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + getSizeMax() + ")"); } } catch (IOException e) { logger.error("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage()); errors.add("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage()); // throw new FileUploadException( // "Processing of " + MULTIPART_FORM_DATA // + " request failed. " + e.getMessage()); } return items; } /** * @return the errors */ public List<String> getErrors() { return errors; } /** * @param errors the errors to set */ public void setErrors(List<String> errors) { this.errors = errors; } }
7.copy org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest,只是import上面自己的ServletFileUpload.这样就可以保存页面的所有参数了。
8.更改struts配置文件加入你自己的JakartaMultiReques
- <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
- ame="jakarta_yourself"
- class="com.xxxxx.util.JakartaMultiPartRequest"
- cope="default" optional="true" />
<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta_yourself" class="com.xxxxx.util.JakartaMultiPartRequest" scope="default" optional="true" />
9.更改struts.properties
struts.multipart.parser=jakarta_yourself
10.就OK啦