前一段時(shí)間剛來公司,看到一個(gè)項(xiàng)目中以前有人寫的struts代碼。是使用了FormFile來處理關(guān)于文件上傳的模塊。但是用力一段時(shí)間后,發(fā)現(xiàn)出問題了。寫完的這個(gè)模塊,上傳文件是沒有問題的,但是當(dāng)服務(wù)器的空間較小的時(shí)候,穿一個(gè)比較大的文件就出問題了,文件還沒有上傳完,就拋出一個(gè)錯(cuò)誤的頁面,報(bào)告上傳模塊出了問題,而且是Tomcat默認(rèn)的出錯(cuò)頁面。 于是想辦法,修改,查看源代碼,發(fā)現(xiàn)原來寫這段代碼的人是默認(rèn)等文件上傳完以后進(jìn)入Action了才判斷文件大小是否超出了限制。 但是,默認(rèn)配置下使用struts的FormFile比較特殊,F(xiàn)ormFile是struts包對外的一個(gè)接口,而且org.apache.struts.upload包是使用的commons-fileupload-1.0進(jìn)行的封裝。如果使用了它來實(shí)現(xiàn)文件上傳的功能,則
必須是FormFile對象在被初始化以后才能使用,那什么時(shí)候它才是被初始化的呢? 答案是:
在進(jìn)入Action之前就已經(jīng)初始化好了! 因此,原先的設(shè)計(jì):在Action中判斷文件大小是根本不能在上傳過程中起到提示作用的,因?yàn)檫@時(shí)候文件已經(jīng)上傳完了。而且這個(gè)設(shè)計(jì)還有一個(gè)確定就是不能捕獲上傳過程中出現(xiàn)的任何問題。也就是說:在Action里我們得到的FormFile對象是上傳的一個(gè)結(jié)果,而不是一個(gè)未上傳好就可以使用的對象! 那如何控制FormFile上傳的過程呢?顯然,在Action里處理已經(jīng)不能奏效了,想想別的辦法,讓我們翻看一下Struts的源代碼找找靈感吧。 這是struts1.1的org.apache.struts.upload包的描述: (見附件) 從上圖我們可以看出有有CommonsMultipartRequestHandler和DiskMultipartRequestHandler兩個(gè)類實(shí)現(xiàn)了MultipartRequestHandler接口。 大家都知道,Commons-fileupload控件在上傳的時(shí)候,使用的
enctype為:enctype="multipart/form-data",因此不難看出MultipartRequestHandler的實(shí)現(xiàn)就是來處理enctype="multipart/form-data"這樣的post請求的。 但是這里有兩個(gè)類,CommonsMultipartRequestHandler和DiskMultipartRequestHandler。到底哪個(gè)是處理FormFile的上傳的呢?這個(gè)問題應(yīng)該從org.apache.struts.config包里來找。 org.apache.struts.config包是用來處理struts配置文件的數(shù)據(jù)的包。找到org.apache.struts.config. ControllerConfig。 看這幾行:
1 protected String multipartClass =
2 "org.apache.struts.upload.CommonsMultipartRequestHandler";
3
4 public String getMultipartClass()
5 {
6 return (this.multipartClass);
7 }
8
9 public void setMultipartClass(String multipartClass)
10 {
11 if (configured)
12 {
13 throw new IllegalStateException("Configuration is frozen");
14 }
15
16 this.multipartClass = multipartClass;
17 }
18
1 /**
2 * The fully qualified Java class name of the MultipartRequestHandler
3 * class to be used.
4 */
5 protected String multipartClass =
6 "org.apache.struts.upload.CommonsMultipartRequestHandler";
7
8 public String getMultipartClass()
9 {
10 return (this.multipartClass);
11 }
12
13 public void setMultipartClass(String multipartClass)
14 {
15 if (configured)
16 {
17 throw new IllegalStateException("Configuration is frozen");
18 }
19
20 this.multipartClass = multipartClass;
21 }
22
這幾行的意思很明白,
如果沒有在配置文件中配置MultipartRequestHandler實(shí)現(xiàn)類的絕對路徑,那就使用org.apache.struts.upload.CommonsMultipartRequestHandler類默認(rèn)處理。 ^_^,這就是關(guān)鍵了:
struts是默認(rèn)使用org.apache.struts.upload.CommonsMultipartRequestHandler類來處理FormFile指定的上傳文件的。 馬上轉(zhuǎn)到org.apache.struts.upload.CommonsMultipartRequestHandler來看看:
1 /**
2 *默認(rèn)文件上傳的大小是250M
3 */
4 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
5 /**
6 *上傳文件在內(nèi)存中使用的緩沖區(qū)大小,超過次數(shù)值的數(shù)據(jù)寫入硬盤。
7 */
8 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
1 /**
2 *默認(rèn)文件上傳的大小是250M
3 */
4 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
5 /**
6 *上傳文件在內(nèi)存中使用的緩沖區(qū)大小,超過次數(shù)值的數(shù)據(jù)寫入硬盤。
7 */
8 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
還有,最最重要的實(shí)現(xiàn)方法:
1 /**
2 * Parses the input stream and partitions the parsed items into a set of
3 * form fields and a set of file items. In the process, the parsed items
4 * are translated from Commons FileUpload <code>FileItem</code> instances
5 * to Struts <code>FormFile</code> instances.
6 * @param request The multipart request to be processed.
7 * @throws ServletException if an unrecoverable error occurs.
8 就是這個(gè)函數(shù)處理上傳文件的request,把request交給Commons FileUpload 控件處理,并解析FileItem轉(zhuǎn)換成Struts的FormFile。
9 */
10 public void handleRequest(HttpServletRequest request) throws ServletException
11
1 /**
2 * Parses the input stream and partitions the parsed items into a set of
3 * form fields and a set of file items. In the process, the parsed items
4 * are translated from Commons FileUpload <code>FileItem</code> instances
5 * to Struts <code>FormFile</code> instances.
6 * @param request The multipart request to be processed.
7 * @throws ServletException if an unrecoverable error occurs.
8 就是這個(gè)函數(shù)處理上傳文件的request,把request交給Commons FileUpload 控件處理,并解析FileItem轉(zhuǎn)換成Struts的FormFile。
9 */
10 public void handleRequest(HttpServletRequest request) throws ServletException
再看看這個(gè)函數(shù)內(nèi)部是怎么實(shí)現(xiàn)的吧?
1 // 使用了DiskFileUpload。
2 // (Commons-FileUpload很老版本的一個(gè)上傳實(shí)現(xiàn)類了,還在用?我的顯示是Deprecated)
3 DiskFileUpload upload = new DiskFileUpload();
4 // 上傳最大值
5 upload.setSizeMax((int) getSizeMax(ac));
6 // 上傳文件在內(nèi)存中使用的緩沖區(qū)大小
7 upload.setSizeThreshold((int) getSizeThreshold(ac));
8 // 存在硬盤的什么地方,一般是默認(rèn)
9 pload.setRepositoryPath(getRepositoryPath(ac));
1 // 使用了DiskFileUpload。
2 // (Commons-FileUpload很老版本的一個(gè)上傳實(shí)現(xiàn)類了,還在用?我的顯示是Deprecated)
3 DiskFileUpload upload = new DiskFileUpload();
4 // 上傳最大值
5 upload.setSizeMax((int) getSizeMax(ac));
6 // 上傳文件在內(nèi)存中使用的緩沖區(qū)大小
7 upload.setSizeThreshold((int) getSizeThreshold(ac));
8 // 存在硬盤的什么地方,一般是默認(rèn)
9 pload.setRepositoryPath(getRepositoryPath(ac));
接著看handleRequest如何處理request的:
1 // Parse the request into file items.
2 List items = null;
3 try
4 {
5 items = upload.parseRequest(request);
6 }
7 //這里是關(guān)鍵:上傳過程中出了超出最大值的異常了,如何處理?
8 catch (DiskFileUpload.SizeLimitExceededException e)
9 {
10 // Special handling for uploads that are too big
11 request.setAttribute(
12 MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
13 Boolean.TRUE);
14 return;
15 }
16 //出了其他異常,如enctype不對,磁盤空間不足怎么辦?
17 catch (FileUploadException e)
18 {
19 log.error("Failed to parse multipart request", e);
20 throw new ServletException(e);
21 }
22
1 // Parse the request into file items.
2 List items = null;
3 try
4 {
5 items = upload.parseRequest(request);
6 }
7 //這里是關(guān)鍵:上傳過程中出了超出最大值的異常了,如何處理?
8 catch (DiskFileUpload.SizeLimitExceededException e)
9 {
10 // Special handling for uploads that are too big
11 request.setAttribute(
12 MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
13 Boolean.TRUE);
14 return;
15 }
16 //出了其他異常,如enctype不對,磁盤空間不足怎么辦?
17 catch (FileUploadException e)
18 {
19 log.error("Failed to parse multipart request", e);
20 throw new ServletException(e);
21 }
22
這次一目了然了:
Struts根本沒有把上傳過程中出的超出最大值的異常帶到Action,而是把它放到了rquest的Attribute里。 而出了其他異常如enctype不對,磁盤空間不足怎么辦?很遺憾,Struts沒有去處理它,而是log了一下,拋給了上一層了。 那我一定要獲得這些全部異常咋辦呢?沒辦法,自己定制一個(gè)MultipartRequestHandler吧,那樣就能徹底解決上傳過程中的控制問題了! 在此之前,我們得先去最新版的commons-fileupload控件看看上傳過程中可能拋出多少異常?
1 //所有上傳異常的父類
2 org.apache.commons.fileupload.FileUploadException
3 //注意:這個(gè)類的類名是FileUploadBase.SizeLimitExceededException是個(gè)public內(nèi)
4 //部類。上傳的formdata總的數(shù)據(jù)超出了規(guī)定大小
5 org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
6 //注意:也是個(gè)內(nèi)部類。這個(gè)才是上傳的文件超出了規(guī)定大小
7 org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
8 //其它的,也看看吧:
9 org.apache.commons.fileupload.FileUploadBase.FileUploadIOException
10 org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException
11 org.apache.commons.fileupload.FileUploadBase.IOFileUploadException
12 org.apache.commons.fileupload.FileUploadBase.UnknownSizeException
1 //所有上傳異常的父類
2 org.apache.commons.fileupload.FileUploadException
3 //注意:這個(gè)類的類名是FileUploadBase.SizeLimitExceededException是個(gè)public內(nèi)
4 //部類。上傳的formdata總的數(shù)據(jù)超出了規(guī)定大小
5 org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
6 //注意:也是個(gè)內(nèi)部類。這個(gè)才是上傳的文件超出了規(guī)定大小
7 org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
8 //其它的,也看看吧:
9 org.apache.commons.fileupload.FileUploadBase.FileUploadIOException
10 org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException
11 org.apache.commons.fileupload.FileUploadBase.IOFileUploadException
12 org.apache.commons.fileupload.FileUploadBase.UnknownSizeException
要想獲得盡可能仔細(xì)的數(shù)據(jù)就在處理的try/catch塊里把上面的異常都catch一下,放到request的attribute里去就OK了。 另外還有要說的是,
最好用commons-fileupload控件的最新版本,因?yàn)镈iskFileUpload這個(gè)類,commons-fileupload已經(jīng)棄用了,取而代之的是ServletFileUpload類了,所以一定要注意!切記,切記….. 這是我寫的CommonsMultipartRequestHandler替代類的public void handleRequest(HttpServletRequest request) throws ServletException函數(shù):
1 public void handleRequest(HttpServletRequest request) throws ServletException
2 {
3 // Get the app config for the current request.
4 ModuleConfig ac = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
5
6 // DiskFileItem工廠,主要用來設(shè)定上傳文件的參數(shù)
7 DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
8
9 // 上傳文件所用到的緩沖區(qū)大小,超過此緩沖區(qū)的部分將被寫入到磁盤
10 fileItemFactory.setSizeThreshold((int) this.getSizeThreshold(ac));
11
12 // 上傳文件用到的臨時(shí)文件存放位置
13 fileItemFactory.setRepository(this.getRepository(ac));
14
15 // 使用fileItemFactory為參數(shù)實(shí)例化一個(gè)ServletFileUpload對象
16 // 注意:該對象為commons-fileupload-1.2新增的類.
17 // 對于1.2以下的commons-fileupload版本并不存在此類.
18 ServletFileUpload upload = new ServletFileUpload(fileItemFactory);
19
20 // 從session中讀取對本次上傳文件的最大值的限制
21 String maxUploadSize = (String)request.getSession().
22 getAttribute(BasicConstants.maxUploadSize);
23
24 // 獲取struts-config文件中controller標(biāo)簽的maxFileSize屬性來確定默認(rèn)上傳的限制
25 // 如果struts-config文件中controller標(biāo)簽的maxFileSize屬性沒設(shè)置則使用默認(rèn)的上傳限制250M.
26 long defaultOrConfigedMaxUploadSize = this.getSizeMax(ac);
27 if (maxUploadSize != null && maxUploadSize != "")
28 {
29 // 如果maxUploadSize設(shè)定不正確則上傳限制為defaultOrConfigedMaxUploadSize的值
30
31 // 正確則為maxUploadSize轉(zhuǎn)換成的字節(jié)數(shù)
32 upload.setSizeMax((long) this.convertSizeToBytes(
33 maxUploadSize, defaultOrConfigedMaxUploadSize));
34
35 }
36 else
37 {
38 // 如果maxUploadSize沒設(shè)置則使用默認(rèn)的上傳限制
39 upload.setSizeMax(defaultOrConfigedMaxUploadSize);
40 }
41
42 // 從session中清空maxUploadSize
43 request.getSession().removeAttribute("maxUploadSize");
44
45 // Create the hash tables to be populated.
46 elementsText = new Hashtable();
47 elementsFile = new Hashtable();
48 elementsAll = new Hashtable();
49
50 // Parse the request into file items.
51 List items = null;
52 // ServletFileUpload類來處理表單請求
53 // 拋出的異常為FileUploadException的子異常
54 // 如果捕獲這些異常就將捕獲的異常放到session中返回.
55
56 try
57 {
58 items = upload.parseRequest(request);
59
60 }
61 catch (FileUploadBase.SizeLimitExceededException e)
62 {
63
64 // 請求數(shù)據(jù)的size超出了規(guī)定的大小.
65 request.getSession().setAttribute(
66 BasicConstants.baseSizeLimitExceededException, e);
67 return;
68 }
69 catch (FileUploadBase.FileSizeLimitExceededException e)
70 {
71 // 請求文件的size超出了規(guī)定的大小.
72 request.getSession().setAttribute(
73 BasicConstants.baseFileSizeLimitExceededException, e);
74 return;
75 }
76 catch (FileUploadBase.IOFileUploadException e)
77 {
78 // 文件傳輸出現(xiàn)錯(cuò)誤,例如磁盤空間不足等.
79 request.getSession().setAttribute(
80 BasicConstants.baseIOFileUploadException, e);
81 return;
82 }
83 catch (FileUploadBase.InvalidContentTypeException e)
84 {
85 // 無效的請求類型,即請求類型enctype != "multipart/form-data"
86 request.getSession().setAttribute(
87 BasicConstants.baseInvalidContentTypeException, e);
88 return;
89 }
90 catch (FileUploadException e)
91 {
92 // 如果都不是以上子異常,則拋出此總的異常,出現(xiàn)此異常原因無法說明.
93 request.getSession().setAttribute(
94 BasicConstants.FileUploadException, e);
95 return;
96 }
97
98 // Partition the items into form fields and files.
99 Iterator iter = items.iterator();
100
101 while (iter.hasNext())
102 {
103 FileItem item = (FileItem) iter.next();
104
105 if (item.isFormField())
106 {
107 addTextParameter(request, item);
108 }
109 else
110 {
111 addFileParameter(item);
112 }
113 }
114 }
115
1 public void handleRequest(HttpServletRequest request) throws ServletException
2 {
3 // Get the app config for the current request.
4 ModuleConfig ac = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
5
6 // DiskFileItem工廠,主要用來設(shè)定上傳文件的參數(shù)
7 DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
8
9 // 上傳文件所用到的緩沖區(qū)大小,超過此緩沖區(qū)的部分將被寫入到磁盤
10 fileItemFactory.setSizeThreshold((int) this.getSizeThreshold(ac));
11
12 // 上傳文件用到的臨時(shí)文件存放位置
13 fileItemFactory.setRepository(this.getRepository(ac));
14
15 // 使用fileItemFactory為參數(shù)實(shí)例化一個(gè)ServletFileUpload對象
16 // 注意:該對象為commons-fileupload-1.2新增的類.
17 // 對于1.2以下的commons-fileupload版本并不存在此類.
18 ServletFileUpload upload = new ServletFileUpload(fileItemFactory);
19
20 // 從session中讀取對本次上傳文件的最大值的限制
21 String maxUploadSize = (String)request.getSession().
22 getAttribute(BasicConstants.maxUploadSize);
23
24 // 獲取struts-config文件中controller標(biāo)簽的maxFileSize屬性來確定默認(rèn)上傳的限制
25 // 如果struts-config文件中controller標(biāo)簽的maxFileSize屬性沒設(shè)置則使用默認(rèn)的上傳限制250M.
26 long defaultOrConfigedMaxUploadSize = this.getSizeMax(ac);
27 if (maxUploadSize != null && maxUploadSize != "")
28 {
29 // 如果maxUploadSize設(shè)定不正確則上傳限制為defaultOrConfigedMaxUploadSize的值
30
31 // 正確則為maxUploadSize轉(zhuǎn)換成的字節(jié)數(shù)
32 upload.setSizeMax((long) this.convertSizeToBytes(
33 maxUploadSize, defaultOrConfigedMaxUploadSize));
34
35 }
36 else
37 {
38 // 如果maxUploadSize沒設(shè)置則使用默認(rèn)的上傳限制
39 upload.setSizeMax(defaultOrConfigedMaxUploadSize);
40 }
41
42 // 從session中清空maxUploadSize
43 request.getSession().removeAttribute("maxUploadSize");
44
45 // Create the hash tables to be populated.
46 elementsText = new Hashtable();
47 elementsFile = new Hashtable();
48 elementsAll = new Hashtable();
49
50 // Parse the request into file items.
51 List items = null;
52 // ServletFileUpload類來處理表單請求
53 // 拋出的異常為FileUploadException的子異常
54 // 如果捕獲這些異常就將捕獲的異常放到session中返回.
55
56 try
57 {
58 items = upload.parseRequest(request);
59
60 }
61 catch (FileUploadBase.SizeLimitExceededException e)
62 {
63
64 // 請求數(shù)據(jù)的size超出了規(guī)定的大小.
65 request.getSession().setAttribute(
66 BasicConstants.baseSizeLimitExceededException, e);
67 return;
68 }
69 catch (FileUploadBase.FileSizeLimitExceededException e)
70 {
71 // 請求文件的size超出了規(guī)定的大小.
72 request.getSession().setAttribute(
73 BasicConstants.baseFileSizeLimitExceededException, e);
74 return;
75 }
76 catch (FileUploadBase.IOFileUploadException e)
77 {
78 // 文件傳輸出現(xiàn)錯(cuò)誤,例如磁盤空間不足等.
79 request.getSession().setAttribute(
80 BasicConstants.baseIOFileUploadException, e);
81 return;
82 }
83 catch (FileUploadBase.InvalidContentTypeException e)
84 {
85 // 無效的請求類型,即請求類型enctype != "multipart/form-data"
86 request.getSession().setAttribute(
87 BasicConstants.baseInvalidContentTypeException, e);
88 return;
89 }
90 catch (FileUploadException e)
91 {
92 // 如果都不是以上子異常,則拋出此總的異常,出現(xiàn)此異常原因無法說明.
93 request.getSession().setAttribute(
94 BasicConstants.FileUploadException, e);
95 return;
96 }
97
98 // Partition the items into form fields and files.
99 Iterator iter = items.iterator();
100
101 while (iter.hasNext())
102 {
103 FileItem item = (FileItem) iter.next();
104
105 if (item.isFormField())
106 {
107 addTextParameter(request, item);
108 }
109 else
110 {
111 addFileParameter(item);
112 }
113 }
114 }
115
其它部分均未做什么大改變。 好了,替代類寫好了,我們怎么去用呢? 這樣:在struts-config文件中寫配置:
1 <!-- ========== Controller Configuration ================================ -->
2
3 <controller>
4
5 <!-- The "input" parameter on "action" elements is the name of a
6
7 local or global "forward" rather than a module-relative path -->
8
9 <set-property value="true" property="inputForward" />
10 <set-property value="text/html; charset=UTF-8"
11 property="contentType" />
12 <!-- 通過寫類的全名來替代struts默認(rèn)的MultipartRequestHandler -->
13
14 <set-property property="multipartClass"
15 value="com.amplesky.commonmodule.struts.AmpleskyMultipartRequestHandler" />
16 <!-- 規(guī)定的上傳文件的最大值 -->
17 <set-property property="maxFileSize" value="15M" />
18 <!-- 緩沖區(qū)大小 -->
19 <set-property property="memFileSize" value="5M" />
20 </controller>
21
1 <!-- ========== Controller Configuration ================================ -->
2
3 <controller>
4
5 <!-- The "input" parameter on "action" elements is the name of a
6
7 local or global "forward" rather than a module-relative path -->
8
9 <set-property value="true" property="inputForward" />
10 <set-property value="text/html; charset=UTF-8"
11 property="contentType" />
12 <!-- 通過寫類的全名來替代struts默認(rèn)的MultipartRequestHandler -->
13
14 <set-property property="multipartClass"
15 value="com.amplesky.commonmodule.struts.AmpleskyMultipartRequestHandler" />
16 <!-- 規(guī)定的上傳文件的最大值 -->
17 <set-property property="maxFileSize" value="15M" />
18 <!-- 緩沖區(qū)大小 -->
19 <set-property property="memFileSize" value="5M" />
20 </controller>
21
好了!現(xiàn)在我們再用FormFile上傳文件,
可以在上傳之前動(dòng)態(tài)設(shè)置或者從配置文件設(shè)置上傳文件的大小,一旦上傳過程中出現(xiàn)了異常,就會被寫入request的attributs里。當(dāng)進(jìn)入action的時(shí)候,通過在Action里獲取異常就可以判斷上傳過程中出了什么問題了,而且在上傳過程中文件一旦超出了規(guī)定大小,或者磁盤大小不足的情況會立即中斷上傳的。這樣我們的功能就實(shí)現(xiàn)了。 最后,感慨一下,
1.感覺commons-fileupload還是挺好用的,但是FormFile的使用不大好,基本上誤導(dǎo)能力很強(qiáng),網(wǎng)上關(guān)于FormFile的資料說明很少,以上這些都是我自己摸索出來的。 2.Struts 1都到了1.3版本了,但是對于FormFile的實(shí)現(xiàn)依然使用commons-fileupload-1.0版本的DiskFileUpload類,可見更新也不怎么樣。其實(shí)我覺得這是apache大部分開源工具的一個(gè)通病:更新確實(shí)不怎么快,commons框架里邊的源代碼和jar包不一致,還有很多是2004或者2003年的…要命的是居然不支持javase5的新特性???? 3. 認(rèn)為把異常放到request的attributs里不好,曾經(jīng)嘗試過拋出異常然后通過配置exception-controller來抓去異常處理,但是抓不到,怎么都抓不到,后來翻了大半天源碼才發(fā)現(xiàn):struts在處理異常請求的時(shí)候?qū)⒊霈F(xiàn)的ServletException和IOExcepton都交給了上層去處理了,根本不會拋出來。所以這兩種異常是抓不到的。