【HTTP 通信】HttpClient 基础篇

更新日期:2022/03/24
愿你如阳光,明媚不忧伤。

友情链接 【初步了解网络原理】防火墙入站出站规则和网络协议

友情链接 【年薪百万之IT界大神成长之路】爆肝两天整理出的HTTP请求和HTTP响应

友情链接 【年薪百万之IT界大神成长之路】一文带你搞懂SSL证书和WEB数据加密

 


1. Http 简介

Http(HyperText Transfer Protocol)超文本传输协议:是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因特网上应用最为广泛的一种网络传输协议(基于 TCP/IP 通信协议来传递数据),所有的 WWW 文件都必须遵守这个标准。

  • HTTP是无连接的:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。(可以节省传输时间)
  • HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。(MIME-type内容类型)
  • HTTP是无状态:无状态是指协议对于事务处理没有记忆能力。如果后续处理需要前面的信息,则它必须重传。(可能导致每次连接传送的数据量增大)
  • 工作原理
    1.客户与服务器建立连接;
    2.客户向服务器提出请求;
    3.服务器接受请求,并根据请求返回相应的文件作为应答;
    4.客户与服务器关闭连接。

  • 存在的问题
    1.请求信息明文传输,容易被窃听截取。
    2.数据的完整性未校验,容易被篡改。
    3.没有验证对方身份,存在冒充危险。


2. Https 简介

Https(Hyper Text Transfer Protocol over SecureSocket Layer)超文本传输安全协议:为了解决上述 HTTP 存在的问题,就构建了以安全为目标可进行加密传输、身份认证的 HTTPS 协议。该协议主要通过数字证书、加密算法、非对称密钥等技术完成互联网数据传输加密,实现互联网传输安全保护。

  • 数据保密性:保证数据内容在传输的过程中不会被第三方查看。就像快递员传递包裹一样,都进行了封装,别人无法获知里面装了什么。
  • 数据完整性:及时发现被第三方篡改的传输内容。就像快递员虽然不知道包裹里装了什么东西,但他有可能中途掉包,数据完整性就是指如果被掉包,我们能轻松发现并拒收。
  • 身份校验安全性:保证数据到达用户期望的目的地。就像我们邮寄包裹时,虽然是一个封装好的未掉包的包裹,但必须确定这个包裹不会送错地方,通过身份校验来确保送对了地方。
  • 工作原理
    1.客户端将它所支持的算法列表和一个用作产生密钥的随机数发送给服务器;
    2.服务器从算法列表中选择一种加密算法,并将它和一份包含服务器公用密钥的证书发送给客户端;该证书还包含了用于认证目的的服务器标识,服务器同时还提供了一个用作产生密钥的随机数;
    3.客户端对服务器的证书进行验证,并抽取服务器的公用密钥;然后,再产生一个称作 pre_master_secret 的随机密码串,并使用服务器的公用密钥对其进行加密,并将加密后的信息发送给服务器;
    4.客户端与服务器端根据 pre_master_secret 以及客户端与服务器的随机数值独立计算出加密和 MAC密钥;
    5.客户端将所有握手消息的 MAC 值发送给服务器;
    6.服务器将所有握手消息的 MAC 值发送给客户端 。

  • 存在的问题
    1.HTTPS协议多次握手,导致页面的加载时间延长近50%,增加 10%到 20%的耗电;
    2.HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
    3.申请SSL证书需要钱,功能越强大的证书费用越高。
    4.SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。


3. SSL/TLS 简介

