Java 如何检查InputStream是否已gzip?

Java 如何检查InputStream是否已gzip?,java,http,gzip,inputstream,httpurlconnection,Java,Http,Gzip,Inputstream,Httpurlconnection,有没有办法检查InputStream是否已被gzip压缩? 代码如下: public static InputStream decompressStream(InputStream input) { try { GZIPInputStream gs = new GZIPInputStream(input); return gs; } catch (IOException e) { logger.info("Input stream

有没有办法检查InputStream是否已被gzip压缩? 代码如下:

public static InputStream decompressStream(InputStream input) {
    try {
        GZIPInputStream gs = new GZIPInputStream(input);
        return gs;
    } catch (IOException e) {
        logger.info("Input stream not in the GZIP format, using standard format");
        return input;
    }
}
我尝试了这种方法,但没有达到预期效果-从流中读取的值无效。 编辑: 添加了我用于压缩数据的方法:

public static byte[] compress(byte[] content) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        GZIPOutputStream gs = new GZIPOutputStream(baos);
        gs.write(content);
        gs.close();
    } catch (IOException e) {
        logger.error("Fatal error occured while compressing data");
        throw new RuntimeException(e);
    }
    double ratio = (1.0f * content.length / baos.size());
    if (ratio > 1) {
        logger.info("Compression ratio equals " + ratio);
        return baos.toByteArray();
    }
    logger.info("Compression not needed");
    return content;

}

将原始流包装在BufferedInputStream中,然后将其包装在GZipInputStream中。
下一步,试着提取一个ZipEntry。如果这样做有效,它就是一个zip文件。然后,在检查后,您可以在BufferedInputStream中使用“标记”和“重置”返回到流中的初始位置。

这与您的要求不完全相同,但如果您使用的是HttpClient,则可能是另一种方法:

private static InputStream getInputStream(HttpEntity entity) throws IOException {
  Header encoding = entity.getContentEncoding(); 
  if (encoding != null) {
     if (encoding.getValue().equals("gzip") || encoding.getValue().equals("zip") ||      encoding.getValue().equals("application/x-gzip-compressed")) {
        return new GZIPInputStream(entity.getContent());
     }
  }
  return entity.getContent();
}
InputStream来自HttpURLConnection#getInputStream()

在这种情况下,您需要检查HTTP
内容编码
响应头是否等于
gzip

URLConnection connection = url.openConnection();
InputStream input = connection.getInputStream();

if ("gzip".equals(connection.getContentEncoding())) {
    input = new GZIPInputStream(input);
}

// ...
所有这些都在中明确规定



更新:根据您压缩流源的方式:此比率检查非常。。。疯了。摆脱它。相同的长度并不一定意味着字节相同。让它始终返回gzip流,这样您就可以始终期待gzip流,只需应用
gzip输入流
,而无需进行严格的检查。

这不是万无一失的,但可能是最简单的,并且不依赖任何外部数据。像所有体面的格式一样,GZip也以一个神奇的数字开始,它可以在不读取整个流的情况下快速检查

public static InputStream decompressStream(InputStream input) {
     PushbackInputStream pb = new PushbackInputStream( input, 2 ); //we need a pushbackstream to look ahead
     byte [] signature = new byte[2];
     int len = pb.read( signature ); //read the signature
     pb.unread( signature, 0, len ); //push back the signature to the stream
     if( signature[ 0 ] == (byte) 0x1f && signature[ 1 ] == (byte) 0x8b ) //check if matches standard gzip magic number
       return new GZIPInputStream( pb );
     else 
       return pb;
}
(幻数的来源:)


更新:我刚刚发现
GZIP\u MAGIC
中还有一个常量,名为
GZIP\u MAGIC
,它包含这个值,所以如果你真的想,你可以使用它的下两个字节。

我认为这是检查字节数组是否是GZIP格式的最简单的方法,它不依赖于任何HTTP实体或mime类型支持

public static boolean isGzipStream(byte[] bytes) {
      int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
      return (GZIPInputStream.GZIP_MAGIC == head);
}
public静态布尔值isGzipStream(字节[]字节){
int head=((int)bytes[0]&0xff)|((bytes[1]我发现它提供了
isCompressed()
的干净实现:


以下是如何读取可以gzip的文件:

private void read(final File file)
        throws IOException {
    InputStream stream = null;
    try (final InputStream inputStream = new FileInputStream(file);
            final BufferedInputStream bInputStream = new BufferedInputStream(inputStream);) {
        bInputStream.mark(1024);
        try {
            stream = new GZIPInputStream(bInputStream);
        } catch (final ZipException e) {
            // not gzipped OR not supported zip format
            bInputStream.reset();
            stream = bInputStream;
        }
        // USE STREAM HERE
    } finally {
        if (stream != null) {
            stream.close();
        }
    }
}

此函数在Java中运行良好:

public static boolean isGZipped(File f) {   
    val raf = new RandomAccessFile(file, "r")
    return GZIPInputStream.GZIP_MAGIC == (raf.read() & 0xff | ((raf.read() << 8) & 0xff00))
}
公共静态布尔值isgzip(文件f){
val raf=新的随机访问文件(文件“r”)
return GZIP输入流.GZIP_MAGIC==(raf.read()&0xff |)((raf.read()是用于解析内容类型的Java库:

<!-- pom.xml -->
    <dependency>
        <groupId>com.j256.simplemagic</groupId>
        <artifactId>simplemagic</artifactId>
        <version>1.8</version>
    </dependency>

基于@biziclop的回答,这个版本使用GZIP_魔法头,另外对于空或单字节数据流是安全的

public static InputStream maybeDecompress(InputStream input) {
    final PushbackInputStream pb = new PushbackInputStream(input, 2);

    int header = pb.read();
    if(header == -1) {
        return pb;
    }

    int b = pb.read();
    if(b == -1) {
        pb.unread(header);
        return pb;
    }

    pb.unread(new byte[]{(byte)header, (byte)b});

    header = (b << 8) | header;

    if(header == GZIPInputStream.GZIP_MAGIC) {
        return new GZIPInputStream(pb);
    } else {
        return pb;
    }
}
publicstaticinputstream可以被压缩(InputStream输入){
最终PushbackInputStream pb=新的PushbackInputStream(输入,2);
int header=pb.read();
如果(标题==-1){
返回pb;
}
intb=pb.read();
如果(b==-1){
pb.未读(表头);
返回pb;
}
未读(新字节[]{(字节)头,(字节)b});

header=(b
InputStream
来自哪里?来自
URLConnection#getInputStream()
?在HTTP这样一个稍微体面的协议中,应该已经以某种方式指示最终用户内容是GZIP的。考虑到GZIP有一个32位CRC,我觉得这很奇怪。一个损坏的流至少应该在结尾抛出一个异常。我想知道OP是否意味着在IOException发生后从流读取的值无效。…这是有意义的,因为GZIPInputStream构造函数会消耗流中的一些字节。IOException发生后,值会损坏。InputStream来自HttpURLConnection#getInputStream(),因此一般的解决方案是创建一个BufferedInputStream来包装原始输入流,然后调用“mark”标记流的开头。然后围绕它包装一个GZipInputStream。如果没有发生异常,则返回GZipInputStream。如果发生异常,则调用“重置”然后返回BufferedInputStream。好吧,GZip!=Zip,所以这个想法是正确的,但是您希望包装GZipInputStream,而不是ZipInputStream。如果是这样,我将修复答案。如果条目的大小超出了缓冲区大小,那么GZipInputStream就没有ZipEntry这样的东西了。GZ streams只包含一个文件(至少,通过Java API)。我尝试了类似的方法,但无法使其工作。我正在从GZipInputStream读取protobufs,因此我不确定是protobuf读取代码还是GZip代码,但标记后来被重置,因此我无法将流设置回开始。这是很久以前的事了,但IIRC HttpClient已经(或者至少可以)了自动解码。@BalusC真的吗?谢谢。这是用httpClient 3编写的,如果它在,那么我就错过了。然后另一方实质上滥用了HTTP协议,或者它根本不是HTTP服务。请与服务管理员联系,如何判断响应是否为Gzip。编辑:等等,你是说有一个servletich正在代理请求,并且您的输入来自其响应?然后需要修复该servlet,使其同时复制所有必需的HTTP头。上次我检查您是否被允许通过HTTP传输任何类型的内容,包括gzip,因此这不是真正的滥用。@biziclop:这种滥用与使用gzip内容编码无关g(见鬼,我甚至在我的初始答案中包含了关于这个的HTTP规范链接),但是关于不沿着它发送强制HTTP头(这意味着OP违反了HTTP规范)。听起来你是在试图压缩二进制内容而不是文本内容。这是真的吗?你为什么要尝试压缩二进制内容?在普通HTTP服务器/客户端中,gzip通常只应用于
内容类型
,从
文本开始
,如
文本/普通
文本/html
文本/css,等等。@BalusC“当存在时,其值指示应用于
def isGZip(file:File): Boolean = {
   int gzip = 0
   RandomAccessFile raf = new RandomAccessFile(f, "r")
   gzip = raf.read() & 0xff | ((raf.read() << 8) & 0xff00)
   raf.close()
   return gzip == GZIPInputStream.GZIP_MAGIC
}
<!-- pom.xml -->
    <dependency>
        <groupId>com.j256.simplemagic</groupId>
        <artifactId>simplemagic</artifactId>
        <version>1.8</version>
    </dependency>
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.j256.simplemagic.ContentType;
// ...

public class SimpleMagicSmokeTest {

    private final static Logger log = LoggerFactory.getLogger(SimpleMagicSmokeTest.class);

    @Test
    public void smokeTestSimpleMagic() throws IOException {
        ContentInfoUtil util = new ContentInfoUtil();
        InputStream possibleGzipInputStream = getGzipInputStream();
        ContentInfo info = util.findMatch(possibleGzipInputStream);

        log.info( info.toString() );
        assertEquals( ContentType.GZIP, info.getContentType() );
    }
public static InputStream maybeDecompress(InputStream input) {
    final PushbackInputStream pb = new PushbackInputStream(input, 2);

    int header = pb.read();
    if(header == -1) {
        return pb;
    }

    int b = pb.read();
    if(b == -1) {
        pb.unread(header);
        return pb;
    }

    pb.unread(new byte[]{(byte)header, (byte)b});

    header = (b << 8) | header;

    if(header == GZIPInputStream.GZIP_MAGIC) {
        return new GZIPInputStream(pb);
    } else {
        return pb;
    }
}