前面介绍了基于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. LOJ P10018 数的划分 题解

    每日一题 day52 打卡 Analysis 这道题直接搜索会TLE到**,但我们发现有很多没有用的状态可以删去,比如 1,1,5; 1,5,1; 5,1,1; 所以很容易想到一个优化:按不下降的顺序 ...

  2. THUPC&CTS 2019 游记

    day ? 去THU报了个到. day? THUPC比赛日,三个人都没有智商,各种签到题不会做,被各路神仙吊着打.G题还猜了个假结论,做了好久都不对.最后顺利打铁了. 还顺便去看一下THUAC. da ...

  3. 开源一个golang小程序商城后台系统(moshopserver)

    开源一个golang小程序商城后台(moshopserver) golang和c/c++比起来是一门新的语言,一直想学,网上搜集了一些资料,有些人说很容易上手,确实是这样,和C/C++比起来,少了很多 ...

  4. mvn pom文件引用顺序关系

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  5. hadoop综合

    对CSV文件进行预处理生成无标题文本文件,将爬虫大作业产生的csv文件上传到HDFS 首先,我们需要在本地中创建一个/usr/local/bigdatacase/dataset文件夹,具体的步骤为: ...

  6. Understanding FiddlerScript

    Understanding FiddlerScript FiddlerScript is one of the most powerful features in Fiddler; it allows ...

  7. 自己搭建gitlab服务,组员不能上传代码

    原因是因为  没有拉分支  直接在master 上开撸代码 ,master 分支 默认是受保护的,具体操作如下

  8. linux shell中如何批量添加一行内容到某些文件的末尾?

    答:先使用find找出要指定的某些文件,然后使用xargs和sed工具将内容插入到这些文件的末尾 find . -name 'filename*' | xargs sed -i '$a\added-c ...

  9. Python实现PIL将图片转成字符串

    # -*- coding: utf-8 -*- # author:baoshan from PIL import Image, ImageFilter codeLib = '''@#$%&?* ...

  10. ISO/IEC 9899:2011 条款6.10——预处理指示符

    6.10 预处理指示符 语法 1.preprocessing-file: groupopt group: group-part group    group-part group-part: if-s ...