由于WINDOWS和UNIX中回车换行符的表示方法不同而引起的.gz文件的损坏Corrupt GZIP trailer

博客讲述了在Windows和Unix系统间上传.gz文件时遇到的异常,具体表现为Unix系统下服务端解压缩时抛出'Corrupt GZIP trailer'异常。问题源于文件在传输过程中因回车换行符的不同处理导致文件损坏。通过调整数据读取方式暂时解决,但原因仍不明确,可能与回车换行符的转换有关。


  客户说,上传文件的时候出了问题。说无法上传某些文件,说是抛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== (byte0x13
                                
&& oldData[oldData.length - 1== (byte0x10{
                            
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

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值