SSL/TLS(Secure Sockets Layer / Transport Layer Security)安全套接层 / 传输层安全性协议:是在因特网等 ip 网络上对数据进行加密、发送和接收的协议(通信程序)之一。它可以对发送和接收数据的一对设备之间的通信进行加密,防止中继设备等网络上的其他设备进行冒充、偷看和篡改数据。

  • 发展历史
    SSL 1.0
    从未公开过,因为存在严重的安全漏洞。
    SSL 2.0
    在1995年2月发布,但因为存在数个严重的安全漏洞而被3.0版本替代。
    SSL 3.0
    在1996年发布,是由网景工程师Paul Kocher、Phil Karlton和Alan Freier完全重新设计的。
    TLS 1.0
    IETF将SSL标准化,即RFC 2246,并将其称为TLS(Transport Layer Security)。从技术上讲,TLS 1.0 与 SSL 3.0的差异非常微小。但正如RFC所述"the differences between this protocol and SSL 3.0 are not dramatic, but they are significant enough to preclude interoperability between TLS 1.0 and SSL 3.0"(本协议和SSL 3.0之间的差异并不是显著,却足以排除TLS 1.0和SSL 3.0之间的互操作性)。TLS 1.0包括可以降级到SSL 3.0的实现,这削弱了连接的安全性。
    TLS 1.1
    TLS 1.1在RFC 4346中定义,于2006年4月发表,它是TLS 1.0的更新。在此版本中的差异包括:
    1.添加对CBC攻击的保护;
    2.隐式IV被替换成一个显式的IV;
    3.更改分组密码模式中的填充错误;
    4.支持IANA登记的参数。

    TLS 1.2
    TLS 1.2 在RFC 5246中定义,于2008年8月发表。它基于更早的TLS 1.1规范。主要区别包括:
    1.可使用密码组合选项指定伪随机函数使用SHA-256替换MD5-SHA-1组合。
    2.可使用密码组合选项指定在完成消息的哈希认证中使用SHA-256替换MD5-SHA-1算法,但完成消息中哈希值的长度仍然被截断为96位。
    3.在握手期间MD5-SHA-1组合的数字签名被替换为使用单一Hash方法,默认为SHA-1。
    4.增强服务器和客户端指定Hash和签名算法的能力。
    5.扩大经过身份验证的加密密码,主要用于GCM和CCM模式的AES加密的支持。
    6.添加TLS扩展定义和AES密码组合。所有TLS版本在2011年3月发布的RFC 6176中删除了对SSL的兼容,这样TLS会话将永远无法协商使用的SSL 2.0以避免安全问题。

    TLS 1.3
    TLS 1.3 在RFC 8446中定义,于2018年8月发表。它基于更早的TLS 1.2规范,与TLS 1.2的主要区别包括:
    1.将密钥协商和认证算法从密码包中分离出来。
    2.移除脆弱和较少使用的命名椭圆曲线支持(参见椭圆曲线密码学)。
    3.移除MD5和SHA-224密码散列函数的支持。
    4.请求数字签名,即便使用之前的配置。
    5.集成HKDF和半短暂DH提议。
    6.替换使用PSK和票据的恢复。
    7.支持1-RTT握手并初步支持0-RTT。
    8.通过在(EC)DH密钥协议期间使用临时密钥来保证完善的前向安全性。
    9.放弃许多不安全或过时特性的支持,包括数据压缩、重新协商、非AEAD密码本、静态RSA和静态DH密钥交换、自定义DHE分组、点格式协商、更改密码本规范的协议、UNIX时间的Hello消息,以及长度字段AD输入到AEAD密码本。
    10.禁止用于向后兼容性的SSL和RC4协商。
    11.集成会话散列的使用。
    12.弃用记录层版本号和冻结数以改进向后兼容性。
    13.将一些安全相关的算法细节从附录移动到标准,并将ClientKeyShare降级到附录。
    14.添加带有Poly1305消息验证码的ChaCha20流加密。
    15.添加Ed25519和Ed448数字签名算法。
    16.添加x25519和x448密钥交换协议。
    17.将支持加密服务器名称指示(EncryptedServerNameIndication, ESNI)。

4. HttpClient 简介

HttpClient 现在由 Apache HttpClient 和 HttpCore 模块中的 HttpComponents 项目所开发,这两个模块可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

  • Apache HttpComponents 项目负责创建和维护一个基于 HTTP 和相关协议的底层 Java 组件工具集。
  • HttpCore 是一组低级的 HTTP 传输组件,可用于以最小的占用空间构建定制客户端和服务器端的 HTTP 服务。HttpCore 支持两种 i/o 模型: 基于经典 Java i/o 的阻塞 i/o 模型和基于 Java NIO 的非阻塞、事件驱动 i/o 模型。
  • HttpClient 是基于 HttpCore 的 HTTP/1.1兼容的 HTTP 代理实现。它还为客户端身份验证、 HTTP 状态管理和 HTTP 连接管理提供了可重用的组件。
  • 主要功能
    1.实现了所有 HTTP 的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE);
    2.支持自动转向(POST 和 PUT 方式这种要求接受后继服务的请求方式,暂时不支持自动转向);
    3.支持 HTTPS 协议;
    4.支持代理服务器;
    5.直接获取服务器发送的 response code 和 headers。
    6.设置连接超时的能力等。

