Java开发笔记(一百一十一)POST方式的HTTP调用
前面介绍了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调用的更多相关文章
- Java开发笔记(三十一)字符类型的表达
前面介绍的Java编程,要么是与数字有关的计算,要么是与逻辑有关的推理,充其量只能实现计算器和状态机.若想让Java运用于更广阔的业务领域,就得使其支撑更加血肉丰满的业务场景,而丰满的前提是能够表达大 ...
- Java开发笔记(六十一)Lambda表达式
前面介绍了匿名内部类的简单用法,通过在sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务代码的连续性.只是匿名内部类的结构仍显啰嗦,虽然它省去了内部类的名称,但是花括号里面的方法定义代码 ...
- Java开发笔记(七十一)容器工具Collections
清单作为一组数据的有序队列,它在组织形式上与数组有着某些异曲同工之处,数组有专门的数组工具Arrays来进行加工操作,照理清单也应该配备对应的清单工具.当然容器这个大家族确实拥有自己的容器工具Coll ...
- Java开发笔记(九十一)IO流处理简单的数据压缩
前面介绍的文件I/O,不管是写入文本还是写入对象,文件中的数据基本是原来的模样,用记事本之类的文本编辑软件都能浏览个大概.这么存储数据,要说方便确实方便,只是不够经济划算,原因有二:其一,写入的数据可 ...
- Java开发笔记(八十一)如何使用系统自带的注解
之前介绍继承的时候,提到对于子类而言,父类的普通方法可以重写也可以不重写,但是父类的抽象方法是必须重写的,如果不重写,编译器就直接在子类名称那里显示红叉报错.例如,以前演示抽象类用法之时,曾经把Chi ...
- Java开发笔记(四十一)日历工具Calendar
前面的文章提到,Date是Java最早的日期工具,估计当时的设计师是个技术宅男,未经过充分调研就拍脑袋写下了Date的源码,造成该工具存在先天不足,比如getYear方法返回的不是纯正的公元纪年.ge ...
- Java开发笔记(五十一)多态的发生场景
江湖上传闻,面向对象之所以厉害,是因为它拥有封装.继承与多态三项神技,只要三板斧一出,号令天下谁敢不从.前面费了老大的劲才讲清楚封装和继承,那么多态又是怎样的神乎其神呢?下面先通过一个简单的例子来说明 ...
- Java开发笔记(二十一)二维数组的扩展
前面介绍的数组容纳的是一串数字,仿佛一根线把这组数字串了起来,故而它只是一维数组.一维数组用来表示简单的数列尚可,要是表达复杂的平面坐标系,那就力不从心了.由于平面坐标系存在水平和垂直两个方向,因此可 ...
- Java开发笔记(序)章节目录
现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...
随机推荐
- windbg自行下载的sos.dll存放路径“..\SOS_x86_x86_4.7.3132.00.dll\5B5543296ee000\”里的“5B5543296ee000”是什么?
问题的引出 我在调试某个崩溃问题时,要跟踪clr的栈,于是,我先执行了指令.loadby sos clrjit,没有报错,然后我又执行!clrstack,结果却有如下输出:0:000:x86> ...
- 验证和交叉验证(Validation & Cross Validation)
之前在<训练集,验证集,测试集(以及为什么要使用验证集?)(Training Set, Validation Set, Test Set)>一文中已经提过对模型进行验证(评估)的几种方式. ...
- [BZOJ1191]超级英雄Hero
Description 现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的 多少获得不同数目的奖品或奖金.主持人问题准备了若干道题目,只有当选手正确 ...
- 2、kafka集群搭建
以三台为例,先安装一台,然后分发: 一.准备 1.下载 http://kafka.apache.org kafka_2.11-2.0.1.tgz 前面的数字2.11是scala的版本,2.0.1是ka ...
- Day17:web前端开发面试题
1.JavaScript 数据类型有哪些? JavaScript 变量能够保存多种数据类型:数值.字符串值.数组.对象等等: var length = 7; // 数字 var lastName = ...
- Eclipse Maven问题小记
Eclipse Maven Web工程报错:java.lang.ClassNotFoundException: ContextLoaderListener 原因:打包项目时没有把相关Maven依赖包打 ...
- inotify 监控文件系统操作
path0=path1=########################################################dir2watch1=/home/nanjing2/GridON ...
- 去掉 vue 的 "You are running Vue in development mode" 提示
去掉 vue 的 "You are running Vue in development mode" 提示 在项目的 main.js 中已经配置了 Vue.config.produ ...
- python opencv PyQt5
import cv2 import numpy as np import sys from PyQt5.QtGui import * from PyQt5.QtCore import * from P ...
- CORS-跨域问题:Access-Control-Allow-Origin Header and the ASP.NET Web API
代码控制跨域: 如何使用:在 Global.asax 对应的控制类中: protected void Application_BeginRequest() { if (CorsFilter.IsOpt ...