点赞再看,能源有限。 微信搜「 程序猿阿朗 」。

本文 Github.com/niumoo/JavaNotes 和 未读代码博客 曾经收录,有很多知识点和系列文章。

超文本传输协定(HTTP)可能是当今互联网上最重要的协定之一,Web 服务、微服务以及反对网络的各种设施上的服务简直都是 HTTP 协定,HTTP 协定曾经从 Web 浏览器走向了更宽泛的应用场景。

尽管 java.net 包曾经提供了 HTTP 拜访资源的基本功能,然而它不够灵便,而且不能得心应手的进行自定义。Apache HttpClient 5 是一个开源的 HTTP 工具包,能够反对最新 HTTP 协定规范,且有丰盛的 API 和弱小的扩大个性,能够用于构建任何须要进行 HTTP 协定解决的应用程序。

这篇文章介绍 Apache HttpClient 5 中最为常见的一些用法,通过这篇文章能够疾速的入门应用 HttpClient 5,次要内容包含 HttpClient 5 的 Get 申请、Post 申请、如何携带参数、JSON 参数、设置超时、异步申请、操作 Cookie、表单登录、根本认证、Digest 认证以及自定义 HTTP 申请拦截器等。

HttpClient 5 依赖

HttpClient 5 Maven 依赖

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 --><dependency>    <groupId>org.apache.httpcomponents.client5</groupId>    <artifactId>httpclient5</artifactId>    <version>5.1.3</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent --><dependency>    <groupId>org.apache.httpcomponents.client5</groupId>    <artifactId>httpclient5-fluent</artifactId>    <version>5.1.3</version></dependency>

HttpClient 5 Gradle 依赖

implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.3'implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.1.3'

HttpClient 5 GET 申请

