前面介绍了基于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. L1731

    生日蛋糕 输入的东西,一个是蛋糕的体积,一个是蛋糕的层数, 简言之,我觉得这个就是两个dfs的状态. 一旦越过这两个就得return ,同时这两个东西也参与进去了dfs. 至于题目, 第一个要求是层数 ...

  2. lombok常用注解@Data@AllArgsConstructor@NoArgsConstructor@Builder@Accessors

    原贴:https://blog.csdn.net/ChenXvYuan_001/article/details/84961992 https://blog.csdn.net/weixin_382293 ...

  3. How to change hostname on debian

    How to change hostname on Debian 10 Linux last updated July 13, 2019 in CategoriesDebian / Ubuntu, L ...

  4. 原生js打地鼠

    我们要做的是一个打地鼠的游戏,只用原生js 1.导入需要的图片 2.编写页面css样式demo.css *{ margin:0; padding:0; } .game{ position: relat ...

  5. Mysql 多表连接查询 inner join 和 outer join 的使用

    JOIN的含义就如英文单词“join”一样,连接两张表,大致分为内连接,外连接,右连接,左连接,自然连接.这里描述先甩出一张用烂了的图,然后插入测试数据. 首先先列举本篇用到的分类(内连接,外连接,交 ...

  6. ThinkPad T410i 2516A21 升級手札(換SSD固態硬碟、I7 CPU、開機20秒)

    最近筆記本越來越慢,開機得20分鐘,而且CPU動不動就飆到80度,趁著開學網上活動,準備給老伙計來一次重大升級.查一下主板芯片,最高支持8G內存,已經滿了,光驅位加了一個1T機械硬盤,那麼能升級的就只 ...

  7. nodejs之express生成项目[windows平台]

    安装nvm,nvm下载地址   用于管理多个版本node,此处可省略! 安装nodejs,nodejs下载地址    淘宝镜像 安装cnpm命令,后面包可以使用cnpm命令安装,此处可省略,如果安装了 ...

  8. Go内置函数

    append go语言中的append函数作用是在切片变量的后面追加新的数据,然后返回新的切片变量 func append(slice []Type, elems ...Type) []type sl ...

  9. Java 12 骚操作, 文件比对居然还能这样玩!

    Java 13 都快要来了,12必须跟栈长学起! Java 13 即将发布,新特性必须抢先看! 之前分享了一些 Java 12 的骚操作,今天继续,今天要分享的是 Java 12 中的文件比对骚操作. ...

  10. python复合数据类型以及英文词频统计

    这个作业的要求来自于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/2753. 1.列表,元组,字典,集合分别如何增删改查及遍历. 列 ...