前面介绍了 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 开发笔记 (序) 章节目录 https://www.cnblogs.com/pinlantu/p/9941789.html 》
Java 开发笔记(一百一十一)POST 方式的 HTTP 调用
来源: http://www.bubuko.com/infodetail-3093333.html