一、三者的对比

  1. HttpClient:代码复杂,还得操心资源回收等。代码很复杂,冗余代码多,不建议直接使用。
  2. RestTemplate: 是 Spring 提供的用于访问Rest服务的客户端, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
  3. okhttp:OkHttp是一个高效的HTTP客户端,允许所有同一个主机地址的请求共享同一个socket连接;连接池减少请求延时;透明的GZIP压缩减少响应数据的大小;缓存响应内容,避免一些完全重复的请求

二、HttpClient的使用

HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。

  • 使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。
  1. 创建HttpClient对象。
  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
  3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
  4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
  6. 释放连接。无论执行方法是否成功,都必须释放连接
  • pom.xml
  <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>
  • httpClient–Get
public class HttpClientExample_get {
    public static void main(String[] args) throws IOException {
        HttpGet request = new HttpGet("https://httpbin.org/get");
        // add request headers
        request.addHeader("custom-key", "test");
        request.addHeader(HttpHeaders.USER_AGENT, "Googlebot");
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(request)) {
            // Get HttpResponse Status
            System.out.println(response.getProtocolVersion());              // HTTP/1.1
            System.out.println(response.getStatusLine().getStatusCode());   // 200
            System.out.println(response.getStatusLine().getReasonPhrase()); // OK
            System.out.println(response.getStatusLine().toString());        // HTTP/1.1 200 OK
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                // return it as a String
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            }
        }
    }
}
  • httpClient–Post
public class HttpClientExample_post {
    public static void main(String[] args) {
        try {
            String result = sendPOST("https://httpbin.org/post");
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static String sendPOST(String url) throws IOException {
        String result = "";
        HttpPost post = new HttpPost(url);
        // add request parameters or form parameters
        List<NameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("username", "abc"));
        urlParameters.add(new BasicNameValuePair("password", "123"));
        urlParameters.add(new BasicNameValuePair("custom", "secret"));
        post.setEntity(new UrlEncodedFormEntity(urlParameters));
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(post)){

            result = EntityUtils.toString(response.getEntity());
        }
        return result;
    }
}
  • httpClient–PostWithJson
public class HttpClientExample_JsonPost {
    public static void main(String[] args) {
        try {
            String result = sendPOST("https://httpbin.org/post");
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    private static String sendPOST(String url) throws IOException {

        String result = "";
        HttpPost post = new HttpPost(url);

        String json = "{" +
                "\"name\":\"mkyong\"," +
                "\"notes\":\"hello\"" +
                "}";
        // send a JSON data
        post.setEntity(new StringEntity(json));
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(post)) {
            result = EntityUtils.toString(response.getEntity());
        }
        return result;
    }
}
  • HttpClient认证
public class HttpClientAuthentication {
    public static void main(String[] args) throws IOException {
        HttpGet request = new HttpGet("http://localhost:8080/books");
        CredentialsProvider provider = new BasicCredentialsProvider();
        provider.setCredentials(
                AuthScope.ANY,
                new UsernamePasswordCredentials("user", "password")
        );
        try (CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setDefaultCredentialsProvider(provider)
                .build();
             CloseableHttpResponse response = httpClient.execute(request)) {
            // 401 if wrong user/password
            System.out.println(response.getStatusLine().getStatusCode());

            HttpEntity entity = response.getEntity();
            if (entity != null) {
                // return it as a String
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            }
        }
    }
}

参考文章
参考文章

三、OKHttp的使用

  • 出现背景
    网络访问的高效性要求,可以说是为高效而生

  • 解决思路

  • 提供了对 HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接
  • 如果 HTTP/2 和 SPDY 不可用,OkHttp会使用连接池来复用连接以提高效率
  • 提供了对 GZIP 的默认支持来降低传输内容的大小
  • 提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
  • 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址
  • OkHttp3设计思路
    在这里插入图片描述
  • Requests(请求): 每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。
  • Responses(响应): 响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
  • 代码
  1. pom.xml
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>
  1. 创建OkHttpClient实例

简单来说,通过OkHttpClient可以发送一个Http请求,并读取该Http请求的响应,它是一个生产Call的工厂。此外,受益于一个共享的响应缓存/线程池/复用的连接等因素,绝大多数应用使用一个OkHttpClient实例,便可以满足整个应用的Http请求。

三种创建实例的方法:

