60 votes

Comment vérifier si l'InputStream est Gzippé ?

Existe-t-il un moyen de vérifier si l'InputStream a été gzippé ? Voici le code :

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;
    }
}

J'ai essayé de cette façon mais cela ne fonctionne pas comme prévu - les valeurs lues à partir du flux sont invalides. EDITION : J'ai ajouté la méthode que j'utilise pour compresser les données :

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;

}

0 votes

Où se trouve le InputStream proviennent-ils ? De URLConnection#getInputStream() ? Dans un protocole un peu décent comme HTTP, l'utilisateur final devrait déjà être informé d'une manière ou d'une autre que le contenu est gzippé.

0 votes

Étant donné que GZIP a un CRC de 32 bits, je trouve cela surprenant. Un flux corrompu devrait au moins lever une exception à la fin.

0 votes

Je me demande si l'OP veut dire que les valeurs lues à partir du flux APRÈS l'apparition de l'IOException ne sont pas valides... ce qui serait logique car le constructeur de GZIPInputStream aurait consommé certains des octets du flux.

80voto

biziclop Points 21446

Ce n'est pas infaillible, mais c'est probablement le plus simple et il ne repose sur aucune donnée externe. Comme tous les formats décents, GZip commence aussi par un nombre magique qui peut être vérifié rapidement sans avoir à lire tout le flux.

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;
}

(Source du chiffre magique : Spécification du format de fichier GZip )

Mise à jour : Je viens de découvrir qu'il existe également une constante appelée GZIP_MAGIC sur GZipInputStream qui contient cette valeur, donc si vous vraiment si vous le souhaitez, vous pouvez utiliser les deux octets inférieurs.

2 votes

Je pense que vous devez utiliser le constructeur à 2 arguments pour PushBackInputStream, puisque par défaut il ne vous permet de repousser que 1 octet (et pb.unread(signature) repousse 2 octets). e.g. new PushBackInputStream(input, 2)

0 votes

Áoverthink Vous avez tout à fait raison, Monsieur. Bien vu et merci.

0 votes

Pas de problème. Réponse utile, d'ailleurs !

40voto

BalusC Points 498232

L'InputStream provient de HttpURLConnection#getInputStream()

Dans ce cas, vous devez vérifier si HTTP Content-Encoding l'en-tête de réponse est égal à gzip .

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

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

// ...

Tout ceci est clairement spécifié dans Spécification HTTP .


Mise à jour En ce qui concerne la façon dont vous avez compressé la source du flux, ce contrôle du ratio est assez... insensé. Débarrassez-vous-en. La même longueur ne signifie pas nécessairement que les octets sont les mêmes. Laissez-le toujours retourner le flux gzippé afin que vous puissiez toujours s'attend à un flux gzippé et applique simplement GZIPInputStream sans contrôles méchants.

0 votes

Malheureusement, ce n'est pas exactement ce dont j'ai besoin car j'utilise http pour échanger des données binaires dans l'architecture client-serveur et, par conséquent, Content-Encoding n'est pas défini. De plus, je ne serai pas en mesure d'appeler getContentEndoing lorsque la requête provient du client qui est servi par la servlet. Mais je vous remercie quand même pour la réponse.

1 votes

L'autre partie abuse en fait du protocole HTTP ou ce n'est pas du tout un service HTTP. Contactez l'administrateur du service pour savoir si la réponse est gzippée ou non. Edit : attendez, vous voulez dire qu'il y a une servlet qui fait office de proxy pour la requête et que votre entrée provient de sa réponse ? Alors cette servlet doit être corrigée pour qu'elle copie également tous les en-têtes HTTP obligatoires.

1 votes

La dernière fois que j'ai vérifié, vous étiez autorisé à transporter n'importe quel type de contenu sur HTTP, gzip inclus, donc ce n'est pas vraiment un abus.

27voto

Aaron Roller Points 232

J'ai trouvé ceci exemple utile qui fournit une implémentation propre de isCompressed() :

/*
 * Determines if a byte array is compressed. The java.util.zip GZip
 * implementation does not expose the GZip header so it is difficult to determine
 * if a string is compressed.
 * 
 * @param bytes an array of bytes
 * @return true if the array is compressed or false otherwise
 * @throws java.io.IOException if the byte array couldn't be read
 */
 public boolean isCompressed(byte[] bytes)
 {
      if ((bytes == null) || (bytes.length < 2))
      {
           return false;
      }
      else
      {
            return ((bytes[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)));
      }
 }

Je l'ai testé avec succès :

@Test
public void testIsCompressed() {
    assertFalse(util.isCompressed(originalBytes));
    assertTrue(util.isCompressed(compressed));
}

11voto

Oconnell Points 51

Je pense que c'est le moyen le plus simple de vérifier si un tableau d'octets est formaté par gzip ou non, il ne dépend d'aucune entité HTTP ou du support de type mime.

public static boolean isGzipStream(byte[] bytes) {
      int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
      return (GZIPInputStream.GZIP_MAGIC == head);
}

1 votes

Je peux confirmer que cela fonctionne - malheureusement, j'ai dû vérifier un flux en utilisant cette méthode à un moment de ma vie ;-)

0 votes

Pour les avantages des autres, qui n'utilisent pas Java : GZIPInputStream.GZIP_MAGIC = 35615 . Voyez vous-même

1voto

Richard H Points 11693

Ce n'est pas exactement ce que vous demandez mais cela pourrait être une approche alternative si vous utilisez 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();
}

0 votes

Cela fait un certain temps déjà, mais je crois que HttpClient a déjà (ou du moins, peut) le décoder automatiquement.

0 votes

@BalusC Vraiment ? merci. Ceci a été écrit avec httpClient 3, si c'est le cas, je l'ai manqué.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X