修改响应体改造2.2拦截器

时间:2017-04-26 20:33:42

标签: java base64 gzip retrofit interceptor

我正在使用Retrofit 2开发应用程序来请求API。这个API在ASP.NET中,它使用GZip和编码到Base64,如下面的代码:

private static string Compress(string conteudo)
{
    Encoding encoding = Encoding.UTF8;
    byte[] raw = encoding.GetBytes(conteudo);

    using (var memory = new MemoryStream())
    {
        using (GZipStream gzip = new GZipStream(memory, CompressionMode.Compress, true))
        {
            gzip.Write(raw, 0, raw.Length);
        }
        return Convert.ToBase64String(memory.ToArray());
    }
}

private static string Decompress(string conteudo)
{
    Encoding encoding = Encoding.UTF8;
    var gzip = Convert.FromBase64String(conteudo);

    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        int size = gzip.Length;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return encoding.GetString(memory.ToArray());
        }
    }
}

现在,我在Android应用程序中需要做的是从Retrofit获取响应,从Base64解码并解压缩。我尝试使用Interceptor来做,但我没有成功。

这是我从服务H4sIAAAAAAAEACspKk0FAI1M/P0EAAAA收到的回复,解码和解压缩回复,我们有true

有人知道怎么做吗?

2 个答案:

答案 0 :(得分:6)

这很容易。下面的代码使用Google Guava来解码Base64字符流,使用Google Gson来反序列化JSON内容。

考虑以下测试服务接口:

interface IService {

    @GET("/")
    Call<String> get();

}

现在,您可以使用模板方法设计模式实现拦截器响应输入流转换器库:

abstract class AbstractTransformingDecodingInterceptor
        implements Interceptor {

    protected abstract InputStream transformInputStream(InputStream inputStream)
            throws IOException;

    @Override
    @SuppressWarnings("resource")
    public final Response intercept(final Chain chain)
            throws IOException {
        final Request request = chain.request();
        final Response response = chain.proceed(request);
        final ResponseBody body = response.body();
        return response.newBuilder()
                .body(ResponseBody.create(
                        body.contentType(),
                        body.contentLength(),
                        Okio.buffer(Okio.source(transformInputStream(body.byteStream())))
                ))
                .build();
    }

}

此实现还应检测内容MIME类型,以便不进行错误的转换,但您可以轻松地自己实现它。所以这里还有两个用于Base64和GZip的转换拦截器:

final class Base64DecodingInterceptor
        extends AbstractTransformingDecodingInterceptor {

    private static final Interceptor base64DecodingInterceptor = new Base64DecodingInterceptor();

    private Base64DecodingInterceptor() {
    }

    static Interceptor getBase64DecodingInterceptor() {
        return base64DecodingInterceptor;
    }

    @Override
    protected InputStream transformInputStream(final InputStream inputStream) {
        return BaseEncoding.base64().decodingStream(new InputStreamReader(inputStream));
    }

}
final class GzipDecodingInterceptor
        extends AbstractTransformingDecodingInterceptor {

    private static final Interceptor gzipDecodingInterceptor = new GzipDecodingInterceptor();

    private GzipDecodingInterceptor() {
    }

    static Interceptor getGzipDecodingInterceptor() {
        return gzipDecodingInterceptor;
    }

    @Override
    protected InputStream transformInputStream(final InputStream inputStream)
            throws IOException {
        return new GZIPInputStream(inputStream);
    }

}

测试一下:

private static final OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(getGzipDecodingInterceptor())
        .addInterceptor(getBase64DecodingInterceptor())
        .addInterceptor(getFakeContentInterceptor())
        .build();

private static final Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://whatever")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

private static final IService service = retrofit.create(IService.class);

public static void main(final String... args)
        throws IOException {
    final String body = service.get().execute().body();
    System.out.println(body);
}

请注意getFakeContentInterceptor返回一个始终返回H4sIAAAAAAAEACspKk0FAI1M/P0EAAAA的假拦截器,以便baseUrl甚至没有真正的URL。输出:

  

答案 1 :(得分:0)

另一种方法是添加Filter并使用包装器修改请求/响应对象。

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // We need to convert the ServletRequest to MultiReadRequest, so that we could intercept later
    MultiReadHttpServletRequest multiReadRequest =
        new MultiReadHttpServletRequest((HttpServletRequest) request);
    HttpServletResponseWrapper responseWrapper =
        new HttpServletResponseWrapper((HttpServletResponse) response);
    chain.doFilter(multiReadRequest, responseWrapper);
  }

您可以添加代理层(jetty代理servlet org.eclipse.jetty.proxy.ProxyServlet)并覆盖方法:onResponseContent,addProxyHeaders

 public void handleProxyResponse(
      HttpServletRequest request,
      HttpServletResponse response,
      byte[] buffer,
      int offset,
      int length,
      Callback callback) {
    try {
      if (response.getStatus() == HttpStatus.OK_200
          && request.getRequestURI().startsWith(INTERCEPTED_END_POINT)) {
        String output;
        boolean acceptsGZipEncoding = acceptsGZipEncoding(request);
        if (acceptsGZipEncoding) {
          output = plainTextFromGz(buffer);
        } else {
          output = new String(buffer);
        }
        String proxyHost = getRequestUrlBase(request);
        try {
          // TODO: get this from config object
          output = output.replace(ProxyServer.REMOTE_HOST, proxyHost);
          byte[] outBuffer;
          if (acceptsGZipEncoding) {
            outBuffer = gzFromPlainText(output);
          } else {
            outBuffer = output.getBytes();
          }
          HttpServletResponseWrapper responseWrapper = (HttpServletResponseWrapper) response;
          responseWrapper.getResponse().reset();
          responseWrapper.getOutputStream().write(outBuffer);
        } catch (Exception e) {
          log.error(e.getMessage(), e);
          // Error in parsing json, writing original response
          response.getOutputStream().write(buffer, offset, length);
        }
      } else {
        response.getOutputStream().write(buffer, offset, length);
      }
      callback.succeeded();
    } catch (Throwable e) {
      callback.failed(e);
    }
  }

  private String getRequestUrlBase(HttpServletRequest request) {
    logHeaders(request);
    return request.getHeader(HttpHeader.HOST.name());
  }

  private String replaceBaseFromUrl(String url, String base) throws Exception {
    URI uri = new URI(url);
    StringBuffer sb = new StringBuffer("");
    if (base.startsWith("http")) {
      sb.append(base);
    } else {
      sb.append("http://").append(base);
    }
    sb.append(uri.getPath());
    if (!Strings.isNullOrEmpty(uri.getQuery())) {
      sb.append("?").append(uri.getQuery());
    }
    return sb.toString();
  }

  private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) {
    String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
    return acceptEncoding != null && acceptEncoding.indexOf("gzip") != -1;
  }

  private byte[] gzFromPlainText(String plainText) {
    if (Strings.isNullOrEmpty(plainText)) {
      return null;
    }

    try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final GZIPOutputStream gzipOutput = new GZIPOutputStream(baos)) {
      gzipOutput.write(plainText.getBytes());
      gzipOutput.finish();
      return baos.toByteArray();
    } catch (IOException e) {
      log.error("Could not convert plain text to gz", e);
    }
    return plainText.getBytes();
  }

  private String plainTextFromGz(byte[] gz) {
    try (GZIPInputStream gzipIn = new GZIPInputStream(new ByteArrayInputStream(gz))) {
      return IOUtils.toString(gzipIn, Charset.defaultCharset());
    } catch (IOException e) {
      log.error("Could not write gz to plain text", e);
    }
    return new String(gz);
  }