OkHttp wiki官方文档翻译(二)

来源:互联网 发布:妇产科网络咨询成本 编辑:程序博客网 时间:2024/06/04 19:18

拦截器

拦截器是一个很强大的机制,可以监视、重写、重试请求。下面的例子是打印请求和响应的日志。

class LoggingInterceptor implements Interceptor {  @Override public Response intercept(Interceptor.Chain chain) throws IOException {    Request request = chain.request();    long t1 = System.nanoTime();    logger.info(String.format("Sending request %s on %s%n%s",        request.url(), chain.connection(), request.headers()));    Response response = chain.proceed(request);    long t2 = System.nanoTime();    logger.info(String.format("Received response for %s in %.1fms%n%s",        response.request().url(), (t2 - t1) / 1e6d, response.headers()));    return response;  }}

chain.proceed(request)的调用是每个拦截器实现类至关重要的一部分。这个简单的方法是所有的Http工作、产生满足请求的响应的地方。

拦截器可以被链式的串联。假设你有一个压缩拦截器和一个校验和拦截器:你需要决定数据是先压缩再校验,还是先校验再压缩。OkHttp使用一个列表来追踪拦截器,拦截器被顺序调用。


应用拦截器

拦截器可以被注册为应用拦截器或者网络拦截器。我们将使用日志拦截器来查看两者的不同。

通过okhttpClient.Builder的addInterceptor()方法可以添加应用拦截器。

OkHttpClient client = new OkHttpClient.Builder()    .addInterceptor(new LoggingInterceptor())    .build();Request request = new Request.Builder()    .url("http://www.publicobject.com/helloworld.txt")    .header("User-Agent", "OkHttp Example")    .build();Response response = client.newCall(request).execute();response.body().close();
http://www.publicobject.com/helloworld.txt被重定向到了https://publicobject.com/helloworld.txt, okhttp自动跟随重定向。我们的应用拦截器被调用一次,从chain.proceed()返回的响应是重定向之后的响应。

INFO: Sending request http://www.publicobject.com/helloworld.txt on nullUser-Agent: OkHttp ExampleINFO: Received response for https://publicobject.com/helloworld.txt in 1179.7msServer: nginx/1.4.6 (Ubuntu)Content-Type: text/plainContent-Length: 1759Connection: keep-alive

我们可以看到我们的请求被重定向了,因为response.request().url()和request.url()是不同的。两个INFO可以看出是两个URLS.

网络拦截器

注册一个网络拦截器也十分的相似。调用addNetworkInterceptor()方法而不是addInterceptor()方法:

OkHttpClient client = new OkHttpClient.Builder()    .addNetworkInterceptor(new LoggingInterceptor())    .build();Request request = new Request.Builder()    .url("http://www.publicobject.com/helloworld.txt")    .header("User-Agent", "OkHttp Example")    .build();Response response = client.newCall(request).execute();response.body().close();
当我们运行这段代码,拦截器运行了两次,一次是最初的请求到http://www.publicobject.com/helloworld.txt另一次是重定向到https://publicobject.com/helloworld.txt.

 

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}User-Agent: OkHttp ExampleHost: www.publicobject.comConnection: Keep-AliveAccept-Encoding: gzipINFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6msServer: nginx/1.4.6 (Ubuntu)Content-Type: text/htmlContent-Length: 193Connection: keep-aliveLocation: https://publicobject.com/helloworld.txtINFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}User-Agent: OkHttp ExampleHost: publicobject.comConnection: Keep-AliveAccept-Encoding: gzipINFO: Received response for https://publicobject.com/helloworld.txt in 80.9msServer: nginx/1.4.6 (Ubuntu)Content-Type: text/plainContent-Length: 1759Connection: keep-alive

网络请求包含更多的数据,比如被OkHttp添加的Accept-Encoding:gzip头用于支持响应压缩。网络拦截器的链具有非空的连接,它可用于询问IP地址和连接到网络服务器的TLS配置。

选择应用和网络拦截器

每个拦截器链都有相应的优势。

应用拦截器

  • 不需要担心中间响应比如重定向和重试。
  • 一般只调用一次,即使Http响应来源于缓存。
  • 只需关注应用最初的意图,无需关心Okhttp注入的头信息,比如If-None-Match。
  • 允许短路和不调用Chain.proceed() 。
  • 允许重试,并多次调用Chain.proceed() 。

网络拦截器

  • 可以操作中间响应,比如重定向和重试。
  • 不会调用短路网络请求的缓存响应。
  • 关注在网络上的数据发送。
  • 访问携带请求的连接。

重写请求

拦截器可以添加、删除、替换请求头。也可以转换请求体。比如如果你知道Web服务器支持压缩,你可以使用一个应用拦截器去添加请求体压缩。