5. HttpClient 接口

这个接口仅代表 HTTP 请求执行的最基本的契约。它对请求执行过程没有施加任何限制或特定细节,而将状态管理、身份验证和重定向处理的细节留给单独的实现。

5.1 HttpClient.class

用户提供一个请求对象,HttpClient发送该请求到目标服务器,服务器返回相应的响应对象,如果执行失败则抛出一个异常。
由于是执行在网络上的程序,在运行 execute 方法的时候,需要处理两个异常,分别是 `HttpException` 和 `IOException`。
HttpException:一般是由于网络原因引起的,对于这种异常,HttpClient 会根据你指定的恢复策略自动试着重新执行 execute 方法;
IOException:主要原因可能是在构造 getMethod 的时候传入的协议不对,比如不小心将"http"写成"htp",或者服务器端返回的内容不正常等,该异常发生是不可恢复的。

HttpClient 接口的实现是线程安全的。建议执行多次请求都使用该类的同一个实例。
*****************************************************************
public interface HttpClient {
    /** @deprecated */
    /* 获取此客户机的参数 */
    @Deprecated
    HttpParams getParams();

    /** @deprecated */
    /* 获取此客户端使用的连接管理器 */
    @Deprecated
    ClientConnectionManager getConnectionManager();
    /* 使用默认上下文执行请求 */
    HttpResponse execute(HttpUriRequest var1) throws IOException, ClientProtocolException;
    /* 使用给定上下文执行请求 */
    HttpResponse execute(HttpUriRequest var1, HttpContext var2) throws IOException, ClientProtocolException;
    /* 使用默认上下文向目标执行请求 */
    HttpResponse execute(HttpHost var1, HttpRequest var2) throws IOException, ClientProtocolException;
    /* 使用给定上下文对目标执行请求 */
    HttpResponse execute(HttpHost var1, HttpRequest var2, HttpContext var3) throws IOException, ClientProtocolException;
    /* 使用默认上下文执行请求,并使用给定的响应处理程序处理响应 */
    <T> T execute(HttpUriRequest var1, ResponseHandler<? extends T> var2) throws IOException, ClientProtocolException;
    /* 使用给定上下文执行请求,并使用给定的响应处理程序处理响应 */
    <T> T execute(HttpUriRequest var1, ResponseHandler<? extends T> var2, HttpContext var3) throws IOException, ClientProtocolException;
    /* 使用默认上下文执行对目标的请求,并使用给定的响应处理程序处理响应 */
    <T> T execute(HttpHost var1, HttpRequest var2, ResponseHandler<? extends T> var3) throws IOException, ClientProtocolException;
    /* 使用给定上下文执行对目标的请求,并使用给定的响应处理程序处理响应 */
    <T> T execute(HttpHost var1, HttpRequest var2, ResponseHandler<? extends T> var3, HttpContext var4) throws IOException, ClientProtocolException;
}
*****************************************************************
5.1.1 HttpRequest.class

一个基本的 http 请求,可以获取请求行对象,由请求方法、请求URI、协议/版本三部分构成。

*****************************************************************
public interface HttpRequest extends HttpMessage {
    RequestLine getRequestLine();
}
................................................................
public interface RequestLine {
    String getMethod();

    ProtocolVersion getProtocolVersion();

    String getUri();
}
*****************************************************************
5.1.2 HttpUriRequest.class

HttpRequest 接口的扩展版本,提供了方便的方法来访问请求 URI和方法类型。而且可以中止请求的执行(例如:暂停下载),如果不支持终止操作会抛出异常。

*****************************************************************
public interface HttpUriRequest extends HttpRequest {
    String getMethod();

    URI getURI();

    void abort() throws UnsupportedOperationException;

    boolean isAborted();
}
*****************************************************************
5.1.3 HttpContext.class

