beauty_beast

上善若水 厚德载物

采用HTTP协议上传文件实现(java)

Posted on 2005-10-13 21:41 柳随风 阅读(7115) 评论(1)  编辑  收藏
    j2ee开发也好几年,文件上传功能基本都是用的第三方的组件,虽然知道其原理,但一直不知道具体是如何实现的,最近有时间,正好同事开发遇到这方面的问题,查了点资料,基本明白了具体实现,为了备忘,就写下这篇随笔。
    首先说说同事遇到的问题,最近的项目是使用webwork开发的,同事需要实现多文件上传的功能,但是
webwork原则上支持三种上传解析 pell,cos,jakarta,三种都有自身的优势和不足,使用发现:
在webwork中只有pell支持中文文件路径,但是使用该方式只能处理一个上传文件,
而其他两种虽然支持多文件上传,但中文支持不好。

上述的见解都是本人的个人认识,可能支持只是我不知道,如果有谁知道不妨指导一下,不胜感激。
上传解析的实现简单说一下:
       通过ServletRequest类的getInputStream()方法获得一个客户端向服务器发出的数据流、分析上传的文件格式,根据分析结果将多个文件依次输出服务器端的目标文件中。
       格式类似下面:
//文件分隔符
-----------------------------7d226137250336 
//文件信息头
Content-Disposition: form-data; name="FILE1"; filename="C:\Documents and Settings\Administrator.TIMBER-4O6B0ZZ0\My Documents\tt.sql"
Content-Type: text/plain
//源文件内容 
create table info(
content image null);
//下一个文件的分隔符
-----------------------------7d226137250336
Content-Disposition: form-data; name="FILE2"; filename=""
Content-Type: application/octet-stream
-----------------------------7d226137250336 