package com.wdbyte.httpclient;import java.io.IOException;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.ParseException;import org.apache.hc.core5.http.io.entity.EntityUtils;/** * @author https://www.wdbyte.com */public class HttpClient5Get {    public static void main(String[] args) {        String result = get("http://httpbin.org/get");        System.out.println(result);    }    public static String get(String url) {        String resultContent = null;        HttpGet httpGet = new HttpGet(url);        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {            try (CloseableHttpResponse response = httpclient.execute(httpGet)) {                // 获取状态码                System.out.println(response.getVersion()); // HTTP/1.1                System.out.println(response.getCode()); // 200                System.out.println(response.getReasonPhrase()); // OK                HttpEntity entity = response.getEntity();                // 获取响应信息                resultContent = EntityUtils.toString(entity);            }        } catch (IOException | ParseException e) {            e.printStackTrace();        }        return resultContent;    }}

响应信息:

HTTP/1.1200OK{  "args": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb1891-5ab5e5376ed960471bf32f17"  },   "origin": "47.251.4.198",   "url": "http://httpbin.org/get"}

HttpClient 5 Fluent GET

应用 Apache HttpClient 5 提供的 Fluent API 能够更便捷的发动 GET 申请,然而可操作的中央较少。

依赖:

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent --><dependency>    <groupId>org.apache.httpcomponents.client5</groupId>    <artifactId>httpclient5-fluent</artifactId>    <version>5.1.3</version></dependency>

示例:

package com.wdbyte.httpclient;import java.io.IOException;import org.apache.hc.client5.http.fluent.Request;import org.apache.hc.client5.http.fluent.Response;/*** @author https://www.wdbyte.com */public class HttpClient5GetFluent {    public static void main(String[] args) {        System.out.println(get("http://httpbin.org/get"));    }    public static String get(String url) {        String result = null;        try {            Response response = Request.get(url).execute();            result = response.returnContent().asString();        } catch (IOException e) {            e.printStackTrace();        }        return result;    }}

输入信息:

{  "args": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb190e-1ba46a92645843a04c55da32"  },   "origin": "47.251.4.198",   "url": "http://httpbin.org/get"}

HttpClient5 GET 申请参数

应用 URIBuilderaddParameters() 办法来构建 GET 申请的参数。

package com.wdbyte.httpclient;import java.io.IOException;import java.net.URI;import java.net.URISyntaxException;import java.util.ArrayList;import java.util.List;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.NameValuePair;import org.apache.hc.core5.http.ParseException;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.message.BasicNameValuePair;import org.apache.hc.core5.net.URIBuilder;/*** @author https://www.wdbyte.com */public class HttpClient5GetParams {    public static void main(String[] args) {        String result = get("http://httpbin.org/get");        System.out.println(result);    }    public static String get(String url) {        String resultContent = null;        HttpGet httpGet = new HttpGet(url);        // 表单参数        List<NameValuePair> nvps = new ArrayList<>();        // GET 申请参数        nvps.add(new BasicNameValuePair("username", "wdbyte.com"));        nvps.add(new BasicNameValuePair("password", "secret"));        // 减少到申请 URL 中        try {            URI uri = new URIBuilder(new URI(url))                .addParameters(nvps)                .build();            httpGet.setUri(uri);        } catch (URISyntaxException e) {            throw new RuntimeException(e);        }        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {            try (CloseableHttpResponse response = httpclient.execute(httpGet)) {                // 获取状态码                System.out.println(response.getVersion()); // HTTP/1.1                System.out.println(response.getCode()); // 200                System.out.println(response.getReasonPhrase()); // OK                HttpEntity entity = response.getEntity();                // 获取响应信息                resultContent = EntityUtils.toString(entity);            }        } catch (IOException | ParseException e) {            e.printStackTrace();        }        return resultContent;    }}

输入信息:

{  "args": {    "password": "secret",     "username": "wdbyte.com"  },   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",     "X-Amzn-Trace-Id": "Root=1-62ecc660-69d58a226aefb1b6226541ec"  },   "origin": "42.120.75.185",   "url": "http://httpbin.org/get?username=wdbyte.com&password=secret"}

上面是通过抓包失去的申请响应信息格式:

// 申请信息GET /get?username=wdbyte.com&password=secret HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateHost: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)// 响应信息HTTP/1.1 200 OKDate: Fri, 05 Aug 2022 07:27:30 GMTContent-Type: application/jsonContent-Length: 405Connection: keep-aliveServer: gunicorn/19.9.0Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true{  "args": {    "password": "secret",     "username": "wdbyte.com"  },   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",     "X-Amzn-Trace-Id": "Root=1-62ecc660-69d58a226aefb1b6226541ec"  },   "origin": "42.120.75.185",   "url": "http://httpbin.org/get?username=wdbyte.com&password=secret"}

HttpClient 5 POST 申请

上面演示发动一个 POST 申请,并携带表单参数。

参数:username=wdbyte.com&password=secret

package com.wdbyte.httpclient;import java.io.IOException;import java.util.ArrayList;import java.util.List;import org.apache.hc.client5.http.classic.methods.HttpPost;import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.NameValuePair;import org.apache.hc.core5.http.ParseException;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.message.BasicNameValuePair;/*** @author https://www.wdbyte.com */public class HttpClient5Post {    public static void main(String[] args) {        String result = post("http://httpbin.org/post");        System.out.println(result);    }    public static String post(String url) {        String result = null;        HttpPost httpPost = new HttpPost(url);        // 表单参数        List<NameValuePair> nvps = new ArrayList<>();        // POST 申请参数        nvps.add(new BasicNameValuePair("username", "wdbyte.com"));        nvps.add(new BasicNameValuePair("password", "secret"));        httpPost.setEntity(new UrlEncodedFormEntity(nvps));        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {            try (CloseableHttpResponse response = httpclient.execute(httpPost)) {                System.out.println(response.getVersion()); // HTTP/1.1                System.out.println(response.getCode()); // 200                System.out.println(response.getReasonPhrase()); // OK                HttpEntity entity = response.getEntity();                // 获取响应信息                result = EntityUtils.toString(entity);                // 确保流被齐全生产                EntityUtils.consume(entity);            }        } catch (IOException | ParseException e) {            e.printStackTrace();        }        return result;    }}

输入信息:

HTTP/1.1200OK{  "args": {},   "data": "",   "files": {},   "form": {    "password": "secret",     "username": "wdbyte.com"  },   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Content-Length": "35",     "Content-Type": "application/x-www-form-urlencoded; charset=ISO-8859-1",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb1ac8-489b2100728c81d70797a482"  },   "json": null,   "origin": "183.128.136.89",   "url": "http://httpbin.org/post"}

上面是通过 Wireshark 抓包失去的申请信息:

POST /post HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateContent-Length: 35Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1Host: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/17)username=wdbyte.com&password=secret

HttpClient 5 Fluent POST

应用 Apache HttpClient 5 提供的 Fluent API 能够更便捷的发动 POST 申请,然而可操作的中央较少。

一样发送一个简略的表单参数:username=wdbyte.com&password=secret

package com.wdbyte.httpclient;import java.io.IOException;import org.apache.hc.client5.http.fluent.Request;import org.apache.hc.core5.http.message.BasicNameValuePair;/*** @author https://www.wdbyte.com */public class HttpClient5PostFluent {    public static void main(String[] args) {        String result = post("http://httpbin.org/post");        System.out.println(result);    }    public static String post(String url) {        String result = null;        Request request = Request.post(url);        // POST 申请参数        request.bodyForm(            new BasicNameValuePair("username", "wdbyte.com"),            new BasicNameValuePair("password", "secret"));        try {            result = request.execute().returnContent().asString();        } catch (IOException e) {            e.printStackTrace();        }        return result;    }}

输入信息:

{  "args": {},   "data": "",   "files": {},   "form": {    "password": "secret",     "username": "wdbyte.com"  },   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Content-Length": "35",     "Content-Type": "application/x-www-form-urlencoded; charset=ISO-8859-1",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb1c8a-7aee8c004f06919f31a2b533"  },   "json": null,   "origin": "183.128.136.89",   "url": "http://httpbin.org/post"}

HttpClient5 POST JSON 参数

应用 StringEntity 类存入 JSON 参数。

package com.wdbyte.httpclient;import java.io.IOException;import org.apache.hc.client5.http.classic.methods.HttpPost;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.ParseException;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.io.entity.StringEntity;/*** @author https://www.wdbyte.com */public class HttpClient5PostWithJson {    public static void main(String[] args) {        String json = "{"            + "    \"password\": \"secret\","            + "    \"username\": \"wdbyte.com\""            + "}";        String result = post("http://httpbin.org/post", json);        System.out.println(result);    }    public static String post(String url, String jsonBody) {        String result = null;        HttpPost httpPost = new HttpPost(url);        httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));              try (CloseableHttpClient httpclient = HttpClients.createDefault()) {            try (CloseableHttpResponse response = httpclient.execute(httpPost)) {                // 获取响应信息                result = EntityUtils.toString(response.getEntity());            }        } catch (IOException | ParseException e) {            e.printStackTrace();        }        return result;    }}

输入信息:

{  "args": {},   "data": "{    \"password\": \"secret\",    \"username\": \"wdbyte.com\"}",   "files": {},   "form": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Content-Length": "55",     "Content-Type": "text/plain; charset=ISO-8859-1",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb1dbb-5a963c1d798b06be3ee1a15e"  },   "json": {    "password": "secret",     "username": "wdbyte.com"  },   "origin": "183.128.136.89",   "url": "http://httpbin.org/post"}

上面是通过 Wireshark 抓包失去的申请响应信息:

// 申请信息POST /post HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateContent-Length: 55Content-Type: application/json; charset=UTF-8Host: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/17){    "password": "secret",    "username": "wdbyte.com"}// 响应信息HTTP/1.1 200 OKDate: Tue, 28 Jun 2022 15:30:17 GMTContent-Type: application/jsonContent-Length: 573Connection: keep-aliveServer: gunicorn/19.9.0Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true{  "args": {},   "data": "{    \"password\": \"secret\",    \"username\": \"wdbyte.com\"}",   "files": {},   "form": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Content-Length": "55",     "Content-Type": "application/json; charset=UTF-8",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",     "X-Amzn-Trace-Id": "Root=1-62bb1e89-64db55730a0361c720232ccd"  },   "json": {    "password": "secret",     "username": "wdbyte.com"  },   "origin": "183.128.136.89",   "url": "http://httpbin.org/post"}

HttpClient 5 设置超时

应用 RequestConfig 对象来配置超时工夫。

package com.wdbyte.httpclient;import java.io.IOException;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.config.RequestConfig;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.ParseException;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.util.Timeout;/*** @author https://www.wdbyte.com */public class HttpClient5GetWithTimeout {    public static void main(String[] args) {        String result = get("http://httpbin.org/get");        System.out.println(result);    }    public static String get(String url) {        String resultContent = null;        // 设置超时工夫        RequestConfig config = RequestConfig.custom()            .setConnectTimeout(Timeout.ofMilliseconds(5000L))            .setConnectionRequestTimeout(Timeout.ofMilliseconds(5000L))            .setResponseTimeout(Timeout.ofMilliseconds(5000L))            .build();        // 申请级别的超时        HttpGet httpGet = new HttpGet(url);        //httpGet.setConfig(config);        //try (CloseableHttpClient httpclient = HttpClients.createDefault()) {        // 客户端级别的超时        try (CloseableHttpClient httpclient = HttpClients.custom().setDefaultRequestConfig(config).build()) {            try (CloseableHttpResponse response = httpclient.execute(httpGet)) {                // 获取状态码                System.out.println(response.getVersion()); // HTTP/1.1                System.out.println(response.getCode()); // 200                System.out.println(response.getReasonPhrase()); // OK                HttpEntity entity = response.getEntity();                // 获取响应信息                resultContent = EntityUtils.toString(entity);            }        } catch (IOException | ParseException e) {            e.printStackTrace();        }        return resultContent;    }}

HttpClient 5 异步申请

上面演示三种 HttpClient 5 异步申请形式。

package com.wdbyte.httpclient;import java.io.IOException;import java.nio.CharBuffer;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutionException;import java.util.concurrent.Future;import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer;import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;import org.apache.hc.client5.http.impl.async.HttpAsyncClients;import org.apache.hc.core5.concurrent.FutureCallback;import org.apache.hc.core5.http.ContentType;import org.apache.hc.core5.http.HttpException;import org.apache.hc.core5.http.HttpResponse;import org.apache.hc.core5.http.nio.AsyncRequestProducer;import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;/** * HttpClient 5 异步申请* @author https://www.wdbyte.com * @date 2022/06/25 */public class HttpClient5Async {    public static void main(String[] args) {        getAsync1("http://httpbin.org/get");        getAsync2("http://httpbin.org/get");        getAsync3("http://httpbin.org/get");    }    /**     * 异步申请     *     * @param url     * @return     */    public static String getAsync1(String url) {        try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {            // 开始 http clinet            httpclient.start();            // 执行申请            SimpleHttpRequest request1 = SimpleHttpRequests.get(url);            Future<SimpleHttpResponse> future = httpclient.execute(request1, null);            // 期待直到返回结束            SimpleHttpResponse response1 = future.get();            System.out.println("getAsync1:" + request1.getRequestUri() + "->" + response1.getCode());        } catch (IOException | ExecutionException | InterruptedException e) {            throw new RuntimeException(e);        }        return null;    }    /**     * 异步申请,依据响应状况回调     *     * @param url     * @return     */    public static String getAsync2(String url) {        try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {            // 开始 http clinet            httpclient.start();            // 依据申请响应状况进行回调操作            CountDownLatch latch = new CountDownLatch(1);            SimpleHttpRequest request = SimpleHttpRequests.get(url);            httpclient.execute(request, new FutureCallback<SimpleHttpResponse>() {                @Override                public void completed(SimpleHttpResponse response2) {                    latch.countDown();                    System.out.println("getAsync2:" + request.getRequestUri() + "->" + response2.getCode());                }                @Override                public void failed(Exception ex) {                    latch.countDown();                    System.out.println("getAsync2:" + request.getRequestUri() + "->" + ex);                }                @Override                public void cancelled() {                    latch.countDown();                    System.out.println("getAsync2:" + request.getRequestUri() + " cancelled");                }            });            latch.await();        } catch (IOException | InterruptedException e) {            throw new RuntimeException(e);        }        return null;    }    /**     * 异步申请,对响应流做点什么     *     * @param url     * @return     */    public static String getAsync3(String url) {        try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {            // 开始 http clinet            httpclient.start();            // 依据申请响应状况进行回调操作            SimpleHttpRequest request = SimpleHttpRequests.get(url);            CountDownLatch latch = new CountDownLatch(1);            AsyncRequestProducer producer = AsyncRequestBuilder.get("http://httpbin.org/get").build();            AbstractCharResponseConsumer<HttpResponse> consumer3 = new AbstractCharResponseConsumer<HttpResponse>() {                HttpResponse response;                @Override                protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {                    System.out.println("getAsync3: 开始响应....");                    this.response = response;                }                @Override                protected int capacityIncrement() {                    return Integer.MAX_VALUE;                }                @Override                protected void data(CharBuffer data, boolean endOfStream) throws IOException {                    System.out.println("getAsync3: 收到数据....");                    // Do something useful                }                @Override                protected HttpResponse buildResult() throws IOException {                    System.out.println("getAsync3: 接管结束...");                    return response;                }                @Override                public void releaseResources() {                }            };            httpclient.execute(producer, consumer3, new FutureCallback<HttpResponse>() {                @Override                public void completed(HttpResponse response) {                    latch.countDown();                    System.out.println("getAsync3: "+request.getRequestUri() + "->" + response.getCode());                }                @Override                public void failed(Exception ex) {                    latch.countDown();                    System.out.println("getAsync3: "+request.getRequestUri() + "->" + ex);                }                @Override                public void cancelled() {                    latch.countDown();                    System.out.println("getAsync3: "+request.getRequestUri() + " cancelled");                }            });            latch.await();        } catch (IOException | InterruptedException e) {            throw new RuntimeException(e);        }        return null;    }}

输入后果:

getAsync1:/get->200getAsync2:/get->200getAsync3: 开始响应....getAsync3: 收到数据....getAsync3: 收到数据....getAsync3: 收到数据....getAsync3: 接管结束...getAsync3: /get->200

HttpClient 5 获取 Cookie

申请 http://httpbin.org/cookies/set/cookieName/www.wdbyte.com 的响应中会带有一个Cookie 信息,其中 name 为 cookieName,value 为 www.wdbyte.com,咱们以此用作测试。

Postman 申请测试,能够看到响应了 Cookie 信息。

上面编写 Java 代码进行申请测试

package com.wdbyte.httpclient;import java.util.List;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.cookie.BasicCookieStore;import org.apache.hc.client5.http.cookie.Cookie;import org.apache.hc.client5.http.cookie.CookieStore;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;import org.apache.hc.client5.http.protocol.HttpClientContext;import org.apache.hc.core5.http.io.entity.EntityUtils;/** * 这个例子演示了应用本地HTTP上下文填充, 自定义属性 */public class HttpClient5WithCookie {    public static void main(final String[] args) throws Exception {        try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {            // 创立一个本地的 Cookie 存储            final CookieStore cookieStore = new BasicCookieStore();            // BasicClientCookie clientCookie = new BasicClientCookie("name", "www.wdbyte.com");            // clientCookie.setDomain("http://httpbin.org/cookies");            // 过期工夫            // clientCookie.setExpiryDate(new Date());            // 增加到本地 Cookie            // cookieStore.addCookie(clientCookie);            // 创立本地 HTTP 申请上下文 HttpClientContext            final HttpClientContext localContext = HttpClientContext.create();            // 绑定 cookieStore 到 localContext            localContext.setCookieStore(cookieStore);            final HttpGet httpget = new HttpGet("http://httpbin.org/cookies/set/cookieName/www.wdbyte.com");            System.out.println("执行申请 " + httpget.getMethod() + " " + httpget.getUri());            // 获取 Coolie 信息            try (final CloseableHttpResponse response = httpclient.execute(httpget, localContext)) {                System.out.println("----------------------------------------");                System.out.println(response.getCode() + " " + response.getReasonPhrase());                final List<Cookie> cookies = cookieStore.getCookies();                for (int i = 0; i < cookies.size(); i++) {                    System.out.println("Local cookie: " + cookies.get(i));                }                EntityUtils.consume(response.getEntity());            }        }    }}

输入后果:

执行申请 GET http://httpbin.org/cookies/set/cookieName/www.wdbyte.com----------------------------------------200 OKLocal cookie: [name: cookieName; value: www.wdbyte.com; domain: httpbin.org; path: /; expiry: null]

HttpClient 5 读取文件内容申请

筹备一个 JSON 内容格局的文件 params.json。

{"name":"www.wdbyte.com"}

读取这个文件作为申请参数发动申请。

package com.wdbyte.httpclient;import java.io.File;import java.io.FileInputStream;import org.apache.hc.client5.http.classic.methods.HttpPost;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.ContentType;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.io.entity.FileEntity;import org.apache.hc.core5.http.io.entity.InputStreamEntity;/** * 加载数据流作为 POST 申请参数 */public class HttpClient5ChunkEncodedPost {    public static void main(final String[] args) throws Exception {        String params = "/Users/darcy/params.json";        try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {            final HttpPost httppost = new HttpPost("http://httpbin.org/post");            final InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(params), -1,                ContentType.APPLICATION_JSON);            // 也能够应用 FileEntity 的模式            // FileEntity reqEntity = new FileEntity(new File(params), ContentType.APPLICATION_JSON);            httppost.setEntity(reqEntity);            System.out.println("执行申请 " + httppost.getMethod() + " " + httppost.getUri());            try (final CloseableHttpResponse response = httpclient.execute(httppost)) {                System.out.println("----------------------------------------");                System.out.println(response.getCode() + " " + response.getReasonPhrase());                System.out.println(EntityUtils.toString(response.getEntity()));            }        }    }}

输入后果:

执行申请 POST http://httpbin.org/post----------------------------------------200 OK{  "args": {},   "data": "{\"name\":\"www.wdbyte.com\"}\n",   "files": {},   "form": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Content-Length": "26",     "Content-Type": "application/json; charset=UTF-8",     "Host": "httpbin.org",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",     "X-Amzn-Trace-Id": "Root=1-62ee4d95-1f956d4303cea09c52694c86"  },   "json": {    "name": "www.wdbyte.com"  },   "origin": "42.120.74.238",   "url": "http://httpbin.org/post"}

HttpClient 5 表单登录

表单登录能够了解为发动一个携带了认证信息的申请,而后失去响应的 Cookie 的过程。当然这里不仅仅实用于表单登录,也能够是简略的发动一个携带了表单信息的申请。

本应该应用 POST 申请发送表单参数测试,然而在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 申请

示例代码:

package com.wdbyte.httpclient;import java.util.ArrayList;import java.util.List;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.cookie.BasicCookieStore;import org.apache.hc.client5.http.cookie.Cookie;import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.NameValuePair;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.message.BasicNameValuePair;/** * 演示基于表单的登录 *  * @author https://www.wdbyte.com */public class HttpClient5FormLogin {    public static void main(final String[] args) throws Exception {        final BasicCookieStore cookieStore = new BasicCookieStore();        try (final CloseableHttpClient httpclient = HttpClients.custom()                .setDefaultCookieStore(cookieStore)                .build()) {            // 本应该应用 POST 申请发送表单参数,然而在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 申请            // HttpPost httpPost = new HttpPost("http://httpbin.org/cookies/set/username/wdbyte.com");            HttpGet httpPost = new HttpGet("http://httpbin.org/cookies/set/username/wdbyte.com");            // POST 表单申请参数            List<NameValuePair> nvps = new ArrayList<>();            nvps.add(new BasicNameValuePair("username", "wdbyte.com"));            nvps.add(new BasicNameValuePair("password", "secret"));            httpPost.setEntity(new UrlEncodedFormEntity(nvps));            try (final CloseableHttpResponse response2 = httpclient.execute(httpPost)) {                final HttpEntity entity = response2.getEntity();                System.out.println("Login form get: " + response2.getCode() + " " + response2.getReasonPhrase());                System.out.println("以后响应信息 "+EntityUtils.toString(entity));;                System.out.println("Post 登录 Cookie:");                final List<Cookie> cookies = cookieStore.getCookies();                if (cookies.isEmpty()) {                    System.out.println("None");                } else {                    for (int i = 0; i < cookies.size(); i++) {                        System.out.println("- " + cookies.get(i));                    }                }            }        }    }}

输入后果:

Login form get: 200 OK以后响应信息 {  "cookies": {    "username": "wdbyte.com"  }}Post 登录 Cookie:- [name: username; value: wdbyte.com; domain: httpbin.org; path: /; expiry: null]

HttpClient 5 Basic Authorization

HTTP 根本认证(Basic Authorization)是一种比较简单的认证实现,次要流程如下

  1. 申请一个须要进行根本认证的 HTTP 接口,然而没有携带认证信息。
  2. 此时会响应 401 状态码,并在响应 header 中的 WWW-Authenticate 提醒须要进行根本认证。
  3. 用户把须要提交认证信息进行冒号拼接,而后进行 base64 编码,再在失去的字符串结尾拼接上 Basic 放入申请头 Authorization 中。
  4. 认证胜利,响应胜利。

你能够通过浏览器关上上面这个 URL 进行根本认证测试。

http://httpbin.org/basic-auth...

在 Apache HttpClient 5 中的实现形式。

package com.wdbyte.httpclient;import org.apache.hc.client5.http.auth.AuthScope;import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.io.entity.EntityUtils;/** * 一个简略的示例,它应用HttpClient执行HTTP申请; * 一个须要进行用户身份验证的指标站点。 */public class HttpClient5BasicAuthentication {    public static void main(final String[] args) throws Exception {        final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();        credsProvider.setCredentials(                new AuthScope("httpbin.org", 80),                new UsernamePasswordCredentials("admin", "123456".toCharArray()));        try (final CloseableHttpClient httpclient = HttpClients.custom()                .setDefaultCredentialsProvider(credsProvider)                .build()) {            final HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/admin/123456");            System.out.println("执行申请" + httpget.getMethod() + " " + httpget.getUri());            try (final CloseableHttpResponse response = httpclient.execute(httpget)) {                System.out.println("----------------------------------------");                System.out.println(response.getCode() + " " + response.getReasonPhrase());                System.out.println(EntityUtils.toString(response.getEntity()));            }        }    }}

输入后果:

执行申请GET http://httpbin.org/basic-auth/user/passwd----------------------------------------200 OK{  "authenticated": true,   "user": "user"}

通过抓包能够看到残缺的 HTTP 申请响应过程。

// 申请GET /basic-auth/user/passwd HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateHost: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)// 响应HTTP/1.1 401 UNAUTHORIZEDDate: Sat, 06 Aug 2022 08:25:33 GMTContent-Length: 0Connection: keep-aliveServer: gunicorn/19.9.0WWW-Authenticate: Basic realm="Fake Realm"Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true// 申请GET /basic-auth/user/passwd HTTP/1.1Host: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)Authorization: Basic dXNlcjpwYXNzd2Q=// 响应HTTP/1.1 200 OKDate: Sat, 06 Aug 2022 08:25:33 GMTContent-Type: application/jsonContent-Length: 47Connection: keep-aliveServer: gunicorn/19.9.0Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true{  "authenticated": true,   "user": "user"}

HttpClient 5 Digest Authorization

HTTP Basic Authorization 的毛病不言而喻,明码通过明文传输存在肯定的平安危险,Digest Authorization 认证形式解决了明文传输的问题,这里不过多介绍 Digest 的相干内容,通过一个图简略的示意 Digest 认证形式的流程。

上面是代码演示。

package com.wdbyte.httpclient;import org.apache.hc.client5.http.auth.AuthExchange;import org.apache.hc.client5.http.auth.AuthScheme;import org.apache.hc.client5.http.auth.AuthScope;import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;import org.apache.hc.client5.http.impl.auth.DigestScheme;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.client5.http.protocol.HttpClientContext;import org.apache.hc.core5.http.HttpHost;import org.apache.hc.core5.http.io.entity.EntityUtils;/** * * HttpClient如何验证多个申请的示例 * 应用雷同的摘要计划。在初始申请/响应替换之后 * 共享雷同执行上下文的所有后续申请都能够重用 * 要向服务器进行身份验证的最初一个摘要nonce值。 */public class HttpClient5PreemptiveDigestAuthentication {    public static void main(final String[] args) throws Exception {        try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {            final HttpHost target = new HttpHost("http", "httpbin.org", 80);            final HttpClientContext localContext = HttpClientContext.create();            final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();            credentialsProvider.setCredentials(                    new AuthScope(target),                    new UsernamePasswordCredentials("admin", "123456".toCharArray()));            localContext.setCredentialsProvider(credentialsProvider);            final HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/admin/123456");            System.out.println("执行申请 " + httpget.getMethod() + " " + httpget.getUri());            for (int i = 0; i < 2; i++) {                try (final CloseableHttpResponse response = httpclient.execute(target, httpget, localContext)) {                    System.out.println("----------------------------------------");                    System.out.println(response.getCode() + " " + response.getReasonPhrase());                    EntityUtils.consume(response.getEntity());                    final AuthExchange authExchange = localContext.getAuthExchange(target);                    if (authExchange != null) {                        final AuthScheme authScheme = authExchange.getAuthScheme();                        if (authScheme instanceof DigestScheme) {                            final DigestScheme digestScheme = (DigestScheme) authScheme;                            System.out.println("Nonce: " + digestScheme.getNonce() +                                    "; count: " + digestScheme.getNounceCount());                        }                    }                }            }        }    }}

通过抓包工具能够清晰的看到 2 次申请的流程,在最初一次申请中,间接共享了认证信息,没有再次的从新认证的流程。

// 1. 申请GET /digest-auth/auth/admin/123456 HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateHost: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)// 2. 详情,提醒认证,给出参数HTTP/1.1 401 UNAUTHORIZEDDate: Fri, 12 Aug 2022 07:11:06 GMTContent-Type: text/html; charset=utf-8Content-Length: 0Connection: keep-aliveServer: gunicorn/19.9.0WWW-Authenticate: Digest realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", qop="auth", opaque="64b7f68b386c3acc38131f7472aa2079", algorithm=MD5, stale=FALSESet-Cookie: stale_after=never; Path=/Set-Cookie: fake=fake_value; Path=/Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true// 3. 参数+明码 加密后再次申请GET /digest-auth/auth/admin/123456 HTTP/1.1Host: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)Cookie: fake=fake_value; stale_after=neverAuthorization: Digest username="admin", realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", uri="/digest-auth/auth/admin/123456", response="7c6726f8ac54c1ba28e19c71b2fc7338", qop=auth, nc=00000001, cnonce="2fa61501d47a9d39", algorithm=MD5, opaque="64b7f68b386c3acc38131f7472aa2079"// 4. 认证胜利,响应HTTP/1.1 200 OKDate: Fri, 12 Aug 2022 07:11:08 GMTContent-Type: application/jsonContent-Length: 48Connection: keep-aliveServer: gunicorn/19.9.0Set-Cookie: fake=fake_value; Path=/Set-Cookie: stale_after=never; Path=/Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true{  "authenticated": true,   "user": "admin"}// 5. 再次申请,共享了登录状态。GET /digest-auth/auth/admin/123456 HTTP/1.1Accept-Encoding: gzip, x-gzip, deflateHost: httpbin.orgConnection: keep-aliveUser-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)Cookie: fake=fake_value; stale_after=neverAuthorization: Digest username="admin", realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", uri="/digest-auth/auth/admin/123456", response="9955ac79f6a51a876a326449447f549d", qop=auth, nc=00000002, cnonce="2fa61501d47a9d39", algorithm=MD5, opaque="64b7f68b386c3acc38131f7472aa2079"// 5. 认证胜利,响应HTTP/1.1 200 OKDate: Fri, 12 Aug 2022 07:11:09 GMTContent-Type: application/jsonContent-Length: 48Connection: keep-aliveServer: gunicorn/19.9.0Set-Cookie: fake=fake_value; Path=/Set-Cookie: stale_after=never; Path=/Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true{  "authenticated": true,   "user": "admin"}

HttpClient 5 拦截器

HttpClient 5 中的拦截器能够对申请过程的各个阶段进行拦挡解决,通过 HttpClientBuilder 中的对于 Interceptor 的办法能够看到能够进行拦挡的节点。

上面编写一个示例,发动三次申请,每次申请都在申请头 herader 中减少一个 request-id 参数,而后对 request-id 值为 2 的申请间接响应 404 完结。

package com.wdbyte.httpclient;import java.io.IOException;import java.util.concurrent.atomic.AtomicLong;import org.apache.hc.client5.http.classic.ExecChain;import org.apache.hc.client5.http.classic.ExecChain.Scope;import org.apache.hc.client5.http.classic.ExecChainHandler;import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.ChainElement;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.ClassicHttpRequest;import org.apache.hc.core5.http.ClassicHttpResponse;import org.apache.hc.core5.http.ContentType;import org.apache.hc.core5.http.EntityDetails;import org.apache.hc.core5.http.Header;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.HttpException;import org.apache.hc.core5.http.HttpRequest;import org.apache.hc.core5.http.HttpRequestInterceptor;import org.apache.hc.core5.http.HttpStatus;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.io.entity.StringEntity;import org.apache.hc.core5.http.message.BasicClassicHttpResponse;import org.apache.hc.core5.http.protocol.HttpContext;/** * 展现如何在申请和响应时进行拦挡进行自定义解决。 */public class HttpClient5Interceptors {    public static void main(final String[] args) throws Exception {        try (final CloseableHttpClient httpclient = HttpClients.custom()            // 增加一个申请 id 到申请 header            .addRequestInterceptorFirst(new HttpRequestInterceptor() {                private final AtomicLong count = new AtomicLong(0);                @Override                public void process(                    final HttpRequest request,                    final EntityDetails entity,                    final HttpContext context) throws HttpException, IOException {                    request.setHeader("request-id", Long.toString(count.incrementAndGet()));                }            })            .addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "custom", new ExecChainHandler() {                // 申请 id 为 2 的,模仿 404 响应,并自定义响应的内容。                @Override                public ClassicHttpResponse execute(                    final ClassicHttpRequest request,                    final Scope scope,                    final ExecChain chain) throws IOException, HttpException {                    final Header idHeader = request.getFirstHeader("request-id");                    if (idHeader != null && "2".equalsIgnoreCase(idHeader.getValue())) {                        final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND,                            "Oppsie");                        response.setEntity(new StringEntity("bad luck", ContentType.TEXT_PLAIN));                        return response;                    } else {                        return chain.proceed(request, scope);                    }                }            })            .build()) {            for (int i = 0; i < 3; i++) {                final HttpGet httpget = new HttpGet("http://httpbin.org/get");                try (final CloseableHttpResponse response = httpclient.execute(httpget)) {                    System.out.println("----------------------------------------");                    System.out.println("执行申请 " + httpget.getMethod() + " " + httpget.getUri());                    System.out.println(response.getCode() + " " + response.getReasonPhrase());                    System.out.println(EntityUtils.toString(response.getEntity()));                }            }        }    }}

输入后果。

----------------------------------------执行申请 GET http://httpbin.org/get200 OK{  "args": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "Request-Id": "1",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",     "X-Amzn-Trace-Id": "Root=1-62f615ba-658ccd42182d22534dbba82c"  },   "origin": "42.120.75.221",   "url": "http://httpbin.org/get"}----------------------------------------执行申请 GET http://httpbin.org/get404 Oppsiebad luck----------------------------------------执行申请 GET http://httpbin.org/get200 OK{  "args": {},   "headers": {    "Accept-Encoding": "gzip, x-gzip, deflate",     "Host": "httpbin.org",     "Request-Id": "3",     "User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",     "X-Amzn-Trace-Id": "Root=1-62f615bb-4eb6ba10736ace0e21d0cb8c"  },   "origin": "42.120.75.221",   "url": "http://httpbin.org/get"}

判若两人,文章代码都寄存在 Github.com/niumoo/javaNotes.

<完>

文章继续更新,能够微信搜一搜「 程序猿阿朗 」或拜访「程序猿阿朗博客 」第一工夫浏览。本文 Github.com/niumoo/JavaNotes 曾经收录,有很多知识点和系列文章,欢送Star。