文档章节

OkHttp:Java 平台上的新一代 HTTP 客户端

JTA的阿呆
 JTA的阿呆
发布于 2016/12/12 13:06
字数 3496
阅读 47
收藏 0

OkHttp 简介

OkHttp 库的设计和实现的首要目标是高效。这也是选择 OkHttp 的重要理由之一。OkHttp 提供了对最新的 HTTP 协议版本 HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率。OkHttp 提供了对 GZIP 的默认支持来降低传输内容的大小。OkHttp 也提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求。当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址。

在 Java 程序中使用 OkHttp 非常简单,只需要在 Maven 的 POM 文件中添加代码清单 1 中的依赖即可。目前 OkHttp 的最新版本是 2.5.0。

清单 1. OkHttp 的 Maven 依赖声明

<!-- https://mvnrepository.com/artifact/com.squareup.okhttp/okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp</groupId>
            <artifactId>okhttp</artifactId>
            <version>2.7.5</version>
        </dependency>

HTTP 连接

虽然在使用 OkHttp 发送 HTTP 请求时只需要提供 URL 即可,OkHttp 在实现中需要综合考虑 3 种不同的要素来确定与 HTTP 服务器之间实际建立的 HTTP 连接。这样做的目的是为了达到最佳的性能。

首先第一个考虑的要素是 URL 本身。URL 给出了要访问的资源的路径。比如 URL http://www.baidu.com 所对应的是百度首页的 HTTP 文档。在 URL 中比较重要的部分是访问时使用的模式,即 HTTP 还是 HTTPS。这会确定 OkHttp 所建立的是明文的 HTTP 连接,还是加密的 HTTPS 连接。

第二个要素是 HTTP 服务器的地址,如 baidu.com。每个地址都有对应的配置,包括端口号,HTTPS 连接设置和网络传输协议。同一个地址上的 URL 可以共享同一个底层 TCP 套接字连接。通过共享连接可以有显著的性能提升。OkHttp 提供了一个连接池来复用连接。

第三个要素是连接 HTTP 服务器时使用的路由。路由包括具体连接的 IP 地址(通过 DNS 查询来发现)和所使用的代理服务器。对于 HTTPS 连接还包括通讯协商时使用的 TLS 版本。对于同一个地址,可能有多个不同的路由。OkHttp 在遇到访问错误时会自动尝试备选路由。

当通过 OkHttp 来请求某个 URL 时,OkHttp 首先从 URL 中得到地址信息,再从连接池中根据地址来获取连接。如果在连接池中没有找到连接,则选择一个路由来尝试连接。尝试连接需要通过 DNS 查询来得到服务器的 IP 地址,也会用到代理服务器和 TLS 版本等信息。当实际的连接建立之后,OkHttp 发送 HTTP 请求并获取响应。当连接出现问题时,OkHttp 会自动选择另外的路由进行尝试。这使得 OkHttp 可以自动处理可能出现的网络问题。当成功获取到 HTTP 请求的响应之后,当前的连接会被放回到连接池中,提供给后续的请求来复用。连接池会定期把闲置的连接关闭以释放资源。

清单 2. OkHttp 最基本的 HTTP 请求

package com.okhttp.test;

import java.io.IOException;

import com.squareup.okhttp.Headers;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class Okhttp {

    public static void main(String[] args) throws IOException {
    
         OkHttpClient client = new OkHttpClient();
         Request request = new Request.Builder()
         .url("http://www.baidu.com")
         .build();
         Response response = client.newCall(request).execute();
            if (!response.isSuccessful()) {
                throw new IOException("服务器端错误: " + response);
            }
         
            Headers responseHeaders = response.headers();
            for (int i = 0; i < responseHeaders.size(); i++) {
                System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
            }
         
            System.out.println(response.body().string());
    }
}

HTTP 头处理

HTTP 头是 HTTP 请求和响应中的重要组成部分。在创建 HTTP 请求时需要设置一些 HTTP 头。在得到 HTTP 的响应之后,也会需要对其中包含的 HTTP 头进行解析。从代码的角度来说,HTTP 头的数据结构是 Map<String, List<String>>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。OkHttp 采用了简单的方式来区分这两种类型,使得对 HTTP 头的使用更加简单。

