关于HTTP POST上传文件时服务端的接收问题

wudeaaa 2010-11-10 08:59:50
HTTP POST方法上传文件,客户端是socket发送,服务端Servlet接收,请求报文头如下:

POST /upload.jsp HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Content-Type: multipart/form-data; boundary=---------------------------7da29f2d890386
Host: 192.9.200.246:8080
Content-Length: 1516663
Connection: Keep-Alive
Cache-Control: no-cache

-----------------------------7da29f2d890386
Content-Disposition: form-data; name="FileData"; filename="D:\set.pdf"
Content-Type: application/pdf

%PDF-1.6
%����
1 0 obj<</Type/Page/Contents 2 0 R/Parent 3 0 R/Resources 4 0 R/CropBox[0 0 595.22 842]/MediaBox[0 0 595.22 842]/Rotate 0>>
endobj
4 0 obj<</ColorSpace<</Cs6 5 0 R>>/ExtGState<</GS1 6 0 R>>/ProcSet[/PDF/Text]/Font<</F1 7 0 R/F3 8 0 R/TT3 9 0 R/F2 10 0 R>>>>
endobj
2 0 obj <</Filter/FlateDecode/Length 1887>>stream
-----------------------------7da29f2d890386--

服务端接收如下

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
final int NONE=0;
final int DATAHEADER=1;
final int FILEDATA=2;
final int FIELDDATA=3;
//将请求消息的实体送到b变量中
int TotalBytes=request.getContentLength();
byte[] b=new byte[TotalBytes];
String contentType=request.getContentType();//请求消息类型
String fieldname=""; //表单域的名称
String fieldvalue=""; //表单域的值
String filename=""; //文件名
String boundary=""; //分界符
String lastboundary=""; //结束符
int filesize=0; //文件长度
Hashtable<String, String> formfields=new Hashtable<String, String>();
int pos=contentType.indexOf("boundary=");
//String fileID; //上传的文件ID
if(pos!=-1) //取得分界符和结束符
{
pos+="boundary=".length();
boundary="--"+contentType.substring(pos);
lastboundary=boundary+"--";
}
int state=NONE;
//得到数据输入流reqbuf
DataInputStream in=new DataInputStream(request.getInputStream());
in.readFully(b);
in.close();
String reqContent=new String(b,"UTF-8");//********如果文件过大会造成内容溢出,但是如果没有读入全部,又无法过滤消息头而得到内容字节数组*********/
BufferedReader reqbuf=new BufferedReader(new StringReader(reqContent));
boolean flag=true;
int i=0;
while(flag==true)
{
String s=reqbuf.readLine();
if((s==lastboundary)||(s==null)) break;
switch(state)
{
case NONE:
if (s.startsWith(boundary))
{
state=DATAHEADER;
i+=1;
}
break;
case DATAHEADER:
pos=s.indexOf("filename=");
if (pos==-1)
{ //将表单域的名字解析出来
pos=s.indexOf("name=");
pos+="name=".length()+1;
s=s.substring(pos);
int l=s.length();
s=s.substring(0,l-1);
fieldname=s;
state=FIELDDATA;
}
else
{ //将文件名解析出来
String temp=s;
pos=s.indexOf("filename=");
pos+="filename=".length()+1;
s=s.substring(pos);
int l=s.length();
s=s.substring(0,l-1);
pos=s.lastIndexOf("\\");
s=s.substring(pos+1);
filename=s;
//从字节数组中取出文件数组
pos=byteIndexOf(b,temp,0);
b=subBytes(b,pos+temp.getBytes().length+2,b.length);//去掉前面的部分
s=reqbuf.readLine();
b=subBytes(b,s.getBytes().length+4,b.length);
pos=byteIndexOf(b,boundary,0);
b=subBytes(b,0,pos-1);
File f=new File(formfields.get("FilePath")+"\\"+filename); //写入文件
DataOutputStream fileout=new DataOutputStream(new FileOutputStream(f));
fileout.write(b,0,b.length-1);
filesize=b.length-1;
state=FILEDATA;
}
break;
case FIELDDATA:
s=reqbuf.readLine();
fieldvalue=s;
formfields.put(fieldname,fieldvalue);
state=NONE;
break;
case FILEDATA:
while((!s.startsWith(boundary))&&(!s.startsWith(lastboundary)))
{
s=reqbuf.readLine();
if (s.startsWith(boundary))
{
state=DATAHEADER;
break;
}
}
break;
}
}
}

问题:服务端既想一下读到全部字符串(用来解析消息头,然后过滤掉得到字节数组),又想保存完整的字节数组byte[] b(b用来去除消息头后写入文件),如果文件过大,这里就会造成内存溢出,但是如果没有一下子读完,后面又无法解析,大家看一下有没有更好的解决方法。

DataInputStream in=new DataInputStream(request.getInputStream());
in.readFully(b);
in.close();
String reqContent=new String(b,"UTF-8");//会造成内存溢出
...全文
792 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
wudeaaa 2010-11-11
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 zhblue 的回复:]
先读1-2k字节,应该已经包括头了,取出form字段,已读取的去掉字段信息剩下的找出属于文件的内容,写入文件,再继续处理剩下的。
[/Quote]读取到的文件有误,不知道哪里出错了
paodan 2010-11-11
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 zhblue 的回复:]
建一个临时文件,追加写入,完成后移动到目标位置(目录、数据库、网络......)


