2014-02-05 4 views
11

У меня возникла проблема, когда я активирую gzip на WS, используя модификацию 1.4.1 и okhttp 1.3.0.retrofit + okhttp: Retrieve GZIPInputStream

RequestInterceptor requestInterceptor = new RequestInterceptor() { 
      @Override 
      public void intercept(RequestFacade request) { 
       request.addHeader("content-type", "application/json"); 
       request.addHeader("accept-encoding", "gzip"); // Here is the problem 
      } 
     }; 
RestAdapter restAdapter = new RestAdapter.Builder() 
      .setEndpoint(Constants.HOST) 
      .setLogLevel(RestAdapter.LogLevel.FULL) 
      .setRequestInterceptor(requestInterceptor) 
      .build(); 

Если я комментирую следующую строку request.addHeader("accept-encoding", "gzip"); нет никаких проблем, но если GZIP активирован, я получаю сообщение об ошибке (мой запрос попадет в failure).

Вот мой LogCat с request.addHeader("accept-encoding", "gzip");

1326    Retrofit D : HTTP/1.1 200 OK 
    1326    Retrofit D Cache-Control: public, max-age=600 
    1326    Retrofit D Content-Encoding: gzip 
    1326    Retrofit D Content-Length: 254 
    1326    Retrofit D Content-Type: application/json 
    1326    Retrofit D Date: Wed, 05 Feb 2014 20:22:26 GMT 
    1326    Retrofit D OkHttp-Received-Millis: 1391631746193 
    1326    Retrofit D OkHttp-Response-Source: NETWORK 200 
    1326    Retrofit D OkHttp-Selected-Transport: http/1.1 
    1326    Retrofit D OkHttp-Sent-Millis: 1391631745971 
    1326    Retrofit D Server: Apache 
    1326    Retrofit D Vary: Accept-Encoding 
    1326    Retrofit D X-Powered-By: PHP/5.3.3-7+squeeze18 
    1326    Retrofit D ������������}�?O�0��~����nHZOH0 �D�ù���?���~w.�:����=�{� 
           ����|A���=�V/~}o�)���&����<�`�6&��ѳ:��5�ke��V�WD�H� 
           ���ud�J5رyp��G�ːg�y�ʴ����Mxq<�#�Rb`Su�@�0��y��lr;�W�2�C3� 
           T��$���.� 
              ��xѥ���R 
                y���hmt����R����o����v��[email protected]� 
           4Y���� 
    1326    Retrofit D <--- END HTTP (254-byte body) 
    1326    System.err W retrofit.RetrofitError: retrofit.converter.ConversionException: com.google.gson.JsonSyntaxException: java.lang.Ille 
           galStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 
    1326    System.err W at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:408) 
    1326    System.err W at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:262) 
    1326    System.err W at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:313) 
    1326    System.err W at retrofit.CallbackRunnable.run(CallbackRunnable.java:38) 
    1326    System.err W at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080) 
    1326    System.err W at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573) 
    1326    System.err W at retrofit.Platform$Android$2$1.run(Platform.java:136) 
    1326    System.err W at java.lang.Thread.run(Thread.java:841) 
    1326    System.err W Caused by: retrofit.converter.ConversionException: com.google.gson.JsonSyntaxException: java.lang.IllegalStateExcep 

Как я могу включить Gzip?

Thx заранее

ответ

26

Просто опустить заголовок accept-encoding из вашего кода. OkHttp добавит свой собственный заголовок accept-encoding, и если сервер ответит gzip, то OkHttp будет молча разархивировать его для вас.

0

Вам необходимо использовать Okhttp, также сделанный квадратом, который поддерживает захват. Не знаю, создаете ли вы собственный экземпляр или если он включен по умолчанию, вы должны проверить документы для этого.

2

После того, как вы столкнулись с подобной проблемой (в моем случае, не добавляя заголовок Accept-Encoding, он иногда не смог бы отключить ответ, оставив в нем заголовок Content-Encoding: gzip, сбой анализатора JSON), и без ясного пути, я вручную включил gzip для Retrofit, создав делегированную реализацию клиента ниже. Он отлично работает, за исключением того, что вы, вероятно, не должны использовать его для очень больших ответов (например, 250 КБ), поскольку они сначала копируются в массив байтов.

public class GzippedClient implements Client { 

    private Client wrappedClient; 

    public GzippedClient(Client wrappedClient) { 
     this.wrappedClient = wrappedClient; 
    } 

