前面介绍了基于HttpURLConnection的网络访问请求,包括GET方式调用接口、POST方式调用接口、下载网络文件、上传本地文件这四种HTTP操作。虽然通过HttpURLConnection能够实现相应的业务功能,但是它的编码过程却有些繁琐,需要时时刻刻注意有关细节,一不留神便会掉到坑里。比如下列编码细节就经常令初学者头痛不已:
1、HttpURLConnection工具独自一人承担了所有的方法实现,分不清哪些方法与请求有关,哪些方法与应答有关;
2、HTTP调用的步骤太多,诸如参数设置、开启连接、写入请求报文、读取应答报文、断开连接这些操作的次序得牢牢记住,一旦弄错顺序就无法正常调用;
3、对于请求报文与应答报文,HttpURLConnection只笼统提供了输出流和输入流,剩下的事全凭开发者自由发挥,害得开发者忙于I/O流与字符串/文件之间的转换工作;
4、服务器返回的应答报文,其数据有可能采用gzip压缩,还可能采取GBK字符编码,然而HttpURLConnection默认情况下却袖手旁观,必须由开发者对数据手工解压和重新编码;
总而言之,HttpURLConnection要求开发者掌握太多的技术细节,容易造成初学者对其望而却步。为此第三方的HTTP框架层出不穷,意图通过简单明了的方法调用来简化HTTP通信编程。Apache旗下的HttpClient便是其中一个佼佼者,它封装了大部分的编码细节,开发者只需书写寥寥数行代码,即可完成常见的HTTP访问操作。当然,Apache的HttpClient毕竟是个外来者,它运用得越广泛,Java的老板Oracle越是觉得不爽,老财主Oracle心想:咱卧榻之侧,岂容他人鼾睡?与其依赖Apache,不如自己动手丰衣足食,于是从Java11开始,JDK新增了自己的HttpClient框架,总算在自力更生的道路上迈开了小小的一步。
Java11的HttpClient体系由三部分组成,分别是表示HTTP客户端的HttpClient、表示HTTP请求过程的HttpRequest、表示HTTP应答过程的HttpResponse。其中HttpClient用于描述通用的客户端连接信息,包括HTTP协议的版本号、HTTP代理、重定向方式、连接超时时间、身份认证、SSL证书等等。下面是创建HTTP客户端对象的代码例子:

		// 创建一个自定义的HTTP客户端对象
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_1_1) // 遵循HTTP协议的1.1版本
.followRedirects(Redirect.NORMAL) // 正常的重定向
.connectTimeout(Duration.ofMillis(5000)) // 连接的超时时间为5秒
.authenticator(Authenticator.getDefault()) // 默认的身份认证
.build();

显然以上的代码例子很啰嗦,对于普通的HTTP连接,一律按照默认的参数就行。于是HTTP客户端对象的创建代码可缩短到如下一行:

		// 创建默认的HTTP客户端对象
HttpClient client = HttpClient.newHttpClient();

至于HttpRequest,则用于描述本次网络访问的请求信息,包括对方地址、接口的调用方式(GET还是POST)、请求的超时时间、请求的头部属性等等。下面是创建HTTP请求对象的代码例子:

		// 创建一个自定义的HTTP请求对象
HttpRequest request = HttpRequest.newBuilder()
.GET() // 调用方式为GET
.uri(URI.create(url)) // 待调用的url地址
.header("Accept-Language", "zh-CN") // 设置头部参数,中文文本
.timeout(Duration.ofMillis(5000)) // 请求的超时时间为5秒
.build();

对于一般的GET调用而言,HTTP请求可以使用默认的参数,再把对方地址作为newBuilder方法的输入参数,如此一来HTTP请求对象的创建代码也可缩短到如下一行:

		// 创建默认的HTTP请求对象(默认GET调用)
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();

接着调用HTTP客户端对象的send方法,第一个参数填HTTP请求对象,第二个参数填BodyHandlers.ofString()表示要求返回字符串形式的应答报文,而send方法的返回值便是HttpResponse对象。HttpResponse主要提供了下列三个方法,以便开发者处理应答数据:

