Retrofit + okhttp: Получить GZIPInputStream

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

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 активирован, я получаю сообщение об ошибке (мой запрос падает с ошибкой).

Вот мой 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  7@P  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 заранее

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

После того, как вы столкнулись с подобной проблемой (в моем случае, не добавляя заголовок 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, то описанное решение будет работать.

Если вы проверите HttpEngine в библиотеке OkHttp, вы можете найти ниже код, что означает, что если вы вручную добавите заголовок «Accept-Encoding»: «gzip» в запрос, тогда ваша ответственность будет зависеть от un-zip. /** * 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; } 

}

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