每个表单提交的元素都有分隔符将其分隔,其提交的表单元素的名称和对应的输入值之间也有特殊的字符将其分隔开。

 都知道格式了,呵呵就尝试了一下,参照了pell中的MultipartRequest类写了一个上传组件(本来不想自己写的,想改造改造就完事的,可惜反编译出来的代码比较难读),代码如下:

  1/*
  2 * 只支持在windows下上传文件
  3 * Created on 2005-10-10
  4 *
  5 * TODO To change the template for this generated file go to
  6 * Window - Preferences - Java - Code Style - Code Templates
  7 */

  8package study.http.upload;
  9
 10import java.io.BufferedInputStream;
 11import java.io.File;
 12import java.io.FileNotFoundException;
 13import java.io.FileOutputStream;
 14import java.io.IOException;
 15import java.io.InputStream;
 16import java.io.UnsupportedEncodingException;
 17import java.util.ArrayList;
 18import java.util.Hashtable;
 19import java.util.Iterator;
 20import java.util.List;
 21import java.util.Map;
 22import java.util.Set;
 23
 24import javax.servlet.ServletException;
 25import javax.servlet.ServletInputStream;
 26import javax.servlet.http.HttpServlet;
 27import javax.servlet.http.HttpServletRequest;
 28import javax.servlet.http.HttpServletResponse;
 29
 30/**
 31 * @author liusuifeng
 32 * 
 33 * TODO To change the template for this generated type comment go to Window -
 34 * Preferences - Java - Code Style - Code Templates
 35 */

 36public class TestServlet extends HttpServlet {
 37
 38    public final static String DEFAULT_ENCODING = "ISO8859_1";
 39
 40    public final static String CHINESE_ENCODING = "GBK";
 41
 42    public final static String SIGN_BOUNDARY = "boundary=";
 43
 44    public final static String SIGN_FORMELEMENT = "name=";
 45
 46    public final static String SIGN_FORMFILE = "filename=";
 47
 48    public final static String SIGN_NOTFILE = "application/octet-stream";
 49
 50    public final static String SIGN_MULTIDATA = "multipart/form-data";
 51
 52    public final static String CHINESE_CONTENTTYPE = "text/html; charset=GBK";
 53
 54    private Hashtable paratable = new Hashtable();
 55
 56    private Hashtable filetable = new Hashtable();
 57
 58    private String strBoundary = "";
 59    
 60    private String strSavePath="";
 61    
 62
 63    private static void println(String s) {
 64        System.out.println(s);
 65    }

 66    
 67    
 68    
 69
 70    /**
 71     * 增加数据到对应的Hashtable中
 72     * 说明:如果Hashtable中已存在该键值,则将新增加的和原来的都封装到列表中。
 73     * @param table    
 74     * @param paraName
 75     * @param paraValue
 76     */

 77    private static void addElement(Hashtable table, String paraName,
 78            Object paraValue) {
 79        ArrayList list = new ArrayList();
 80        if (table.containsKey(paraName)) {
 81            Object o = table.get(paraName);
 82            if (o instanceof List) {
 83                ((List) o).add(paraValue);
 84            }
 else {
 85                list.add(o);
 86                list.add(paraValue);
 87                o = list;
 88            }

 89            table.put(paraName, o);
 90        }
 else {
 91            table.put(paraName, paraValue);
 92        }

 93    }

 94
 95    public static String getHashInfo(Hashtable paratable){
 96        StringBuffer sb=new StringBuffer();
 97        Set keySet=paratable.keySet();
 98        Iterator it=keySet.iterator();
 99        while(it.hasNext()){
100            
101            Object keyobj=it.next();
102            Object valueobj=paratable.get(keyobj);
103            
104            sb.append("<tr>");
105            sb.append("<td>"+keyobj.toString()+"</td>");
106            if(valueobj instanceof List){
107                sb.append("<td>");
108                int isize=((List)valueobj).size();
109                for(int i=0;i<isize;i++){
110                    Object tempobj=((List)valueobj).get(i);
111                    if(i<isize-1){
112                       sb.append(tempobj.toString()+",");
113                    }

114                    else{
115                       sb.append(tempobj.toString());
116                    }

117                }

118                
119                sb.append("</td>");
120            }

121            else{
122                sb.append("<td>"+valueobj.toString()+"</td>");
123            }

124            sb.append("</tr>");
125        }

126        return sb.toString();
127    }

128    
129    
130    private static byte[] getfileBytes(InputStream is) {
131        List byteList = new ArrayList();
132        byte[] filebyte = null;
133        int readbyte = 0;
134        try {
135            while ((readbyte = is.read()) != -1{
136                byteList.add(new Byte((byte) readbyte));
137            }

138        }
 catch (FileNotFoundException e) {
139            e.printStackTrace();
140        }
 catch (IOException e) {
141            e.printStackTrace();
142        }

143        filebyte = new byte[byteList.size()];
144        for (int i = 0; i < byteList.size(); i++{
145            filebyte[i] = ((Byte) byteList.get(i)).byteValue();
146        }

147        return filebyte;
148
149    }

150    
151    
152
153    
154    protected void doGet(HttpServletRequest request,
155            HttpServletResponse response) throws ServletException, IOException {
156        doPost(request, response);
157    }

158
159    protected void doPost(HttpServletRequest request,
160            HttpServletResponse response) throws ServletException, IOException {
161        paratable = new Hashtable();
162        filetable = new Hashtable();
163        strSavePath=this.getInitParameter("savepath");
164        File file=new File(strSavePath);
165        if(!file.exists()){
166            file.mkdirs();
167        }

168        String contentType = request.getContentType();    
169        strBoundary = getBoundary(contentType);
170        ServletInputStream sis = request.getInputStream();
171        BufferedInputStream bis = new BufferedInputStream(sis);
172        parseInputStream(bis);
173        appendPara(request.getParameterMap());  /*追加url对应传递的参数*/
174        response.setContentType(CHINESE_CONTENTTYPE);
175        
176//        response.getWriter().write(getOutPutInfo());
177//        response.getWriter().write(new String(getfileBytes(sis),"GBK"));
178        bis.close();
179        sis.close();
180        request.setAttribute("para",paratable);
181        request.setAttribute("file",filetable);
182        
183        this.getServletContext().getRequestDispatcher("/result.jsp").
184        forward(request,response);
185        
186    }

187    
188    
189    /**
190     * 不用Hashtable对应的put方法,目的避免覆盖重复的键值
191     * @return
192     */

193    private void appendPara(Map map){
194        
195        if(map!=null){
196            Set keySet=map.keySet();
197            Iterator it=keySet.iterator();
198            while(it.hasNext()){
199                Object keyobj=it.next();
200                String[] valueobj=(String[])map.get(keyobj);
201                println("keyobj===="+keyobj);
202                println("valueobj===="+valueobj);
203                for(int i=0;i<valueobj.length;i++){
204                    addElement(paratable,(String)keyobj,valueobj[i]);
205                }

206            }

207        }

208    }

209    
210    
211
212    /**
213     * 输出上传表单信息
214     * 
215     * @param pw
216     */

217    protected String getOutPutInfo() {
218        StringBuffer sb = new StringBuffer();
219        sb.append("<table width=100% border=1>");
220        sb.append("<tr><td>参数名</td><td>参数值</td></tr>");
221        sb.append(getHashInfo(paratable));
222        sb.append(getHashInfo(filetable));
223        sb.append("</table>");
224        return sb.toString();
225    }

226
227    /**
228     * 解析字节流
229     * @param is
230     */

231    private void parseInputStream(InputStream is) {
232        byte[] sizes = getfileBytes(is);
233        int icount = 0;
234        String s = "";
235        int readbyte = 0;
236        String reals;
237        try {
238            reals = new String(sizes, DEFAULT_ENCODING);
239            String realsvalue = new String(sizes, CHINESE_ENCODING);
240            String[] arrs = reals.split(strBoundary);
241            String[] arrsvalue = realsvalue.split(strBoundary);
242            for (int i = 0; i < arrs.length; i++{
243                String tempStr = arrs[i];
244                String tempStr2 = arrsvalue[i];
245                if (tempStr.indexOf(SIGN_FORMFILE) >= 0{
246                    readFile(tempStr, tempStr2);
247                }
 else {
248                    readParameter(tempStr2);
249                }

250            }

251        }
 catch (UnsupportedEncodingException e) {
252            e.printStackTrace();
253        }

254
255    }

256
257    /**
258     * 获取本次上传对应的表单元素间的分隔符,注意该分隔符是随机生成的
259     * @param contentType   
260     * @return
261     */

262    private String getBoundary(String contentType) {
263        String tempStr = "";
264        if (contentType != null && contentType.startsWith(SIGN_MULTIDATA)
265                && contentType.indexOf(SIGN_BOUNDARY) != -1{
266            //获取表单每个元素的分隔符
267            tempStr = contentType
268                    .substring(
269                            contentType.indexOf(SIGN_BOUNDARY)
270                                    + SIGN_BOUNDARY.length()).trim();
271        }

272        return tempStr;
273    }

274
275    /**
276     * 解析文件上传对应的字节流。实现算法<br>
277     * 通过解析ISO8859_1编码方式的字符串后转换成对应上传文件的字节。
278     * 通过解析GBK编码方式的字符串后转换成对应上传文件的文件名。
279     * 说明:因不清楚字节在不同编码方式下的关系,只好使用两个字符串(比较影响性能,以后优化)
280     * @param s   以ISO8859_1编码方式组成的字符串
281     * @param s2  以GBK编码方式组成的字符串
282     */

283    private void readFile(String s, String s2) {
284        int filepos = -1;
285        if ((filepos = s.indexOf(SIGN_FORMFILE)) >= 0{
286            String realName = readFileName(s2);
287            //部分确定上传的是文件而不是任意输入的字符串
288            if(!realName.equals("")&& realName.length()>0 && (realName.indexOf(".")>=0)){
289                String filepath = readWriteFile(s, realName);
290                addElement(filetable, realName, filepath);
291            }

292        }
 
293        else {
294            /*上传的不是文件*/
295            if (s.indexOf(SIGN_NOTFILE) >= 0{
296                return;
297            }

298        }

299
300    }

301    
302    /**
303     * 解析文件上传对应的名称 
304     * 实现说明:如果上传的是文件对应格式为:<br>filename="文件名"</br> 格式
305     * 通过处理可以拆分出对应的文件名  
306     * @param s   以GBK编码方式组成的包含文件名的字符串
307     * @return    对应上传文件的文件名(不包括文件路径)
308     */

309    private String readFileName(String s) {
310        int filepos = s.indexOf(SIGN_FORMFILE);
311        String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1);
312        int iendpos = tempstr.indexOf("\"");
313        String fileName = tempstr.substring(0, iendpos);
314        int ifilenamepos = fileName.lastIndexOf("\\");
315        String realName = fileName.substring(ifilenamepos + 1);        
316        return realName;
317
318    }

319
320    /**
321     * 通过解析ISO8859_1编码方式的字符串后转换成对应上传文件的字节。
322     * 实现算法说明:文件名转化后的字节和具体的文件字节中间是以两个重复的两个字符隔开,
323     * 对应char值为13,10,转换后的字符对应的最后四个字符也是格式字符,获取对应中间的字节即为
324     * 上传文件的真正的字节数
325     * @param s        以ISO8859_1编码方式组成的包含文件名和具体文件字节的字符串
326     * @param realName  对应的文件名
327     * @return          对应生成的文件名包括全路径
328     */

329    private String readWriteFile(String s, String realName) {
330        int filepos = s.indexOf(SIGN_FORMFILE);
331        String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1);
332        int icount = 0;
333        while (true{
334            int charnum = tempstr.charAt(icount);
335            int charnum2 = tempstr.charAt(icount + 1);
336            int charnum3 = tempstr.charAt(icount + 2);
337            int charnum4 = tempstr.charAt(icount + 3);
338            if (charnum == 13 && charnum2 == 10 && charnum3 == 13
339                    && charnum4 == 10{
340                break;
341            }

342            icount++;
343        }

344        String filevalue = tempstr.substring(icount + 4, tempstr.length() - 4);
345        FileOutputStream fos = null;
346        String createName=strSavePath + realName;
347        File uploadfile = new File(createName);        
348        String shortname=realName.substring(0,realName.lastIndexOf("."));
349        String filetype=realName.substring(realName.lastIndexOf(".")+1);
350        int namecount=1;
351        while(uploadfile.exists()){            
352            createName=strSavePath+shortname+"["+namecount+"]"+"."+filetype;
353            uploadfile=new File(createName);
354            namecount++;
355            
356        }

357        try {
358            byte[] filebytes = filevalue.getBytes(DEFAULT_ENCODING);
359            fos = new FileOutputStream(uploadfile);
360            fos.write(filebytes);
361        }
 catch (FileNotFoundException e) {
362            e.printStackTrace();
363        }
 catch (IOException e1) {
364
365            e1.printStackTrace();
366        }
 finally {
367            try {
368                fos.close();
369            }
 catch (IOException e2) {
370
371                e2.printStackTrace();
372            }

373        }

374
375        return createName;
376    }

377
378    
379    /**
380     * 解析提交过来的表单元素对应的名称以及值<br> 
381     * 实现说明:如果表单元素的是对应格式为:<br>name="表单元素名"</br> 格式
382     * 表单元素名和具体的输入值中间是以两个重复的两个字符隔开,
383     * 对应char值为13,10,转换后的字符对应的最后四个字符也是格式字符,获取对应中间的字符即为
384     * 表单元素的输入值
385     * 通过处理可以拆分出对应的表单元素名以及输入值  
386     * @param s   以GBK编码方式组成的包含表单元素名和值的字符串    
387     */
    
388    private void readParameter(String s) {
389        String paraName = "";
390        String paraValue = "";
391        int istartlen = -1;
392        int iendlen = -1;
393
394        if ((istartlen = s.indexOf(SIGN_FORMELEMENT)) >= 0{
395            String tempstr = s.substring(istartlen + SIGN_FORMELEMENT.length()
396                    + 1);
397            int nameindex = tempstr.indexOf("\"");
398            paraName = tempstr.substring(0, nameindex);
399            paraValue = tempstr.substring(nameindex + 5, tempstr.length() - 4);
400            addElement(paratable, paraName, paraValue);
401        }

402    }

403
404}

组件简单说明:
       上传路径在servlet初始参数中设定。
       上传的表单元素、文件数据分别封装在Hashtable中。
做了测试,测试环境说明:
 AppServer: WeblogicSP4
 OS:  WindowXP/ Soloaris 9.0
测试程序:
index.jsp(文件上传页面):
<html>
  
<head><title>File Upload</title></head>
  
<body>

  
<form  name="kkkkkk"     action="test.upload?ssss=bbbbbbbbb&ccccc=eeeeeeee" enctype="multipart/form-data" method="post" >
      
<input type=text name="ssss" ><br>
      
<input type=text name="ssss" ><br>
      
<input type=text name="ssss3" ><br>
      
<textarea name="araea"></textarea><br>
      
      
<input type=file name="cccc"  ><br>
       
<input type=file name="ddddd" ><br>
      
<input type=submit value="submit" name="bbbbbbbbb">     
  
</form>

  
</body>
</html>

result.jsp(查看提交表单数据)
<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.*"%> 
<%@ page import="study.http.upload.*"%> 



<%
  Hashtable paratable
=(Hashtable)request.getAttribute("para");
  Hashtable filetable
=(Hashtable)request.getAttribute("file");
  
String parastr=TestServlet.getHashInfo(paratable);
  out.println(
"<table width=100% border=1>");
  out.println(parastr);
  out.println(TestServlet.getHashInfo(filetable));
  out.println(
"</table>");
 
%>
<html>
  
<head><title>File Upload</title></head>
  
<body>

  

  
</body>
</html>


测试时对应的web应用已经指定相关字符集,weblogic.xml内容如下:
<weblogic-web-app>
  
<charset-params>
        
<input-charset>
            
<resource-path>/*</resource-path>
            
<java-charset-name>GBK</java-charset-name>
        
</input-charset>
    
</charset-params>
</weblogic-web-app>



测试运行基本正常,总算解决了心中的一个很长时间的困惑。

注:以上的程序只是本人学习的代码,有很多欠缺的地方,欢迎大家指正,不甚感激。
         上面代码只供参考,嘿嘿出了问题我就不负责了。

最近没啥事,又出差在外,唉日子过得好慢!!!!!

















  




















        

Feedback

# re: 采用HTTP协议上传文件实现(java)  回复  更多评论   

2006-09-27 14:28 by testdfsdgf
good

只有注册用户登录后才能发表评论。


网站导航: