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开发笔记(序)章节目录》
上一篇: mysql分组函数及其用例