在设置 HTTP 头时,使用 header(name, value) 方法来设置 HTTP 头的唯一值。对同一个 HTTP 头,多次调用该方法会覆盖之前设置的值。使用 addHeader(name, value) 方法来为 HTTP 头添加新的值。在读取 HTTP 头时,使用 header(name) 方法来读取 HTTP 头的最近出现的值。如果该 HTTP 头只有单个值,则返回该值;如果有多个值,则返回最后一个值。使用 headers(name) 方法来读取 HTTP 头的所有值。

代码清单 3 中使用 header 方法设置了 User-Agent 头的值,并添加了一个 Accept 头的值。在进行解析时,通过 header 方法来获取 Server 头的单个值,通过 headers 方法来获取 Set-Cookie 头的所有值。

package com.okhttp.test;

import java.io.IOException;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class Headers {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();
 
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .header("User-Agent", "My super agent")
            .addHeader("Accept", "text/html")
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    System.out.println(response.header("Server"));
    System.out.println(response.headers("Set-Cookie"));
   }
}

POST 请求

HTTP POST 和 PUT 请求可以包含要提交的内容。只需要在创建 Request 对象时,通过 post 和 put 方法来指定要提交的内容即可。代码清单 4中通过 RequestBody 的 create 方法来创建媒体类型为 text/plain 的内容并提交。

package com.okhttp.test;

import java.io.IOException;

import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

public class PostString {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();
    MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
    String postBody = "Hello World";
 
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .post(RequestBody.create(MEDIA_TYPE_TEXT, postBody))
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    System.out.println(response.body().string());
   }
}

以 String 类型提交内容只适用于内容比较小的情况。当请求内容较大时,应该使用流来提交。

代码清单 5 中给了使用流方式来提交内容的示例。这里创建了 RequestBody 的一个匿名子类。该子类的 contentType 方法需要返回内容的媒体类型,而 writeTo 方法的参数是一个 BufferedSink 对象。我们需要做的就是把请求的内容写入到 BufferedSink 对象即可。

package com.okhttp.test;

import java.io.IOException;

import okio.BufferedSink;

import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

public class PostStream {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();
    final MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
    final String postBody = "Hello World";
 
    RequestBody requestBody = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MEDIA_TYPE_TEXT;
        }
 
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8(postBody);
        }
 
 @Override
        public long contentLength() throws IOException {
            return postBody.length();
        }
    };
 
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .post(requestBody)
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    System.out.println(response.body().string());
   }
}

如果所要提交的内容来自本地文件,则不需要额外的流操作,只需要通过 RequestBody 的 create 方法,并把 File 类型的对象作为参数传入即可。

如果需要模拟 HTML 中的表单提交,可以通过 FormEncodingBuilder 来创建请求内容,见代码清单 6

RequestBody formBody = new FormEncodingBuilder()
                .add("query", "Hello")
                .build();

如果需要模拟 HTML 中的文件上传功能,可以通过 MultipartBuilder 来创建 multipart 请求内容。

代码清单 7 中的 multipart 请求添加了一个表单属性 title 和一个文件 file。

MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
    RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"title\""),
                RequestBody.create(null, "测试文档"))
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"file\""),
                RequestBody.create(MEDIA_TYPE_TEXT, new File("input.txt")))
        .build();

响应缓存

OkHttp 可以对 HTTP 响应的内容在磁盘上进行缓存。在进行 HTTP 请求时,如果该请求的响应已经被缓存而且没有过期,OkHttp 会直接使用缓存中的响应内容,而不需要真正的发出 HTTP 请求到远程服务器。在创建缓存时需要指定一个磁盘目录和缓存的大小。

代码清单 8 中,创建出 Cache 对象之后,通过 OkHttpClient 的 setCache 进行设置。通过 Response 对象的 cacheResponse 和 networkResponse 方法可以得到缓存的响应和从实际的 HTTP 请求得到的响应。如果该请求的响应来自实际的网络请求,则 cacheResponse 方法的返回值为 null;如果响应来自缓存,则 networkResponse 的返回值为 null。OkHttp 在进行缓存时会遵循 HTTP 协议的要求,因此可以通过标准的 HTTP 头 Cache-Control 来控制响应的缓存时间。

package com.okhttp.test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import com.squareup.okhttp.Cache;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class CacheResponse {
   public static void main(String[] args) throws IOException {
    int cacheSize = 100 * 1024 * 1024;
    File cacheDirectory = Files.createTempDirectory("cache").toFile();
    Cache cache = new Cache(cacheDirectory, cacheSize);
    OkHttpClient client = new OkHttpClient();
    client.setCache(cache);
 
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    System.out.println(response.cacheResponse());
    System.out.println(response.networkResponse());
   }
}

用户认证