/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */final class GzipRequestInterceptor implements Interceptor {  @Override public Response intercept(Interceptor.Chain chain) throws IOException {    Request originalRequest = chain.request();    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {      return chain.proceed(originalRequest);    }    Request compressedRequest = originalRequest.newBuilder()        .header("Content-Encoding", "gzip")        .method(originalRequest.method(), gzip(originalRequest.body()))        .build();    return chain.proceed(compressedRequest);  }  private RequestBody gzip(final RequestBody body) {    return new RequestBody() {      @Override public MediaType contentType() {        return body.contentType();      }      @Override public long contentLength() {        return -1; // We don't know the compressed length in advance!      }      @Override public void writeTo(BufferedSink sink) throws IOException {        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));        body.writeTo(gzipSink);        gzipSink.close();      }    };  }}

重写响应

相应的,拦截器可以重写响应头和转换响应体。但是这比重写请求要危险的多,因为这违反了Web服务器的期望。

在一个棘手的情况下,如果已经做好应对的后果,重写响应头是解决问题的有效方式。例如,您可以修复服务器的配置错误的Cache-Control响应头以便更好地响应缓存:

/** Dangerous interceptor that rewrites the server's cache-control header. */private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {  @Override public Response intercept(Interceptor.Chain chain) throws IOException {    Response originalResponse = chain.proceed(chain.request());    return originalResponse.newBuilder()        .header("Cache-Control", "max-age=60")        .build();  }};

OkHttp的拦截器在OkHttp2.2或更高版本可以使用。不幸的是,拦截器并不能与OkUrlFactory一起很好的工作,基于OkUrlFactory的类库也不能与拦截器很好的工作,包括Retrofit ≤ 1.8 and Picasso ≤ 2.4.

HTTPS


OkHttp 尝试平衡如下两个关注点:

  • 连接尽可能多的主机。包括运行最新版本的boringssl的主机和运行过时版本的OpenSSL主机。
  • 连接的安全性。包括远程Web服务器证书验证以及数据交换的私密性。

当与一个Https服务器打交道,OkHttp需要知道TLS版本和cipher suites。一个客户端想要最大化的连接性就必须包括废弃的TLS版本以及设计不精良的cipher suites。一个严格的客户端想要最大化的安全性需要限制使用最新版本的TLS以及最强劲的cipher suites。

Specific security vs. connectivity decisions are implemented by ConnectionSpec. OkHttp 内置了如下connection specs:

  • MODERN_TLS 是一个连接modern HTTPS 服务器的安全配置.
  • COMPATIBLE_TLS 是一个连接not current–HTTPS 服务器的安全配置. 
  • CLEARTEXT 是一个用于http://URLs的不安全的配置。

默认情况下, OkHttp 会尝试一个MODERN_TLS 连接, 其次如果modern configuration失败了,OkHttp 会尝试COMPATIBLE_TLS 连接.

在每一个发布的OKHttp版本中,TLS的版本和ciper suites支持的情况都会不一样。比如在okhttp 2.2 我们放弃支持SSL 3.0以规避POODLE 的攻击。还有在OKHttp2.3中我们放弃支持RC4.为了安全,请保持更新OKHttp至最新版本。 

你也可以使用你自己的TLS和cipher suies构建你自己的ConnectionSpec.例如,限制配置三个备受推崇的cipher suies。它的缺点是,它需要的Andr​​oid 5.0+和一个类似的网络服务器。

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)      .tlsVersions(TlsVersion.TLS_1_2)    .cipherSuites(          CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,          CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,          CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)    .build();OkHttpClient client = new OkHttpClient.Builder()     .connectionSpecs(Collections.singletonList(spec))    .build();

Certificate Pinning(证书锁定技术)

默认情况下,OKHttp信任权威的证书颁发机构。这个策略最大化了连接性,但是受限于证书颁发机构发生的攻击比如2011 DigiNotar攻击。这里也假设你的Https 服务器安装的也是权威的证书颁发机构的证书。

使用CertificatePinner去限制什么样的证书及颁发机构可以被信任。Certificate Pinning 提高的安全性,但是却限制了你的服务器团队去更新他们TLS证书。Do not use certificate pinning without the blessing of your server’s TLS administrator!

 public CertificatePinning() {    client = new OkHttpClient.Builder()        .certificatePinner(new CertificatePinner.Builder()            .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")            .build())        .build();  }  public void run() throws Exception {    Request request = new Request.Builder()        .url("https://publicobject.com/robots.txt")        .build();    Response response = client.newCall(request).execute();    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);    for (Certificate certificate : response.handshake().peerCertificates()) {      System.out.println(CertificatePinner.pin(certificate));    }  }

定制信任的证书

如下代码展示了如何定制自己的证书。在此之前,, do not use custom certificates without the blessing of your server’s TLS administrator!

 private final OkHttpClient client;  public CustomTrust() {    SSLContext sslContext = sslContextForTrustedCertificates(trustedCertificatesInputStream());    client = new OkHttpClient.Builder()        .sslSocketFactory(sslContext.getSocketFactory())        .build();  }  public void run() throws Exception {    Request request = new Request.Builder()        .url("https://publicobject.com/helloworld.txt")        .build();    Response response = client.newCall(request).execute();    System.out.println(response.body().string());  }  private InputStream trustedCertificatesInputStream() {    ... // Full source omitted. See sample.  }  public SSLContext sslContextForTrustedCertificates(InputStream in) {    ... // Full source omitted. See sample.  }

Evens(再稳定版之后再做翻译)

Warning: This is a non-final API.

As of OkHttp 3.9, this feature is an unstable preview: the API is subject to change, and the implementation is incomplete. We expect that OkHttp 3.10 or 3.11 will finalize this API. Until then, expect API and behavior changes when you update your OkHttp dependency.

原创粉丝点击