  • 创建一个默认配置OkHttpClient,可以使用默认的构造函数。
  • 通过new OkHttpClient.Builder()方法来一步一步配置一个OkHttpClient实例。
  • 如果要求使用现有的实例,可以通过newBuilder()方法来进行构造。
OkHttpClient client = new OkHttpClient();
OkHttpClient clientWith30sTimeout = client.Builder()
    .readTimeout(30, TimeUnit.SECONDS)
    .build();
OkHttpClient client  = client.newBuilder().build();

看一下OkHttpClient的源码,会发现缓存/代理等等需求,一应俱全的按照类封装到了Builder中。

Dispatcher dispatcher;          // 分发
Proxy proxy;                    // 代理
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>(); // 拦截器
final List<Interceptor> networkInterceptors = new ArrayList<>(); // 网络拦截器
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache;    // 缓存
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;   // 代理证书
Authenticator authenticator;        // 证书
ConnectionPool connectionPool;
Dns dns;        // DNS
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
  1. okhttp–get
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}
  • Request

简单看一下Request类,可以发现它代表一个Http请求,需要注意的是Request一旦build()之后,便不可修改。主要通过new Request.Builder()来一步一步构造的。看一下Builder的代码。

public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}
  • 默认是Get方法,

此外还创建了头信息。Headers类中是通过List<String> namesAndValues = new ArrayList<>(20),来存放头信息的,一开始我也很纳闷,头信息都是一对一对的为什么要用List,看一下源码发现,在存取的时候都是将索引+2或者-2。并且头信息可以存在多个相同的Key信息。

  • 发起请求

跟到newCall()方法中发现,又使用OkHttpClient实例和Request的实例,一起构造了一个RealCall的实例。RealCall类简单做了一个托管并通过Dispather类对请求进行分发和执行,实际开启线程发起请求的方法就在这个类中。随后又调用execute()方法,拿到了一个响应。这个execute()方法,实际上执行的就是RealCall中的execute()方法,最后调用了Dispatcher的execute()方法。

  • Response

Response代表一个Http的响应,这个类的实例不可修改。

一个简单的Get请求和说明就结束了

  1. okhttp–post
  • POST提交字符串
public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type。一次请求就是向目标服务器发送一串文本。什么样的文本?有下面结构的文本。
在这里插入图片描述

  • 例子
POST /meme.php/home/user/login HTTP/1.1
Host: 114.215.86.90
Cache-Control: no-cache
Postman-Token: bd243d6b-da03-902f-0a2c-8e9377f6f6ed
Content-Type: application/x-www-form-urlencoded

tel=13637829200&password=123456

例如,MediaType.parse(“application/json; charset=utf-8”);这个就带表请求体的类型为JSON格式的。定义好数据类型,还要将其变为请求体,最后通过post()方法,随请求一并发出。

  • POST提交键值对

OkHttp也可以通过POST方式把键值对数据传送到服务器

OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody formBody = new FormBody.Builder()
    .add("platform", "android")
    .add("name", "bug")
    .add("subject", "XXXXXXXXXXXXXXX")
    .build();
    Request request = new Request.Builder()
      .url(url)
      .post(formBody)
      .build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException("Unexpected code " + response);
    }
}
  1. HTTP头部的设置和读取

HTTP 头的数据结构是 Map<String, List<String>>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以查看这个请求头大全。

OkHttp的处理方式是:

  • 使用header(name,value)来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
  • 使用addHeader(name,value)来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
  • 使用header(name)读取唯一值或多个值的最后一个值
  • 使用headers(name)获取所有值
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("https://github.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"));

参考文章

四、RestTemplate的使用

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 初始化(3种)
RestTemplate restTemplate = new RestTemplate();
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());

当然如果想使用OkHttp的话也得引入相应的jar包

  • 默认配置

默认情况下RestTemplate自动帮我们注册了一组HttpMessageConverter用来处理一些不同的contentType的请求。如果现有的转换器不能满足你的需求,你还可以实现org.springframework.http.converter.HttpMessageConverter接口自己写一个。详情参考官方api。其他相关的配置,也可以在官方文档中查看。当然,对于一个请求来说,超期时间,请求连接时间等都是必不可少的参数,为了更好的适应业务需求,所以可以自己修改restTemplate的配置

  • RestTemplate使用总结
  • RestTemplate提供了六种常用的HTTP方法实现远程服务调用,RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型。

  • getForObject – 发送GET请求,将HTTP response转换成一个指定的object对象

  • postForEntity –发送POST请求,将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象(包含响应数据和响应头)

  • 每个HTTP方法对应的RestTemplate方法都有3种。其中2种的url参数为字符串,URI参数变量分别是Object数组和Map,第3种使用URI类型作为参数

  • exchange 和execute方法比上面列出的其它方法(如getForObject、postForEntity等)使用范围更广,允许调用者指定HTTP请求的方法(GET、POST、PUT等),并且可以支持像HTTP PATCH(部分更新)

  • GET 请求