OkHttp 提供了对用户认证的支持。当 HTTP 响应的状态代码是 401 时,OkHttp 会从设置的 Authenticator 对象中获取到新的 Request 对象并再次尝试发出请求。Authenticator 接口中的 authenticate 方法用来提供进行认证的 Request 对象,authenticateProxy 方法用来提供对代理服务器进行认证的 Request 对象。代码清单 9 中新的 Request 对象中添加了 HTTP 基本认证的 Authorization 头。

OkHttpClient client = new OkHttpClient();
    client.setAuthenticator(new Authenticator() {
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        String credential = Credentials.basic("user", "password");
        return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
    }
     
    public Request authenticateProxy(Proxy proxy, Response response)
    throws IOException {
        return null;
    }
    });

异步调用

OkHttp 除了支持常用的同步 HTTP 请求之外,还支持异步 HTTP 请求调用。在使用同步调用时,当前线程会被阻塞,直到 HTTP 请求完成。当同时发出多个 HTTP 请求时,同步调用的性能会比较差。这个时候通过异步调用可以提高整体的性能。

代码清单 10 给出了异步调用的示例。在通过 newCall 方法创建一个新的 Call 对象之后,不是通过 execute 方法来同步执行,而是通过 enqueue 方法来添加到执行队列中。在调用 enqueue 方法时需要提供一个 Callback 接口的实现。在 Callback 接口实现中,通过 onResponse 和 onFailure 方法来处理响应和进行错误处理。

package com.okhttp.test;

import java.io.IOException;

import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class AsyncGet {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();
 
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .build();
 
    client.newCall(request).enqueue(new Callback() {
        public void onFailure(Request request, IOException e) {
            e.printStackTrace();
        }
 
        public void onResponse(Response response) throws IOException {
            if (!response.isSuccessful()) {
                throw new IOException("服务器端错误: " + response);
            }
 
            System.out.println(response.body().string());
        }
});
   }
}

取消调用

当一个 HTTP 调用执行之后,可以通过 Call 接口的 cancel 方法来取消该请求。当一个调用被取消之后,等待该请求的响应的代码会收到 IOException。同步和异步调用都可以被取消。如果需要同时取消多个请求,可以在创建请求时通过 RequestBuilder 的 tag 方法来为请求添加一个标签。在需要时可以通过 OkHttpClient 的 cancel 方法来取消拥有一个标签的所有请求。代码清单 11 中的所有请求都设置了标签 website,可以通过 cancel 方法来全部取消。

package com.okhttp.test;

import java.io.IOException;
import java.util.List;

import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class CancelRequest {
private OkHttpClient client = new OkHttpClient();
private String tag = "website";
 
public void sendAndCancel() {
    sendRequests(Lists.newArrayList(
            "http://www.baidu.com",
            "http://www.163.com",
            "http://www.sina.com.cn"));
    client.cancel(this.tag);
}
 
public void sendRequests(List<String> urls) {
    urls.forEach(url -> {
        client.newCall(new Request.Builder()
                    .url(url)
                    .tag(this.tag)
                    .build())
                .enqueue(new SimpleCallback());
    });
}
 
private static class SimpleCallback implements Callback {
 
    public void onFailure(Request request, IOException e) {
        e.printStackTrace();
    }
 
    public void onResponse(Response response) throws IOException {
        System.out.println(response.body().string());
    }
}
 
public static void main(String[] args) throws IOException {
    new CancelRequest().sendAndCancel();
}
}

拦截器

拦截器是 OkHttp 提供的对 HTTP 请求和响应进行统一处理的强大机制。拦截器在实现和使用上类似于 Servlet 规范中的过滤器。多个拦截器可以链接起来,形成一个链条。拦截器会按照在链条上的顺序依次执行。 拦截器在执行时,可以先对请求的 Request 对象进行修改;再得到响应的 Response 对象之后,可以进行修改之后再返回。

代码清单 12 中的拦截器 LoggingInterceptor 用来记录 HTTP 请求和响应的相关信息。Interceptor 接口只包含一个方法 intercept,其参数是 Chain 对象。Chain 对象表示的是当前的拦截器链条。通过 Chain 的 request 方法可以获取到当前的 Request 对象。在使用完 Request 对象之后,通过 Chain 对象的 proceed 方法来继续拦截器链条的执行。当执行完成之后,可以对得到的 Response 对象进行额外的处理。

package com.okhttp.test;