statusCode:获取应答的状态码。
body:获取应答报文的内容。
headers:获取应答的所有头部属性。
接下来结合HttpClient、HttpRequest、HttpResponse,很容易写出GET方式的HTTP调用代码,具体代码如下所示:

	// 对指定url发起GET调用
private static void testCallGet(String url) {
// 创建默认的HTTP客户端对象
HttpClient client = HttpClient.newHttpClient();
// 创建默认的HTTP请求对象(默认GET调用)
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
try {
// 客户端传递请求信息,且返回字符串形式的应答报文
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// 获取应答的所有头部属性
HttpHeaders headers = response.headers();
// 打印HTTP调用的应答内容长度、内容类型、压缩方式
System.out.println( String.format("应答内容长度=%s, 内容类型=%s, 压缩方式=%s",
headers.firstValue("Content-Length").orElse(null),
headers.firstValue("Content-Type").orElse(null),
headers.firstValue("Content-Encoding").orElse(null)) );
// 打印HTTP调用的应答状态码和应答报文
System.out.println( String.format("应答状态码=%d, 应答报文=%s",
response.statusCode(), response.body()) );
} catch (Exception e) {
e.printStackTrace();
}
}

然后在外部调用上面的testCallGet方法,以股指查询的接口地址为例,查询上证指数的调用代码如下:

		testCallGet("https://hq.sinajs.cn/list=s_sh000001");

运行以上的股指查询代码,观察到以下的查询日志,可见HttpClient已经自动完成了中文字符的GBK编码。

应答内容长度=75, 内容类型=application/javascript; charset=GBK, 压缩方式=null
应答状态码=200, 应答报文=var hq_str_s_sh000001="上证指数,3244.8103,-1.7611,-0.05,5045184,50643124";

利用HttpClient发起POST方式的调用过程类似GET方式,唯一的区别在于:创建HTTP请求对象之时要调用POST方法并传入请求报文。下面是采取POST方式访问服务地址的HttpClient代码例子:

	// 对指定url发起POST调用
private static void testCallPost(String url, String body) {
System.out.println("请求报文="+body);
// 创建默认的HTTP客户端对象
HttpClient client = HttpClient.newHttpClient();
// 创建一个自定义的HTTP请求对象
HttpRequest request = HttpRequest.newBuilder(URI.create(url)) // 待调用的url地址
.POST(BodyPublishers.ofString(body)) // 调用方式为POST,且请求报文为字符串
.header("Content-Type", "application/json") // 设置头部参数,内容类型为json
.build();
try {
// 客户端传递请求信息,且返回字符串形式的应答报文
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// 打印HTTP调用的应答状态码和应答报文
System.out.println( String.format("应答状态码=%d, 应答报文=%s",
response.statusCode(), response.body()) );
} catch (Exception e) {
e.printStackTrace();
}
}

接着由外部调用上面的testCallPost方法,这里访问的是本机的HTTP服务,交互报文为json格式,具体代码如下所示:

		testCallPost("http://localhost:8080/NetServer/checkUpdate", "{\"package_list\":[{\"package_name\":\"com.qiyi.video\"}]}");

运行以上的服务访问代码,观察到以下的接口日志,可见HttpClient正确完成了POST方式的接口调用。

