retrofit + okhttp : Retrieve GZIPInputStream

后端 未结 4 1335
自闭症患者
自闭症患者 2021-02-19 19:33

I have a problem when i activate gzip on WS using retrofit 1.4.1 and okhttp 1.3.0.

RequestInterceptor requestInterceptor = new RequestInterceptor() {
                    


        
相关标签:
4条回答
  • 2021-02-19 19:41

    You need have to use Okhttp also made by square which supports grip. Don't know if you have create a custom instance or if it's enabled by default you should check the docs for that.

    0 讨论(0)
  • 2021-02-19 19:56

    After running into a similar issue (in my case, without adding any Accept-Encoding header, it would occasionally fail to un-gzip the response, leaving also the Content-Encoding: gzip header in it, crashing the JSON parser), and with no clear way around this, I manually enabled gzip for Retrofit by creating the delegated Client implementation below. It works great, except that you probably should not use it for very large (e.g. > 250KB) responses, as they are first copied into a byte array.

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

    You will also have to add an Accept-Encoding header to your requests, e.g. by using a RequestInterceptor

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

    Finally, you have to wrap your existing Client into this new GzippedClient, like so:

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

    That's it. Now your data will be gzipped.

    EDIT: It seems that in OkHttp version 1.5.1, a bug (https://github.com/square/okhttp/pull/632) seems to have been fixed related to the transparent gzipping which may (or may not) have been the source of my initial issue. If so, the occasional failure to un-gzip may no longer occur, though it happened rarely enough that I cannot confirm this yet. Either way, if you want to rely on your own, rather than the transparent adding/removing of headers and gzipping, then the solution described will work.

    0 讨论(0)
  • 2021-02-19 19:57

    If you check HttpEngine in OkHttp library, then you can find below code, which means if you manually add "Accept-Encoding":"gzip" header into request, then un-zip is your responsibility. /** * True if this client added an "Accept-Encoding: gzip" header field and is * therefore responsible for also decompressing the transfer stream. */ private boolean transparentGzip;

    so if you manually add "Accept-Encoding":"gzip" header, then after getting response, do un-zip like below.

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

    }

    0 讨论(0)
  • 2021-02-19 20:01

    Just omit the accept-encoding header from your code. OkHttp will add its own accept-encoding header, and if the server responds with gzip then OkHttp will silently unzip it for you.

    0 讨论(0)
提交回复
热议问题