HTTP 起初是被设计成一种无状态的、面向请求和响应的协议。然而实际的应用经常需要在请求-响应切换过程中保存状态信息。为了使应用能够维持处理状态,HttpClient 允许 HTTP 请求可以在一个特殊的上下文环境(HttpContext)中执行。如果一个 context 在连续的 HTTP 请求中被复用,那么这些逻辑相关的请求可以参与到同一个逻辑会话中。
HttpContext 功能与 java.util.Map<String, Object>类似,它是一组任意值的集合。一个应用程序可以在请求执行之前填充上下文属性或者在请求执行完成后检查上下文。
在HTTP请求执行的过程中,HttpClient 向 context 添加了以下属性:
· HttpConnection,表示与目标服务器的实际连接
· HttpHost,表示连接的目标
· HttpRoute,表示完整的连接路由
· HttpRequest,表示 Http 请求
· HttpResponse,表示 Http 响应
· lang.Boolean,表示请求是否被完整的发送到目标
· RequestConfig,表示请求的配置
· java.util.List,表示在请求处理过程中接收到的一组重定向地址集合

HttpContext 可以保存多个对象,因而它在多个线程共享时可能并不安全。这里推荐每个线程维持各自HttpContext。

*****************************************************************
public interface HttpContext {
    String RESERVED_PREFIX = "http.";

    Object getAttribute(String var1);

    void setAttribute(String var1, Object var2);

    Object removeAttribute(String var1);
}
*****************************************************************
5.1.4 HttpHost.class

保存描述与主机的 HTTP 连接所需的所有变量。这包括远程主机、端口和协议。
hostname — 主机名(IP 或 DNS 名称),可以为空。
port — 可设置值为 “-1” (则为默认端号)。
protocol — 可以为空,为空则默认为默认的协议。

*****************************************************************
@Contract(
    threading = ThreadingBehavior.IMMUTABLE
)
public final class HttpHost implements Cloneable, Serializable {
    private static final long serialVersionUID = -7529410654042457626L;
    public static final String DEFAULT_SCHEME_NAME = "http";
    protected final String hostname;
    protected final String lcHostname;
    protected final int port;
    protected final String schemeName;
    protected final InetAddress address;
.................................................................
    public HttpHost(String hostname, int port, String scheme) {
        this.hostname = (String)Args.containsNoBlanks(hostname, "Host name");
        this.lcHostname = hostname.toLowerCase(Locale.ROOT);
        if (scheme != null) {
            this.schemeName = scheme.toLowerCase(Locale.ROOT);
        } else {
            this.schemeName = "http";
        }

        this.port = port;
        this.address = null;
    }
.................................................................
    public HttpHost(String hostname, int port) {
        this((String)hostname, port, (String)null);
    }
.................................................................
    public static HttpHost create(String s) {
        Args.containsNoBlanks(s, "HTTP Host");
        String text = s;
        String scheme = null;
        int schemeIdx = s.indexOf("://");
        if (schemeIdx > 0) {
            scheme = s.substring(0, schemeIdx);
            text = s.substring(schemeIdx + 3);
        }

        int port = -1;
        int portIdx = text.lastIndexOf(":");
        if (portIdx > 0) {
            try {
                port = Integer.parseInt(text.substring(portIdx + 1));
            } catch (NumberFormatException var7) {
                throw new IllegalArgumentException("Invalid HTTP host: " + text);
            }

            text = text.substring(0, portIdx);
        }

        return new HttpHost(text, port, scheme);
    }
.................................................................
    public HttpHost(String hostname) {
        this((String)hostname, -1, (String)null);
    }
.................................................................
    public HttpHost(InetAddress address, int port, String scheme) {
        this((InetAddress)Args.notNull(address, "Inet address"), address.getHostName(), port, scheme);
    }
.................................................................
    public HttpHost(InetAddress address, String hostname, int port, String scheme) {
        this.address = (InetAddress)Args.notNull(address, "Inet address");
        this.hostname = (String)Args.notNull(hostname, "Hostname");
        this.lcHostname = this.hostname.toLowerCase(Locale.ROOT);
        if (scheme != null) {
            this.schemeName = scheme.toLowerCase(Locale.ROOT);
        } else {
            this.schemeName = "http";
        }

        this.port = port;
    }
.................................................................
    public HttpHost(InetAddress address, int port) {
        this((InetAddress)address, port, (String)null);
    }
.................................................................
    public HttpHost(InetAddress address) {
        this((InetAddress)address, -1, (String)null);
    }
.................................................................
    public HttpHost(HttpHost httphost) {
        Args.notNull(httphost, "HTTP host");
        this.hostname = httphost.hostname;
        this.lcHostname = httphost.lcHostname;
        this.schemeName = httphost.schemeName;
        this.port = httphost.port;
        this.address = httphost.address;
    }
.................................................................
    public String getHostName() {
        return this.hostname;
    }
.................................................................
    public int getPort() {
        return this.port;
    }
.................................................................
    public String getSchemeName() {
        return this.schemeName;
    }
.................................................................
    public InetAddress getAddress() {
        return this.address;
    }
.................................................................
    public String toURI() {
        StringBuilder buffer = new StringBuilder();
        buffer.append(this.schemeName);
        buffer.append("://");
        buffer.append(this.hostname);
        if (this.port != -1) {
            buffer.append(':');
            buffer.append(Integer.toString(this.port));
        }

        return buffer.toString();
    }
.................................................................
    public String toHostString() {
        if (this.port != -1) {
            StringBuilder buffer = new StringBuilder(this.hostname.length() + 6);
            buffer.append(this.hostname);
            buffer.append(":");
            buffer.append(Integer.toString(this.port));
            return buffer.toString();
        } else {
            return this.hostname;
        }
    }
}
*****************************************************************
5.1.5 ResponseHandler.class