    @Override 
    public Response execute(Request request) throws IOException { 
     Response response = wrappedClient.execute(request); 

     boolean gzipped = false; 
     for (Header h : response.getHeaders()) { 
      if (h.getName() != null && h.getName().toLowerCase().equals("content-encoding") && h.getValue() != null && h.getValue().toLowerCase().equals("gzip")) { 
       gzipped = true; 
       break; 
      } 
     } 

     Response r = null; 
     if (gzipped) { 
      InputStream is = null; 
      ByteArrayOutputStream bos = null; 

      try { 
       is = new BufferedInputStream(new GZIPInputStream(response.getBody().in())); 
       bos = new ByteArrayOutputStream(); 

       int b; 
       while ((b = is.read()) != -1) { 
        bos.write(b); 
       } 

       TypedByteArray body = new TypedByteArray(response.getBody().mimeType(), bos.toByteArray()); 
       r = new Response(response.getUrl(), response.getStatus(), response.getReason(), response.getHeaders(), body); 
      } finally { 
       if (is != null) { 
        is.close(); 
       } 
       if (bos != null) { 
        bos.close(); 
       } 
      } 
     } else { 
      r = response; 
     } 
     return r; 
    } 

} 

Вам также необходимо добавить заголовок Accept-Encoding к вашим запросам, например. с помощью RequestInterceptor

requestFacade.addHeader("Accept-Encoding", "gzip"); 

Наконец, вы должны обернуть существующий клиент в этот новый GzippedClient, например, так:

restBuilder.setClient(new GzippedClient(new OkClient(okHttpClient))); 

Вот и все. Теперь ваши данные будут gzipped.