import java.io.IOException;

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Interceptor.Chain;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class LoggingInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
 
    long t1 = System.nanoTime();
    System.out.println(String.format("发送请求: [%s] %s%n%s",
            request.url(), chain.connection(), request.headers()));
 
    Response response = chain.proceed(request);
 
    long t2 = System.nanoTime();
    System.out.println(String.format("接收响应: [%s] %.1fms%n%s",
            response.request().url(), (t2 - t1) / 1e6d, response.headers()));
 
    return response;
}
}

OkHttp 中的拦截器分成应用和网络拦截器两种。应用拦截器对于每个 HTTP 响应都只会调用一次,可以通过不调用 Chain.proceed 方法来终止请求,也可以通过多次调用 Chain.proceed 方法来进行重试。网络拦截器对于调用执行中的自动重定向和重试所产生的响应也会被调用,而如果响应来自缓存,则不会被调用。应用和网络拦截器的添加方式见代码清单 13

清单 13. 添加应用和网络拦截器

1

2

3

client.interceptors().add(new LoggingInterceptor()); //添加应用拦截器

 

client.networkInterceptors().add(new LoggingInterceptor()); //添加网络拦截器

 

OkHttp 作为一个简洁高效的 HTTP 客户端,可以在 Java 和 Android 程序中使用。相对于 Apache HttpClient 来说,OkHttp 的性能更好,其 API 设计也更加简单实用。本文对 OkHttp 进行了详细的介绍,包括同步和异步调用、HTTP GET 和 POST 请求处理、用户认证、响应缓存和拦截器等。对于新开发的 Java 应用,推荐使用 OkHttp 作为 HTTP 客户端。

 

 

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
JTA的阿呆
粉丝 1
博文 19
码字总数 19304
作品 0
闸北
后端工程师
Java HTTP 组件库选型看这篇就够了

原文出处:行思錄 Java HTTP 组件库选型看这篇就够了 最近项目需要使用 Java 重度调用 HTTP API 接口,于是想着封装一个团队公用的 HTTP client lib. 这个库需要支持以下特性: 连接池管理,...

行思錄
09/25
0
0
Android中流行的第三方库资源收集

1、GSON GSON是将JSON解析成POJO的Java库。GSON也可以将POJO解析成JSON。 2、OkHttp OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和 HTTP 缓存。默认情况下,OKHttp会自动...

jdroid
2014/05/02
0
0
feign和okhttp的结合

背景 使用feign可以很方便的调用各种http接口 http请求神器之Feign 那么feign是如何做到的呢? 分析 本质上默认场景feign仍然是使用httpClient进行调用的。 通过声明式的RequestMapping等注解...

Mr_Qi
07/11
0
0
Xutils, OKhttp, Volley, Retrofit对比

Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框...

安卓笔记侠
08/17
0
0
OkHttp 3.11.0 发布,轻量的 Java 网络请求框架

OkHttp 3.11.0 已发布,OkHttp 是一个轻量的 Java 网络请求框架。 本次更新内容如下: OkHttp's new okhttp-tls submodule tames HTTPS and TLS. OkHttp now supports prior knowledge clear......

淡漠悠然
07/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

区块链100讲:盘点那些常用的加密算法原理

在开发过程中,常常用到各种加密方法和算法,本文总结了几种常用加密方法的原理。 1 对称加密 原理:加密和解密数据使用同一个密钥,适合对大量数据进行加解密 安全性:关键是密钥的保存方式...

HiBlock
14分钟前
0
0
zookeeper基本常识

一、Zookeeper基础知识 1 zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致。2 zookeeper有watch事件,是一次性触发的,当watch监视的数...

啃不动地大坚果
19分钟前
0
0
Forrester企业级容器平台权威排行出炉,小初创Rancher缘何成为领导者?

全球著名的调研机构Forrester Research近日发布了《The Forrester New Wave: Enterprise Container Platform Software Suites, Q4 2018》报告,对企业级容器平台(ECP)市场进行全面评估,希...

RancherLabs
22分钟前
0
0
【三 异步HTTP编程】 2. 流式HTTP响应

标准响应及Content-Length头 自HTTP1.1以来,服务器为了在一个链接中处理多个HTTP请求及响应,必须随response一起返回合适的Content-Length值。 默认情况下,对于简单请求你无需返回 Conten...

Landas
58分钟前
0
0
Java后端技术栈,到底如何深入学习?

Java,是现阶段中国互联网公司中,覆盖度最广的研发语言。有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及免费学习资料。 一 。性能...

别打我会飞
今天
1
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部