处理从 HttpResponse 生成响应对象的过程的处理程序。需要自己去实现。

*****************************************************************
public interface ResponseHandler<T> {
    T handleResponse(HttpResponse var1) throws ClientProtocolException, IOException;
}
*****************************************************************

6. HttpClient 使用

  • pom.xml
    使用前首先引入 HttpClient 的依赖
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
  • 使用 HttpClient 需要以下 6 个步骤:
    1.创建 HttpClient 的实例;
    2.创建某种连接方法的实例;
    3.调用 HttpClient 实例的 execute 方法来执行 method 实例;
    4.读 response;
    5.释放连接。无论执行方法是否成功,都必须释放连接;
    6.对得到后的内容进行处理。

6.1 快速开始

*****************************************************************
/**
 * @author : ISCCF_A
 * @version : ITGodRoad_0.0.1
 * @dateTime: 2022/03/10 11:17
 * @since : 11
 */
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        quickStart();
    }
.................................................................
    public static void quickStart() throws IOException {
        // 第一步:创建 HttpClient 的实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 第二步:创建某种连接方法的实例,例子为 HttpGet
        HttpGet httpGet = new HttpGet("https://www.baidu.com/");
        // 第三步:调用 HttpClient 实例的 execute 方法来执行 method 实例,注意这里要对异常做处理
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 第四步:读 response;
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
        // 第五步:释放连接。无论执行方法是否成功,都必须释放连接;
        response.close();
        // 第六步:对得到后的内容进行处理。
        System.out.println("响应状态为:" + response.getStatusLine());
    }
}Console------------------------------------------------------
响应内容长度为:-1
17:42:35.830 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[0x8e]>[0xda]9[0xd9][0xdd][0xce]w[0xee][0x9f]>[0xfe]d[0x8c]4<[0xf8]}m[0xf5][0xf6][0xc9][0x93][0xed]f[0xa7][0xd9][0xba][0xda][0xc9]w[0xfe][0x1a]y0[0xaf][0xe4]y[0xcf]b[0xa8]Lg[0x8e]B^X[0x10][0xdb][0xbc][0xf5][0xe6][[0xfd];[0xfd][0x1b][0x0][0x0][0xff][0xff]\[0x85]C[0x1]M[0x9][0x0][0x0]"
17:42:35.831 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://www.baidu.com:443] can be kept alive indefinitely
17:42:35.831 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
17:42:35.831 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://www.baidu.com:443][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20]
响应内容为:<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>ç™¾åº¦ä¸€ä¸‹ï¼Œä½ å°±çŸ¥é“</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>æ–°é—»</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>å
³äºŽç™¾åº¦</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前å¿
读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

响应状态为:HTTP/1.1 200 OK
*****************************************************************

6.2 GET 有参请求

  • 首先我们需要一个测试类,并发布在 Tomcat 中。
*****************************************************************
@RestController
public class Test {
    @RequestMapping(value = "/testHttpGetWithParams", method = RequestMethod.GET)
    public String testHttpGetWithParams(String name, String blog) {
        return name + "の" + blog + " CSDN 博客!";
    }
}
*****************************************************************
  • TestHttpClient.class
*****************************************************************
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        getWithParams("Ouseki", "ITGodRoad");
    }
