前面介绍了GET方式的HTTP调用,该方式主要用于向服务器索取数据,不管是字符串形式的应答报文,还是二进制形式的网络文件,都属于服务器提供的信息。当然调用方也可以向服务地址传送请求参数,除了通过连接对象设置的HTTP参数,还能给url地址添加形如“?参数A名称=A参数值&参数B名称=B参数值”这样的业务参数,服务地址根据url后面的业务参数,再返回符合条件的应答数据。倘若服务器不仅仅作为信息提供方,还想成为信息接收方,例如保存调用方提交的表单数据,或者保存调用方待上传的文件,那便要求调用方的程序能够传送复杂的数据信息。通过GET方式固然也能在url后方填写简单的请求参数,但是这并非信息传送的可靠手段,原因有三:
1、往URL末尾添加的请求参数,全为明文传输,不利于数据的保密措施;
2、URL格式的请求串只支持键值对形式的参数,难以表达复杂的结构化数据,譬如数组形式的参数;
3、URL本身是个字符串,Query部分的请求参数也只能是字符串,这叫二进制形式的文件上传如何是好?
鉴于种种不可避免的困难,GET方式实在不适合向服务器提交数据,必须采用POST方式提交数据才行。POST方式同样需要服务器给个调用地址,但该方式的业务参数没放到URL末尾,而是放在了请求报文当中。所谓的请求报文与应答报文相对应,应答报文要从连接对象的输入流中获取,而请求报文要写入连接对象的输出流。编码实现POST请求的时候,除了调用setRequestMethod要将请求方式设置为POST,还需留意连接对象的下列几种方法:
setRequestProperty:设置请求属性。该方法可设置特定名称的属性值。
setDoOutput:准备让连接执行输出操作。默认为false(GET方式),POST方式需要设置为true。
setDoInput:准备让连接执行输入操作。默认为true,通常无需特意调用该方法。
getOutputStream:从连接对象中获取输出流,后续会把请求报文写入输出流。
getHeaderField:获取应答报文头部指定名称的字段值。该方法可得到特定名称的参数值,例如getHeaderField("Content-Length")返回的是应答报文的长度,getHeaderField("Content-Type")返回的是应答报文的内容类型,conn.getHeaderField("Content-Encoding")返回的是应答报文的压缩方式。
上述几种方法中尤为值得注意的是setRequestProperty,依据不同的请求属性名称,该方法将会设置各式各样的属性值,以此提醒服务器做好相应的准备工作。其中常见的属性名称及其属性值罗列如下:
Content-Type:请求报文的内容类型。如果请求报文采取形如“参数A名称=A参数值&参数B名称=B参数值”的url参数格式,则内容类型应设置为“application/x-www-form-urlencoded”;如果请求报文是json格式,则内容类型应设置为“application/json”;如果请求报文是xml格式,则内容类型应设置为“application/xml”;如果请求报文是分段传输的文件数据,则内容类型应设置为“multipart/form-data;boundary=***”。
Connection:指定连接的保持方式。如果是文件上传,则必须设置为“Keep-Alive”,表示建议服务器保留连接,以便能够持续发送文件的分段数据。
User-Agent:指定调用方的浏览器类型。
Accept:指定可接受的应答报文类型。如果不设置则默认“*/*”,表示允许返回任何类型的应答报文;如果设置为“image/png”,则表示只接受返回png图片。
Accept-Language:指定可接受的应答报文语言。通常无需设置,如果只接受中文则可设置为“zh-cn”。
Accept-Encoding:指定可接受的应答报文压缩方式。如果不设置则默认identity,表示不允许应答报文使用压缩;如果设置为gzip,则表示允许应答报文采用GZIP压缩,此时服务器可能返回gzip压缩的应答数据,也可能返回未压缩的应答数据。
接下来举个请求报文是json串的HTTP接口例子,采用POST方式的调用方法代码如下所示:

	// 对指定url发起POST调用
private static void testCallPost(String callUrl, String body) {
try {
URL url = new URL(callUrl); // 根据网址字符串构建URL对象
// 打开URL对象的网络连接,并返回HttpURLConnection连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST"); // 设置请求方式为POST调用
conn.setRequestProperty("Content-Type", "application/json"); // 请求报文为json格式
conn.setDoOutput(true); // 准备让连接执行输出操作。默认为false,POST方式需要设置为true
conn.connect(); // 开始连接
OutputStream os = conn.getOutputStream(); // 从连接对象中获取输出流
os.write(body.getBytes()); // 往输出流写入请求报文
// 打印HTTP调用的应答内容长度、内容类型、压缩方式
System.out.println( String.format("应答内容长度=%s, 内容类型=%s, 压缩方式=%s",
conn.getHeaderField("Content-Length"), conn.getHeaderField("Content-Type"),
conn.getHeaderField("Content-Encoding")) );
// 对输入流中的数据进行解压和字符编码,得到原始的应答字符串
String content = StreamUtil.getUnzipString(conn);
// 打印HTTP调用的应答状态码和应答报文
System.out.println( String.format("应答状态码=%d, 应答报文=%s",
conn.getResponseCode(), content) );
conn.disconnect(); // 断开连接
} catch (Exception e) {
e.printStackTrace();
}
}

然后由外部在调用testCallPost时输入服务地址和请求报文,具体代码示例如下:

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

运行上述的POST代码,从以下的接口日志可知POST方式正确发送了请求报文,且正常收到了应答报文。

请求报文={"package_list":[{"package_name":"com.qiyi.video"}]}
应答内容长度=152, 内容类型=text/plain;charset=utf-8, 压缩方式=null
应答状态码=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"}]}

通过HTTP接口上传文件也要采用POST方式,只是文件上传还需遵守一定的数据规则,除了内容类型设置为“multipart/form-data;boundary=***”(***处要替换成边界字符串),请求报文也得依顺序填入报文头、报文体和报文尾,详细的上传过程代码如下所示:

	// 把本地文件上传给指定url
private static void testUpload(String filePath, String uploadUrl) {
// 从本地文件路径获取文件名
String fileName = filePath.substring(filePath.lastIndexOf("/"));
String end = "\r\n"; // 结束字符串
String hyphens = "--"; // 连接字符串
String boundary = "WUm4580jbtwfJhNp7zi1djFEO3wNNm"; // 边界字符串
try (FileInputStream fis = new FileInputStream(filePath)) {
URL url = new URL(uploadUrl); // 根据网址字符串构建URL对象
// 打开URL对象的网络连接,并返回HttpURLConnection连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true); // 准备让连接执行输出操作。默认为false,POST方式都要设置为true
conn.setRequestMethod("POST"); // 设置请求方式为POST调用
// 连接过程要保持活跃
conn.setRequestProperty("Connection", "Keep-Alive");
// 请求报文要求分段传输,并且各段之间以边界字符串隔开
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
// 根据连接对象的输出流构建数据输出流
DataOutputStream ds = new DataOutputStream(conn.getOutputStream());
// 以下写入请求报文的头部
ds.writeBytes(hyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; "
+ "name=\"file\";filename=\"" + fileName + "\"" + end);
ds.writeBytes(end);
// 以下写入请求报文的主体
byte[] buffer = new byte[1024];
int length;
// 先将文件数据写入到缓冲区,再将缓冲数据写入输出流
while ((length = fis.read(buffer)) != -1) {
ds.write(buffer, 0, length);
}
ds.writeBytes(end);
// 以下写入请求报文的尾部
ds.writeBytes(hyphens + boundary + hyphens + end);
ds.close(); // 关闭数据输出流
// 对输入流中的数据进行解压和字符编码,得到原始的应答字符串
String content = StreamUtil.getUnzipString(conn);
// 打印HTTP上传的应答状态码和应答报文
System.out.println( String.format("应答状态码=%d, 应答报文=%s",
conn.getResponseCode(), content) );
conn.disconnect(); // 断开连接
} catch (Exception e) {
e.printStackTrace();
}
}

然后由外部在调用testUpload方法时输入上传地址和待上传的文件路径,具体代码示例如下:

		testUpload("E:/bliss.jpg", "http://localhost/NetServer/uploadServlet");

运行上述的上传代码,从以下的上传日志可知文件已经成功上传至服务器。

应答状态码=200, 应答报文=文件上传成功,文件大小为1912K

  

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

Java开发笔记(一百一十一)POST方式的HTTP调用的更多相关文章

  1. Java开发笔记(三十一)字符类型的表达

    前面介绍的Java编程,要么是与数字有关的计算,要么是与逻辑有关的推理,充其量只能实现计算器和状态机.若想让Java运用于更广阔的业务领域,就得使其支撑更加血肉丰满的业务场景,而丰满的前提是能够表达大 ...

  2. Java开发笔记(六十一)Lambda表达式

    前面介绍了匿名内部类的简单用法,通过在sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务代码的连续性.只是匿名内部类的结构仍显啰嗦,虽然它省去了内部类的名称,但是花括号里面的方法定义代码 ...

  3. Java开发笔记(七十一)容器工具Collections

    清单作为一组数据的有序队列,它在组织形式上与数组有着某些异曲同工之处,数组有专门的数组工具Arrays来进行加工操作,照理清单也应该配备对应的清单工具.当然容器这个大家族确实拥有自己的容器工具Coll ...

  4. Java开发笔记(九十一)IO流处理简单的数据压缩

    前面介绍的文件I/O,不管是写入文本还是写入对象,文件中的数据基本是原来的模样,用记事本之类的文本编辑软件都能浏览个大概.这么存储数据,要说方便确实方便,只是不够经济划算,原因有二:其一,写入的数据可 ...

  5. Java开发笔记(八十一)如何使用系统自带的注解

    之前介绍继承的时候,提到对于子类而言,父类的普通方法可以重写也可以不重写,但是父类的抽象方法是必须重写的,如果不重写,编译器就直接在子类名称那里显示红叉报错.例如,以前演示抽象类用法之时,曾经把Chi ...

  6. Java开发笔记(四十一)日历工具Calendar

    前面的文章提到,Date是Java最早的日期工具,估计当时的设计师是个技术宅男,未经过充分调研就拍脑袋写下了Date的源码,造成该工具存在先天不足,比如getYear方法返回的不是纯正的公元纪年.ge ...

  7. Java开发笔记(五十一)多态的发生场景

    江湖上传闻,面向对象之所以厉害,是因为它拥有封装.继承与多态三项神技,只要三板斧一出,号令天下谁敢不从.前面费了老大的劲才讲清楚封装和继承,那么多态又是怎样的神乎其神呢?下面先通过一个简单的例子来说明 ...

  8. Java开发笔记(二十一)二维数组的扩展

    前面介绍的数组容纳的是一串数字,仿佛一根线把这组数字串了起来,故而它只是一维数组.一维数组用来表示简单的数列尚可,要是表达复杂的平面坐标系,那就力不从心了.由于平面坐标系存在水平和垂直两个方向,因此可 ...

  9. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

随机推荐

  1. 将windbg与.dmp文件关联

    如果您厌倦了启动调试器.加载转储文件.设置sympath.加载扩展名等,这里有一个很好的方法,可以在.dmp文件的上下文菜单上获取“调试此转储文件”,并自动加载所有您喜欢的命令.首先创建一个包含以下内 ...

  2. P2388 阶乘之乘

    首先感谢wxy学长之前告诉我这道题,结果今天竟然一眼切了,咕咕咕 题目链接: P2388 阶乘之乘 题目思路: 第一眼看到一定想到的是先求一下阶乘然后看最后又几个零,但是这样会TIL啊 想一下0是怎么 ...

  3. SQL基础-约束&索引

    一.约束 1.约束简介 约束(constraint): 表中数据的限制条件. 完整性 有效性 约束的种类: 主键约束 外键约束 唯一约束 检查约束 非空约束 2.主键约束 主键约束: 唯一标识表中每一 ...

  4. c博客作业01——顺序 分支结构

    本章学习总结 1.1 学习内容总结 ·学习switch分支的使用,switch后加括号(),括号内填一个变量或字符 如 switch (a) { case 2: case 3: default: } ...

  5. 廖雪峰Python笔记

    △命令行模式和Python交互模式 在Windows开始菜单选择“命令提示符”,就进入到命令行模式,它的提示符类似C:\>:在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就 ...

  6. win10 无法使用内置管理员账户打开应用

    运行gpedit.msc 启用两项 如何登陆 win 10 账户? 进入 win 10 应用商店,下载一个软件,然后登陆即可

  7. vue Uncaught SyntaxError: Unexpected token < 报错

    这个问题是因为项目中出现没有闭合的标签,如<img src="">  需改成<img src="xxx.png"/>

  8. React_03_ECMAScript6

    1.ES6解构赋值 1.1.解构赋值概述 解构赋值是对赋值运算符的扩展. 它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值.在代码书写上简洁且易读,语义更加清晰明了:也方便了复杂对象中数 ...

  9. Markdown文字添加颜色

    转自:原文地址 添加红色 效果: 写法: $\color{red}{red}$ 添加绿色 效果: 写法: $\color{green}{green}$ 添加蓝色 效果: 写法: $\color{blu ...

  10. 【2019.11.27】SDN课程阅读作业(2)

    过去20年中可编程网络的发展可以分为几个阶段?每个阶段的贡献是什么? Making computer networks more programmable enables innovation in ...