java.io.BufferedOutputStream
包装
java.io.FileOutputStream

读System.getenv()找临时目录
[/Quote]对于有格式的文件如何追加?比如word,pdf
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
顶啦,但是这样解析的很麻烦
歪嘴鱼 2010-11-10
  • 打赏
  • 举报
回复
先读1-2k字节,应该已经包括头了,取出form字段,已读取的去掉字段信息剩下的找出属于文件的内容,写入文件,再继续处理剩下的。
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 zhblue 的回复:]
建一个临时文件,追加写入,完成后移动到目标位置(目录、数据库、网络......)


java.io.BufferedOutputStream
包装
java.io.FileOutputStream

读System.getenv()找临时目录
[/Quote]恩,谢谢,关键在解析报文的时候怎么做?
amsuker 2010-11-10
  • 打赏
  • 举报
回复
将贴顶上去,然后我就有积分了
歪嘴鱼 2010-11-10
  • 打赏
  • 举报
回复
建一个临时文件,追加写入,完成后移动到目标位置(目录、数据库、网络......)


java.io.BufferedOutputStream
包装
java.io.FileOutputStream

读System.getenv()找临时目录
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
求解,大家有没有好的方法。
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 qingkangxu 的回复:]
>关于内存溢出
int TotalBytes=request.getContentLength();
byte[] b=new byte[TotalBytes];
以上代码,文件较大的时候肯定是要内存溢出的,这是一个基础问题,没有人会这样做的。如果你这样写程序,那么你的程序一点都经不起考验。一般申请内存都是new byte[4096]/new byte[2048]等,你一下子申请那么……
[/Quote]是这样的,这只是一个测试而已,你的意思是按照字节读取,当读到CRLF的表示一行结束,然后再解析这一行的字符串?
qingkangxu 2010-11-10
  • 打赏
  • 举报
回复
>关于内存溢出
int TotalBytes=request.getContentLength();
byte[] b=new byte[TotalBytes];
以上代码,文件较大的时候肯定是要内存溢出的,这是一个基础问题,没有人会这样做的。如果你这样写程序,那么你的程序一点都经不起考验。一般申请内存都是new byte[4096]/new byte[2048]等,你一下子申请那么大的内存块肯定不行。
>关于你想解析的问题
你解析的应该是form表单域的数据,那么你最好还是按照字节来解析吧,遇到特殊分隔符号才意味着某个字符串的结束,你想直接把byte读入String,然后判断String的条件看来好像不是很合适。具体我也不是特清楚了,只是感觉比如form表单域,每一个项目都应该是以【CRLF】---【0D0A】为分隔符的。
以上你可能看不明白,不过我也不知道怎么表达好...
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 stl0 的回复:]
引用 5 楼 wudeaaa 的回复:

消息头可以一行一行读取,但是文件(二进制数据)一行一行读取然后再写入就会有问题。

POST数据的本体部分 也就是文件部分,你现在是一行行读取,一行行的写入吧

为什么不读取每行之后,存到一个object中,然后统一写入文件呢?
[/Quote]首先是把所有数据(头和实体)全部读入到一个byte数组中,然后由byte数组生成一个字符串,由
字符串得到字符流BufferedReader,然后用 reader.readline()来读取请求头,并从byte数组中过滤掉,
最后把过滤后的byte数组写入文件。
stl0 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 wudeaaa 的回复:]

消息头可以一行一行读取,但是文件(二进制数据)一行一行读取然后再写入就会有问题。
[/Quote]
POST数据的本体部分 也就是文件部分,你现在是一行行读取,一行行的写入吧

为什么不读取每行之后,存到一个object中,然后统一写入文件呢?
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
消息头可以一行一行读取,但是文件(二进制数据)一行一行读取然后再写入就会有问题。
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 stl0 的回复:]
我觉得处理都放在while(flag==true)循环中来做,是导致内存溢出的原因,对于post数据,可以一行一行的读啊

Java code

while ((s = reader.readLine()) != null) {
....(具体处理)
}
[/Quote]一行一行处理的话,用BufferedWriter.write(s)写入文件就会有错误了(字符编码,格式等问题)
stl0 2010-11-10
  • 打赏
  • 举报
回复
我觉得处理都放在while(flag==true)循环中来做,是导致内存溢出的原因,对于post数据,可以一行一行的读啊

while ((s = reader.readLine()) != null) {
....(具体处理)
}
wudeaaa 2010-11-10
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 stl0 的回复:]
可以先获取总体的字节数吧,只读取相应字节数的数据。

文件过大,可不可以在客户端坐下判断,规定个传输文件的最大SIZE
[/Quote]就是文件相应字节数据过大,现在测试读到20多M就会溢出了。
stl0 2010-11-10
  • 打赏
  • 举报
回复
可以先获取总体的字节数吧,只读取相应字节数的数据。

文件过大,可不可以在客户端坐下判断,规定个传输文件的最大SIZE

81,094

社区成员

发帖
与我相关
我的任务
社区描述
Java Web 开发
社区管理员
  • Web 开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