.................................................................
    public static void getWithParams(String name, String blog) throws IOException {
        // 创建 HttpClient 的实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 将参数放入键值对类 NameValuePair 中,再放入集合中
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("name", name));
        params.add(new BasicNameValuePair("blog", blog));
        // 设置 uri 信息,并将参数集合放入 uri;
        URI uri = null;
        try {
            uri = new URIBuilder().setScheme("http").setHost("localhost")
                    .setPort(8080).setPath("/ITGodRoad/testHttpGetWithParams")
                    // 注:这里也支持一个键值对一个键值对地往里面放 setParameter(String key, String value)
                    .setParameters(params).build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        // 创建 Get 请求
        HttpGet httpGet = new HttpGet(uri);
        // 配置 Get 请求的信息
        CloseableHttpResponse response = null;
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    // 设置连接超时时间(单位毫秒)
                    .setConnectTimeout(5000)
                    // 设置请求超时时间(单位毫秒)
                    .setConnectionRequestTimeout(5000)
                    // socket 读写超时时间(单位毫秒)
                    .setSocketTimeout(5000)
                    // 设置是否允许重定向(默认为true)
                    .setRedirectsEnabled(true).build();
            // 将上面的配置信息放到 Get 请求里
            httpGet.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpGet);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            System.out.println("响应状态为:" + response.getStatusLine());
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        } catch (IOException | ParseException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        }
    }
}Console------------------------------------------------------
响应状态为:HTTP/1.1 200 
响应内容长度为:33
17:23:16.921 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive for 20.0 seconds
17:23:16.922 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
17:23:16.922 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20]
响应内容为:OusekiITGodRoad CSDN 博客!
17:23:16.922 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
17:23:16.923 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
17:23:16.924 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
*****************************************************************

6.3 POST 有参请求(普通参数)

  • 首先我们需要一个测试类,并发布在 Tomcat 中。
*****************************************************************
@RestController
public class Test {
    @RequestMapping(value = "/testHttpPostWithParams", method = RequestMethod.POST)
    public String testHttpPostWithParams(String name, String blog) {
        return name + "の" + blog + " CSDN 博客!";
    }
}
*****************************************************************
  • TestHttpClient.class
    POST 传递普通参数时,方式与 GET 一样。
*****************************************************************
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        postWithParams("Ouseki", "ITGodRoad");
    }
.................................................................
    public static void postWithParams(String name, String blog) throws IOException {
        // 创建 HttpClient 的实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 将参数放入键值对类 NameValuePair 中,再放入集合中
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("name", name));
        params.add(new BasicNameValuePair("blog", blog));
        // 设置 uri 信息,并将参数集合放入 uri;
        URI uri = null;
        try {
            uri = new URIBuilder().setScheme("http").setHost("localhost")
                    .setPort(8080).setPath("/ITGodRoad/testHttpPostWithParams")
                    // 注:这里也支持一个键值对一个键值对地往里面放 setParameter(String key, String value)
                    .setParameters(params).build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        // 创建 Post 请求
        HttpPost httpPost = new HttpPost(uri);
        // 配置 Post 请求的信息
        CloseableHttpResponse response = null;
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    // 设置连接超时时间(单位毫秒)
                    .setConnectTimeout(5000)
                    // 设置请求超时时间(单位毫秒)
                    .setConnectionRequestTimeout(5000)
                    // socket 读写超时时间(单位毫秒)
                    .setSocketTimeout(5000)
                    // 设置是否允许重定向(默认为true)
                    .setRedirectsEnabled(true).build();
            // 将上面的配置信息放到 Get 请求里
            httpPost.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpPost);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            System.out.println("响应状态为:" + response.getStatusLine());
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        } catch (IOException | ParseException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        }
    } 
}Console------------------------------------------------------
响应状态为:HTTP/1.1 200 
响应内容长度为:33
19:06:59.500 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive for 20.0 seconds
19:06:59.500 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
19:06:59.500 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20]
响应内容为:OusekiITGodRoad CSDN 博客!
19:06:59.502 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
19:06:59.503 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
19:06:59.504 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
*****************************************************************