做好了准备工作,先来看使用 RestTemplate 发送 GET 请求。在 RestTemplate 中,和 GET 请求相关的方法有如下几个:
在这里插入图片描述

  • 代码
public class TemplateGet {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
        HttpClient httpClient = builder.build();
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);

        //三种方式请求
        String forObject = restTemplate.getForObject("https://httpbin.org/get", String.class);
        ResponseEntity<String> forEntity = restTemplate.getForEntity("https://httpbin.org/get", String.class);
        ResponseEntity<String> responseEntity = restTemplate.exchange("https://httpbin.org/get", HttpMethod.GET, null, String.class);
        //遍历响应头
        System.out.println("响应头--start");
        forEntity.getHeaders().entrySet().forEach(System.out::println);
        System.out.println("响应头--end");
        assert forObject != null;
        byte[] bytes = forObject.getBytes(StandardCharsets.UTF_8);
        String res = new String(bytes, StandardCharsets.UTF_8);
        System.out.println(res);
        System.out.println(forEntity.getBody());
        System.out.println(responseEntity.getBody());
    }
}
  • post请求
    在这里插入图片描述
  1. json形式
public class TemplatePostJson {
    public static void main(String[] args) {
        //生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
        HttpClient httpClient = builder.build();
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        String url = "https://httpbin.org/post";
        String json = "{" +
                "\"name\":\"mkyong\"," +
                "\"notes\":\"hello\"" +
                "}";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
        headers.set("CustomHeader", "myCustom"); //Other headers
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);

        //三种方式请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        String s = restTemplate.postForObject(url, httpEntity, String.class);
        ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

        assert s != null;
        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
        String body=new String(bytes,StandardCharsets.UTF_8);
        System.out.println(body);
        System.out.println(responseEntity.getBody());
        System.out.println("响应头--start");
        stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);
        System.out.println("响应头--end");
    }
}
  1. map(表单)形式
public class TemplatePostMap {
    public static void main(String[] args) {
        //生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
        HttpClient httpClient = builder.build();
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);

        String url = "https://httpbin.org/post";


        MultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
        map.add("aa", "aa");
        map.add("bb", "bb");
        map.add("cc", "cc");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
        headers.set("CustomHeader", "myCustom"); //Other headers
        HttpEntity<MultiValueMap<Object, Object>> httpEntity = new HttpEntity<>(map, headers);

        //三种方式请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        String s = restTemplate.postForObject(url, httpEntity, String.class);
        ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

        assert s != null;
        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
        String body=new String(bytes,StandardCharsets.UTF_8);
        System.out.println(body);
        System.out.println(responseEntity.getBody());
        System.out.println("响应头--start");
        stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);
        System.out.println("响应头--end");
    }
}

其实,如果参数是一个 MultiValueMap 的实例,则以 key/value 的形式发送,如果是一个普通对象,则会被转成 json 发送。

  • postForLocation

postForLocation 方法的返回值是一个 URL 对象,因为 POST 请求一般用来添加数据,有的时候需要将刚刚添加成功的数据的 URL 返回来,此时就可以使用这个方法,一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。

  • PUT 请求

只要将 GET 请求和 POST 请求搞定了,接下来 PUT 请求就会容易很多了,PUT 请求本身方法也比较少,只有三个,如下:
在这里插入图片描述
这三个重载的方法其参数其实和 POST 是一样的,可以用 key/value 的形式传参,也可以用 JSON 的形式传参,无论哪种方式,都是没有返回值的

  • DELETE 请求

和 PUT 请求一样,DELETE 请求也是比较简单的,只有三个方法,如下:

在这里插入图片描述
不同于 POST 和 PUT ,DELETE 请求的参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递,当然,这里也是没有返回值的。

public class TemplateDeleteJson {
    public static void main(String[] args) {
        //生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
        HttpClient httpClient = builder.build();
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        String url = "https://httpbin.org/delete";
        String json = "{" +
                "\"name\":\"mkyong\"," +
                "\"notes\":\"hello\"" +
                "}";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
        headers.set("CustomHeader", "myCustom"); //Other headers
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
        restTemplate.delete(url,httpEntity);
    }
}

参考文章
参考文章
参考文章

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