客户说,上传文件的时候出了问题。说无法上传某些文件,说是抛java.io.IOException: Corrupt GZIP trailer异常,但并不是所有文件都会抛异常。仅限于某几个文件(脑烂了~),客户还把“有问题”的文件发了过来。一个叫testFile.bbc的文件,大小是97,663字节,还有一个叫 测试文件.bbd,大小是2,325,310字节。
BUG从天而降
上传文件这部分的测试是在Windows环境下做的。当时测试的时候也没遇到什么问题。可是客户说他们是在Unix系统下测试时出的问题,Windows系统一切正常(脑烂了~)。为了重现这个BUG,我们装了Solaris系统。并且安装了Eclipse和Weblogic的Solaris版本,将服务端工程载入Eclipse并启动。启动客户端的浏览器(Windows,IE6),上传文件,结果上传失败服务端抛出java.io.IOException: Corrupt GZIP trailer。这到底是为什么?
问题的描述
1.系统构成描述:
客户端是一个APPLET,该APPLET的功能是将用户指定的文件以.gz的形式压缩以后发送到服务端。
服务端是一个SERVLET,它的作用是将客户端发送过来的.gz文件解压出来,并存在服务器的某个目录中。
2.发送数据中的发送文件格式描述:
第一步:发送 --Boundary(这里的Boundary是一个时间标签,用来分割数据)回车换行
第二步:发送 Content-Disposition: form-data; name=uploadfile;filename=testFile.bbc回车换行
第三步:发送 Content-type:x-gzip回车换行
第四步:发送 回车换行
第五步:发送 .gz文件内容
第六步:发送 回车换行
第七步:发送 --Boundary--
3.客户端按照描述2所述的格式将数据发送到服务端。服务端按照该格式来解析数据,将数据中的文件内容部分抽出来并写入.gz文件中。
投机取巧
考虑到每次抛异常都是在读最后一次数据的时候,所以我们将原来每次读1024个字节,改为每次读1个Int,并且如果在读最后一个Int的时候抛出Corrupt GZIP trailer异常,我们就不理。没有了异常,表面上来看问题似乎已经解决了
protected
void
bufferedCopy(
final
BufferedInputStream aoInput,
final
BufferedOutputStream aoOutput)
throws
IOException
...
{
/** *//** B00054 删除 start */
// int niRead;
// byte[] noData;
//
// noData = new byte[getBufferSize()];
//
// while ((niRead = aoInput.read(noData)) > 0) {
// aoOutput.write(noData, 0, niRead);
// }
/** *//** B00054 删除 end */

/** *//** B00054 追加 start */
int n = aoInput.read();
try ...{
while (n != -1) ...{
aoOutput.write(n);
n = aoInput.read();
}
} catch (IOException e) ...{
if (e.getMessage ().indexOf ("Corrupt GZIP trailer") == -1) ...{
throw e;
}
}
/** *//** B00054 追加 end */
aoOutput.flush();
}
纸永远包不住火
我们一边庆祝BUG的成功修改,一边进入测试阶段。我随便上传了几个客户传来的“问题文件”,结果都OK。没有问题。但是当我把客户端的文件和服务器上的文件拿来用HexWorkShop一比较,有了一个惊人的发现。两个文件竟然不一样。而且只差一个字节(脑烂了~!)。
问题分析:
为了找寻问题的根源,我们进行了跟踪调试。结果发现:
1.在客户端将文件压缩成.gz文件之后,发送数据之前,利用winrar打开压缩包解压测试,没有出问题。
2.服务端接收到.gz文件之后,解压文件之前,将服务端的testFile.bbc.gz复制到Windows环境下,并用winrar打开,结果CRC校验错误[c:/testFile.bbc.gz CRC failed in testFile.bbc. The file is corrupt]
That's mean 服务端收到文件后,解压缩之前,文件已经是坏的了。难怪会抛Corrupt GZIP trailer异常。
追根溯源
经过反复的跟踪调试终于找到了问题的出处。问题就出在服务端解析数据的过程中。
.gz文件读入(原来的写法): <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>
boolean
nbCutSeparator
=
false
;
/**/
/* 开始读取文件 */

while
((niRead
=
aoInput.readLine(noData,
0
, m_BufferSize))
>
-
1
)
...
{
nsLine = new String(noData, 0, niRead);
if (isBoundary(nsLine)) ...{
break;
} else if(nbCutSeparator) ...{
// 如果下一行不是Boundary,所以将CRLF写入文件
nbCutSeparator = false;
noBO.write("/r/n".getBytes());
}
// 除了回车换行符以外的数据写入文件
if(nsLine.endsWith("/r/n")) ...{
nbCutSeparator = true;
niRead = niRead - "/r/n".length();
}
noBO.write(noData, 0 , niRead);
}
.gz文件读入(修正后的写法):
while
((niRead
=
aoInput.readLine(noData,
0
, m_BufferSize))
>
-
1
)
...
{
nsLine = new String(noData, 0, niRead);

if (isBoundary(nsLine)) ...{
if (oldData != null && oldData.length > 0) ...{
if (oldData.length >= 2
&& oldData[oldData.length - 2] == (byte) 0x13
&& oldData[oldData.length - 1] == (byte) 0x10) ...{
if (oldData.length > 2) ...{
noBO.write(oldData, 0, oldData.length - 2);
}
} else ...{
noBO.write(oldData);
}
}
break;
}

if (oldData != null && oldData.length > 0) ...{
noBO.write(oldData);
}
oldData = new byte[niRead];
for (int i = 0; i < niRead; i++) ...{
oldData[i] = noData[i];
}
}
后记
至今我还不是很清楚,为什么在Unix下,第一种写法会有问题。并且也不清楚在什么情况下会发生问题。
我只知道一定是和回车换行符有关系的,我仅仅是换了一个方法来复制文件,问题就莫名其妙的解决了。
可是,这到底是为什么呢?
那好吧,就写到这里,哪位高人如能指点一二,在下感激不尽~
之前我在网上看到JDK有一个相关的BUG:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4262583
但是,现在看来,这个问题和SUN的BUG没太大关系吧?应该是算法上的问题吧?
这位仁兄貌似也遇到了和我类似的问题,好像问题也得到了解决
http://forum.java.sun.com/thread.jspa?threadID=719980&messageID=4154773
博客讲述了在Windows和Unix系统间上传.gz文件时遇到的异常,具体表现为Unix系统下服务端解压缩时抛出'Corrupt GZIP trailer'异常。问题源于文件在传输过程中因回车换行符的不同处理导致文件损坏。通过调整数据读取方式暂时解决,但原因仍不明确,可能与回车换行符的转换有关。
568