6.4 POST 有参请求(对象参数)

  • 首先我们需要一个测试类,并发布在 Tomcat 中。
*****************************************************************
@RestController
public class Test {
    @RequestMapping(value = "/testHttpPostWithObject", method = RequestMethod.POST)
    public String testHttpPostWithObject(@RequestBody HttpUser httpUser) {
        return httpUser.toString();
    }
}
*****************************************************************
  • TestHttpClient.class
*****************************************************************
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        HttpUser httpUser = new HttpUser("Ouseki", "ITGodRoad");
        postWithObject(httpUser);
    }
.................................................................
    public static void postWithObject(HttpUser httpUser) throws IOException {
        // 创建 HttpClient 的实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 设置 uri 信息,并将参数集合放入 uri;
        URI uri = null;
        try {
            uri = new URIBuilder().setScheme("http").setHost("localhost")
                    .setPort(8080).setPath("/ITGodRoad/testHttpPostWithObject")
                    .build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        // 创建 Post 请求
        HttpPost httpPost = new HttpPost(uri);
        // 用阿里的 fastjson,将 Object 转换为 json 字符串;
        String jsonString = JSON.toJSONString(httpUser);
        // 将转换好的 json 字符串放入 Post 请求的请求体中
        httpPost.setEntity(new StringEntity(jsonString, StandardCharsets.UTF_8));
        // 设置请求头类型
        httpPost.setHeader("Content-Type", "application/json;charset=utf8");
        // 配置 Post 请求的信息
        CloseableHttpResponse response = null;
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    // 设置连接超时时间(单位毫秒)
                    .setConnectTimeout(5000)
                    // 设置请求超时时间(单位毫秒)
                    .setConnectionRequestTimeout(5000)
                    // socket 读写超时时间(单位毫秒)
                    .setSocketTimeout(5000)
                    // 设置是否允许重定向(默认为true)
                    .setRedirectsEnabled(true).build();
            // 将上面的配置信息放到 Get 请求里
            httpPost.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpPost);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            System.out.println("响应状态为:" + response.getStatusLine());
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        } catch (IOException | ParseException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        }
    }
}Console------------------------------------------------------
响应状态为:HTTP/1.1 200 
响应内容长度为:33
19:35:09.419 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive for 20.0 seconds
19:35:09.420 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
19:35:09.420 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20]
响应内容为:OusekiITGodRoad CSDN 博客!
19:35:09.421 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
19:35:09.424 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
19:35:09.425 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
*****************************************************************

7. 解决响应乱码问题

细心的同学应该发现6.1章节快速访问百度时,返回的内容乱码了,我们只需要设置一下返回内容的编码即可正常显示。

*****************************************************************
/**
 * @author : ISCCF_A
 * @version : ITGodRoad_0.0.1
 * @dateTime: 2022/03/10 11:17
 * @since : 11
 */
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        quickStart();
    }
.................................................................
    public static void quickStart() throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("https://www.baidu.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            // 这里设置一下编码格式
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity,StandardCharsets.UTF_8));
        }
        response.close();
        System.out.println("响应状态为:" + response.getStatusLine());
    }
}Console------------------------------------------------------
响应内容长度为:-1
19:46:54.584 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[0x8e]>[0xda]9[0xd9][0xdd][0xce]w[0xee][0x9f]>[0xfe]d[0x8c]4<[0xf8]}m[0xf5][0xf6][0xc9][0x93][0xed]f[0xa7][0xd9][0xba][0xda][0xc9]w[0xfe][0x1a]y0[0xaf][0xe4]y[0xcf]b[0xa8]Lg[0x8e]B^X[0x10][0xdb][0xbc][0xf5][0xe6][[0xfd];[0xfd][0x1b][0x0][0x0][0xff][0xff]\[0x85]C[0x1]M[0x9][0x0][0x0]"
19:46:54.585 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://www.baidu.com:443] can be kept alive indefinitely
19:46:54.585 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
19:46:54.585 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://www.baidu.com:443][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20]
响应内容为:<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

响应状态为:HTTP/1.1 200 OK
*****************************************************************
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值