请求报文={"package_list":[{"package_name":"com.qiyi.video"}]}
应答状态码=200, 应答报文={"package_list":[{"package_name":"com.qiyi.video","download_url":"https://3g.lenovomm.com/w3g/yydownload/com.qiyi.video/60020","new_version":"10.2.0"}]}

  

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(一百一十二)Java11新增的HttpClient的更多相关文章

  1. Java开发笔记(三十二)字符型与整型相互转化

    前面提到字符类型是一种新的变量类型,然而编码实践的过程中却发现,某个具体的字符值居然可以赋值给整型变量!就像下面的例子代码那样,把字符值赋给整型变量,编译器不但没报错,而且还能正常运行! // 字符允 ...

  2. Java开发笔记(八十二)注解的基本单元——元注解

    Java的注解非但是一种标记,还是一种特殊的类型,并且拥有专门的类型定义.前面介绍的五种内置注解,都可以找到对应的类型定义代码,例如查看注解@Override的源码,发现它的代码定义是下面这样的: @ ...

  3. Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  4. Java开发笔记(五十二)对象的类型检查

    前面介绍了类的多态性,来自于鸡类的实例chicken,既能用来表达公鸡实例,也能用来表达母鸡实例.可是这导致了一个问题,假如在call方法内部需要手工判断输入参数属于公鸡实例还是母鸡实例,那该如何是好 ...

  5. Java开发笔记(六十二)如何定义函数式接口

    前面介绍了Lambda表达式的用法,从实践中发现它确实极大地方便了开发者,然而不管是匿名内部类还是Lambda表达式,所举的例子都离不开各类数组的排序方法,倘使Lambda表达式仅能用于sort方法, ...

  6. Java开发笔记(七十二)Java8新增的流式处理

    通过前面几篇文章的学习,大家应能掌握几种容器类型的常见用法,对于简单的增删改和遍历操作,各容器实例都提供了相应的处理方法,对于实际开发中频繁使用的清单List,还能利用Arrays工具的asList方 ...

  7. Java开发笔记(七十)Java8新增的几种泛型接口

    由于泛型存在某种不确定的类型,因此很少直接运用于拿来即用的泛型类,它更经常以泛型接口的面目出现.例如几种基本的容器类型Set.Map.List都被定义为接口interface,像HashSet.Tre ...

  8. Java开发笔记(三十八)利用正则表达式校验字符串

    前面多次提到了正则串.正则表达式,那么正则表达式究竟是符合什么定义的字符串呢?正则表达式是编程语言处理字符串格式的一种逻辑式子,它利用若干保留字符定义了形形色色的匹配规则,从而通过一个式子来覆盖满足了 ...

  9. Java开发笔记(三十九)日期工具Date

    Date是Java最早的日期工具,编程中经常通过它来获取系统的当前时间.当然使用Date也很简单,只要一个new关键字就能创建日期实例,就像以下代码示范的那样: // 创建一个新的日期实例,默认保存的 ...

  10. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

随机推荐

  1. 【Quartz】将定时任务持久化到数据库

    之前的文章所做的demo是将定时任务的信息保存在内存中的,见以下配置 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore 如果,我们需要在 ...

  2. pdo in 查询

    $ids1 = implode(",",$upload_ids);if(!empty($upload_ids)){ $ids_db= pdo_fetchall('select id ...

  3. Nmap / NetCat(nc) / 网络安全工具

    nmap - 网络探测工具和安全/端口扫描器 nmap [ <扫描类型> ...] [ <选项> ] { <扫描目标说明> } 描述 Nmap ("Net ...

  4. jQuery功能一览

    // Hello world!"); }); $("#btn5").click(function(){ $("#test5").val("D ...

  5. IE8“开发人员工具”使用详解上(各级菜单详解)

    来源: http://www.cnblogs.com/JustinYoung/archive/2009/03/24/kaifarenyuangongju.html IE8“开发人员工具”使用详解上(各 ...

  6. [python]解决Windows下安装第三方插件报错:UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xcb in position 0:

    系统:win7IDE:pycharm Python版本:2.7 安装第三方插件是报错:  报错原因与编码有关,pip把下载的临时文件存放在了用户临时文件中,这个目录一般是C:\Users\用户名\Ap ...

  7. python学习day6 数据类型Ⅳ(集合)

    day6 数据类型-集合 内容补充: 列表功能: .reverse()反转 v = [1,2,3,4,5,6]v.reverse()print() #[6, 5, 4, 3, 2, 1] .sort( ...

  8. Python基础之初识类和对象

    我们在前面学习了解了面向过程编程,接下来我们一起来学习一下面向对象编程.其实不管是面向过程,还是面向对 象,说白了就是一种编程方式而已.既然是面向对象编程,顾名思义,此编程方式的落地需要使用 “类” ...

  9. 在docker中使用mysql数据库,在局域网访问

    1.获取mysql镜像 docker pull mysql:5.6 注意:此处之所以获取mysql5.6是因为mysql5.7在centos7中启动可能会报错 2.查看镜像列表 docker imag ...

  10. Beta阶段第二次冲刺

    Beta阶段第二次冲刺 严格按照Git标准来,组员有上传Git的才有贡献分没有的为0 代码签入图 1.part1 -站立式会议照片 2.part2 -项目燃尽图 3.part3 -项目进展 1.正在进 ...