EDIT: Кажется, что в OkHttp версии 1.5.1 ошибка (https://github.com/square/okhttp/pull/632) была исправлена, связанная с прозрачным gzipping, который может (или не может) быть источником моей первоначальной проблемы. Если это так, то случайный отказ от un-gzip больше не может произойти, хотя это случается редко, и я пока не могу подтвердить это. В любом случае, если вы хотите полагаться на свои собственные, а не на прозрачное добавление/удаление заголовков и gzipping, то описанное решение будет работать.

2

Если вы проверите HttpEngine в библиотеке OkHttp, вы можете найти ниже код, а это означает, что если вы вручную добавите заголовок «Accept-Encoding»: «gzip» в запрос, тогда ваша ответственность будет зависеть от вас. /** * True if this client added an "Accept-Encoding: gzip" header field and is * therefore responsible for also decompressing the transfer stream. */ private boolean transparentGzip;

так что если вы вручную добавите заголовок «Accept-Encoding»: «gzip», то после получения ответа сделайте un-zip, как показано ниже.

private static String readContentFromTypedInput(TypedInput typedInput){ 

     InputStreamReader isr = null; 
     BufferedReader br = null; 
     char[] cbuf = new char[512]; 
     StringWriter stringWriter = new StringWriter(); 

     try { 
      final InputStream in = typedInput.in(); 
      boolean isGzipped = GZipper.isGzippped(in); 
      if(isGzipped){ 
       return new String(GZipper.doUnZip(in)); 
      } 
      isr = new InputStreamReader(in); 
      br = new BufferedReader(isr); 
      while((br.read(cbuf))!= -1){ 
       stringWriter.write(cbuf); 
      } 
     } catch (IOException e) { 
      throw new InvalidTestCaseException("failed read received content.", e); 
     } finally{ 
      try{ 
       if(br != null) br.close(); 
      }catch(IOException e){ 
       //ignore 
      } 
     } 
     return stringWriter.toString().trim(); 
    } 

GZipper.java

public class GZipper{ 

public static final String DEFAULT_CHARSET = "utf-8"; 
private static final int BYTE_BLOCK_LENGTH = 1024; 

public static byte[] doZip(final String message){ 
    if(message == null || message.isEmpty()){ 
     throw new SystemFailedException("Fail to zip - given message is null or empty"); 
    } 
    byte[] gzippped = null; 
    try { 
     gzippped = doZip(message.getBytes(DEFAULT_CHARSET)); 
    } catch (Throwable e) { 
     throw new SystemFailedException(e.getMessage(), e); 
    } 
    return gzippped; 
} 

public static byte[] doZip(final byte[] unzippedMessageByte){ 
    validate(unzippedMessageByte, "Fail to zip - given bytes is null or empty"); 

    ByteArrayInputStream is = null; 
    ByteArrayOutputStream bos = null; 
    GZIPOutputStream gzip_os = null; 
    byte[] compressedBytes = null; 
    try{ 
     is = new ByteArrayInputStream(unzippedMessageByte); 
     bos = new ByteArrayOutputStream(); 
     gzip_os = new GZIPOutputStream(bos); 
     copy(is, gzip_os); 
     gzip_os.finish(); 
     compressedBytes = bos.toByteArray(); 
    }catch(IOException e){ 
     throw new SystemFailedException(e.getMessage(), e); 
    }finally{ 
     try{ 
      if(is != null){is.close();} 
      if(gzip_os != null){gzip_os.close();} 
      if(bos != null){bos.close();} 
     }catch(IOException e){ 
      //ignore 
     } 
    } 
    return compressedBytes; 
} 

public static String doUnZipToString(final byte[] gzippedMessage){ 
    validate(gzippedMessage, "Fail to unzip - given bytes is null or empty"); 
    byte[] gzippped = null; 
    String unzippedMessage = null; 
    try { 
     gzippped = doUnZip(gzippedMessage); 
     unzippedMessage = new String(gzippped, DEFAULT_CHARSET); 
    } catch (Throwable e) { 
     throw new SystemFailedException(e.getMessage(), e); 
    } 
    return unzippedMessage; 
} 

private static void validate(final byte[] bytes, String failedMessage) { 
    if(bytes == null || bytes.length == 0){ 
     throw new SystemFailedException(failedMessage); 
    } 
} 

public static byte[] doUnZip(InputStream in) { 
    if(!(in instanceof ByteArrayInputStream)){ 
     try { 
      return doUnZip(IOUtils.toByteArray(in)); 
     } catch (IOException e) { 
      throw new SystemFailedException(e.getMessage(), e); 
     } 
    } 

    ByteArrayOutputStream bos = null; 
    InputStream gzip_is = null; 
    byte[] bytes = null; 
    try{ 
     bos = new ByteArrayOutputStream(); 
     gzip_is = new GZIPInputStream(in); 
     copy(gzip_is,bos); 
     bytes = bos.toByteArray(); 
    }catch(IOException e){ 
     throw new SystemFailedException(e.getMessage(), e); 
    }finally{ 
     try{ 
      if(gzip_is != null) gzip_is.close(); 
      if(bos != null) bos.close(); 
     }catch(IOException e){ 
      //ignore 
     } 
    } 
    return bytes; 
} 

public static byte[] doUnZip(final byte[] zippedMessage){ 
    validate(zippedMessage, "Fail to unzip - given bytes is null or empty"); 
    ByteArrayInputStream is = null; 
    try{ 
     is = new ByteArrayInputStream(zippedMessage); 
     return doUnZip(is); 
    }finally{ 
     try{ 
      if(is != null) is.close(); 
     }catch(IOException e){ 
      //ignore 
     } 
    } 
} 

public static String doUnZip(File file){ 
    validate(file); 

    GZIPInputStream gzipInputStream = null; 
    StringWriter writer = null; 
    String result = ""; 

    try{ 
     byte[] buffer = new byte[BYTE_BLOCK_LENGTH]; 
     gzipInputStream = new GZIPInputStream(new FileInputStream(file)); 
     writer = new StringWriter(); 
     while((gzipInputStream.read(buffer)) > 0){ 
      writer.write(new String(buffer)); 
      writer.flush(); 
     } 
     result = writer.toString(); 
    }catch(IOException e){ 
     //do something to handle exception 
    } 
    finally{ 
     try{ 
      if(writer != null){writer.close();} 
      if(gzipInputStream != null){gzipInputStream.close();} 
     }catch(IOException e){ 
      //ignore 
     } 
    } 
    return result; 
} 

private static void validate(File file) { 
    if(file==null || !file.exists()){ 
     throw new SystemFailedException("Fail to unzip - file is not exist"); 
    } 
} 

private static void copy(InputStream in, OutputStream out)throws IOException { 
    byte[] buf = new byte[BYTE_BLOCK_LENGTH]; 
    int len = -1; 
    while ((len = in.read(buf, 0, buf.length)) != -1) { 
     out.write(buf, 0, len); 
    } 
} 

public static boolean isGzipped(byte[] input){ 
    return isGzippped(new ByteArrayInputStream(input)); 
} 

public static boolean isGzippped(InputStream in){ 
    boolean markSupported = in.markSupported(); 
    boolean result = false; 
    try { 
     if(markSupported){ 
      in.mark(0); 
      result = (readUShort(in) == GZIPInputStream.GZIP_MAGIC); 
      in.reset(); 
     } 
    } catch (Exception e) { 
     result = false; 
    } 
    return result; 
} 

private static int readUShort(InputStream in) throws IOException { 
    int b = readUByte(in); 
    return ((int)readUByte(in) << 8) | b; 
} 

/* 
* Reads unsigned byte. 
*/ 
private static int readUByte(InputStream in) throws IOException { 
    int b = in.read(); 
    if (b == -1) { 
     throw new EOFException(); 
    } 
    if (b < -1 || b > 255) { 
     b = 0; 
    } 
    return b; 
} 

}