Java以form-data(表单)的形式调用第三方接口

news2024/11/27 14:37:31

Java以form-data(表单)的形式调用第三方接口

  • 前言
  • 本文目标
  • 用到的类
  • 工具类及测试信息
    • 工具类代码
    • 测试信息
      • 测试代码
      • 测试结果
  • 遇到的问题
    • getContentLength()的滥用
      • 调用的错误
    • 慎用请求输出流flush()方法
    • 未写入标识
      • 调用错误
  • 总结

前言

之前写的调用第三方接口: Java使用原生API调用第三方接口

但是其中只包含了简单的接口(传递数据为JSON)调用。也就是Content-Type的值是设置成:

 httpCon.setRequestProperty("Content-Type", 
 "application/json;charset=utf-8");

当第三方接口需要包含文件类型的参数,我们要设置成以表单形式提交,就要那么该属性就应该设置成

httpCon.setRequestProperty("Content-Type",
 "multipart/form-data; boundary=" + boundary);

表示是以表单形式提交数据。请记住这里的boundary,这是自己的设置的上传的信息的边界标识,稍后我会讲到。

本文目标

所以本文只针对以下类似的情况,这里我以PostMan的调用方式为示例。

  1. 如果在PostMan里面能调用成功,那么后续的内容是对你有用的。

  2. 如果这里你的接口的调用不通,则代表你可能需要使用其他的方式。不过阅读本文可能会对你有所帮助。

在这里插入图片描述
idStringnameString
uploadFiles为文件集合,可以多选。
其中的uploadFiles需要自己选择下,默认是Text文本格式,不然无法选择文件。
在这里插入图片描述
多选文件的话是自己在选择文件的时候,按住ctrl多选。。。真滴奇怪。。。

上述的参数,在请求中的存放形式为:

----------------------------boundary
Content-Disposition: form-data; name="uploadFiles"; filename="1.txt"
<1.txt>
----------------------------boundary
Content-Disposition: form-data; name="uploadFiles"; filename="2.txt"
<2.txt>
----------------------------boundary
Content-Disposition: form-data; name="id"
1008611
----------------------------boundary
Content-Disposition: form-data; name="name"
张三
----------------------------boundary--
 

记住以上的形式,这很重要,因为到时候我们需要按照上述形式拼接数据

boundary用于标识上传的数据,其中它的值,在设置里由自己设置,上面说过。
我这里是以
----------------------------boundary开头
----------------------------boundary--结尾
上述表示的是,我上传了两个文件:1.txt、2.txt,它们的键是同一个uploadFiles,两个字符参数:id =1008611 ,name = 张三。

好了现在,我们就要用Java来实现上述的PostMan调用三方接口的方法。

用到的类

这里就不再赘述了,因为跟文头提到文章里用的是一样的。流程也差不多。

工具类及测试信息

工具类代码

package com.http;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;

/**
 * @author 三文鱼先生
 * @title
 * @description
 * @date 2022/11/28
 **/
public class FormDataInterFaceUtils {
    //前缀
    public static String PREFIX = "--";
    //换行符
    public static String ROW = "\r\n";
    //产生一个边界
    static String BOUNDARY = UUID.randomUUID().toString().replaceAll("-" , "");

    /**
     * @description 将map里的表单信息 写入到所给的url请求中 并返回执行完请求的结果
     * @author 三文鱼先生
     * @date 9:38 2022/11/28 
     * @param url 所给的请求地址
     * @param map 参数的键值对映射 使用泛型 文件和字符参数都以对象表示
     * @return java.lang.String
     **/
    public static String doPost(String url , Map<String , Object> map) {
        //构造连接
        try {
            HttpURLConnection httpCon = getPostConnection(url);
            DataOutputStream outputStream = new DataOutputStream(httpCon.getOutputStream());
            //部分的三方请求可能需要携带类似于token这样的信息到请求头里才可以正常访问
            //可以使用setRequestProperty(键,值)来设置
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                Object o = entry.getValue();
                if(o instanceof String) {
                    //强转
                    String str = (String) o;
                    //添加键值对
                    addKeyString(outputStream , entry.getKey() , str);
                    int i = httpCon.getContentLength();
                }else {
                    //否则就是文件流
                    File file = (File) o;
                    //添加文件
                    addFile(outputStream , entry.getKey() , file);
                }
            }
            //写入边界结束符
            outputStream.write((PREFIX + BOUNDARY + PREFIX + ROW).getBytes(StandardCharsets.UTF_8));
            outputStream.flush();//可以理解为发送请求
            //获取返回结果 -- 默认为字符串
            return getInvokeResult(httpCon);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @description 写入键值对  示例为写入:name-张三
     * @author 三文鱼先生
     * @date 9:40 2022/11/28
     * @param out 请求的输出流
     * @param key 字符的键
     * @param str 字符的值
     * @return void
     **/
    public static void addKeyString(DataOutputStream out,
                                    String key ,
                                    String str) {
        try{
            StringBuilder stringBuilder = new StringBuilder();
            //先写入数据的边界标识
            stringBuilder.append(PREFIX).append(BOUNDARY).append(ROW);
            stringBuilder.append("Content-Disposition: form-data; name=\"")
                    .append(key).append("\"").append(ROW);
            //数据类型及编码
            stringBuilder.append("Content-Type: text/plain; charset=UTF-8");
            //Todo 连续两个换行符 表示文字的键信息部分结束
            stringBuilder.append(ROW).append(ROW);
            //写入信息的值
            stringBuilder.append(str);
            //表示数据的结尾
            stringBuilder.append(ROW);
            //写入数据 键值对一起写入
            out.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * @description 向输出流中写入文件 示例为: a.txt - 对应的File对象
     * @author 三文鱼先生
     * @date 9:42 2022/11/28
     * @param out 请求的输出流
     * @param name 文件的键
     * @param file 具体文件
     * @return void
     **/
    public static void addFile(DataOutputStream out , String name ,
                                File file) throws IOException {
        if(!file.exists())
            System.out.println("文件不存在");
        StringBuilder stringBuilder = new StringBuilder();
        //标识这是一段边界内的数据
        stringBuilder.append(PREFIX).append(BOUNDARY).append(ROW);
        //拼接文件名称
        stringBuilder.append("Content-Disposition: form-data; name=\"");
        stringBuilder.append(name).append("\"; ")//文件的键
                .append("filename=\"")//文件名称
                .append(file.getName())
                .append("\"")
                .append(ROW)
                //设置内容类型为流及编码为UTF-8
                .append("Content-Type: application/octet-stream; charset=UTF-8");

        //Todo 这两个换行很重要 标识文件信息的结束 后面的信息为文件流
        stringBuilder.append(ROW).append(ROW);

        //写入文件的信息到输出流
        out.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        //这里开始写入文件流
        try(
                DataInputStream fileIn = new DataInputStream(new FileInputStream(file))
        ) {
            //一次读取1M
            byte[] bytes = new byte[1024*1024];
            int length = 0;
            while ((length = fileIn.read(bytes)) != -1) {
                out.write(bytes , 0 , length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //Todo 文件流写完之后 需要换行表示结束
        out.write(ROW.getBytes(StandardCharsets.UTF_8));
    }


    /**
     * @description 以所给的url获取一个Post类型的连接
     * @author 三文鱼先生
     * @date 9:43 2022/11/28
     * @param url 请求的地址
     * @return java.net.HttpURLConnection
     **/
    public static HttpURLConnection getPostConnection(String url) {
        HttpURLConnection httpCon = null;
        try {
            URL urlCon = new URL(url);
            //在这里获取的就是一个已经打开的连接了
            httpCon = (HttpURLConnection) urlCon.openConnection();
            //请求方式为Post
            httpCon.setRequestMethod("POST");
            //设置通用的请求属性
            httpCon.setRequestProperty("accept", "*/*");
            httpCon.setRequestProperty("connection", "Keep-Alive");
            //设置浏览器代理
            httpCon.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
            //这里要设置为表单类型
            httpCon.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
            //是否可读写
            httpCon.setDoOutput(true);
            httpCon.setDoInput(true);
            //禁用缓存
            httpCon.setUseCaches(false);
            //设置连接超时60s
            httpCon.setConnectTimeout(60000);
            //设置读取响应超时60s
            httpCon.setReadTimeout(60000);
        } catch (IOException e ) {
            e.printStackTrace();
        }
        return  httpCon;
    }

    /**
     * @description 从请求中获取请求的执行返回
     * @author 三文鱼先生
     * @date 9:44 2022/11/28 
     * @param httpCon 请求的连接
     * @return java.lang.String
     **/
    public static String getInvokeResult(HttpURLConnection httpCon) {
        try(
                BufferedReader reader = new BufferedReader(new InputStreamReader(httpCon.getInputStream()))
        ) {
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试信息

下面是测试信息

测试代码

public class StrTest {
    public static void main(String[] args) {
        String url = "http://localhost:800/testInterface";
        Map<String , Object> map = new HashMap<>();
        map.put("id" ,"1008611");
        map.put("name" ,"张三");
        File file = new File("D:\\file\\1.txt");
        map.put("uploadFiles" , file);
        File file1 = new File("D:\\file\\2.txt");
        map.put("uploadFiles" , file1);
        System.out.println(FormDataInterFaceUtils.doPost(url, map));

    }
}

测试结果

{"msg":"以表单类型调用成功","code":"10086"}

遇到的问题

在撰写工具类的时候遇到一些问题,简单整理如下:

getContentLength()的滥用

方法描述是这样的:Returns the value of the content-length header field.翻译过来就是:返回内容长度头字段的值

我以为是以下请求信息中的Content-Length
在这里插入图片描述

本来是想写入一个文件就调用该方法看看内容长度的,结果疯狂报错。。。后面经过一系列调试,才知道这方法那么麻烦。。。

当我们调用该方法的时候,它会以当前的数据发送请求,然后关闭输出流

就是假如你要调用的接口有三个参数,然后你每写入一个参数调用一次该方法,那么就相当于你每次都以一个参数调用该接口。但你并不能执行三次,因为第一次执行以后,输出流就已经关闭了,你也就无法再向其中写入信息了。

进入debug中可以看到当执行到HttpURLConnection.class里的writeRequests()方法中以下代码时候:

 synchronized(this.poster) {
                    this.poster.close();//这一行就是罪魁祸首
                    this.requests.set("Content-Length", String.valueOf(this.poster.size()));
                }

也就是这个方法会将当前请求输出流和输入流的信息获取,然后关闭输入输出流,以流里的数据执行请求,返回的是执行该请求后返回响应的长度

简单来说,就是这个方法会以当前的数据帮你把这个请求执行,然后返回响应的结果。执行成功就返回-1,否则就是执行错误的长度。跟你当前的没有内容长度没有半毛钱关系。。。

调用的错误

如果在数据未全部放进去的时候,执行该方法,则会导致调用失败,其返回为白页:
在这里插入图片描述

<html>
	<body>
		<h1>Whitelabel Error Page</h1>
		<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
		<div>There was an unexpected error (type=null, status=null).</div>
	</body>
</html>

慎用请求输出流flush()方法

在以往的使用习惯中,我们每次操作完某个流时,在最后都会加上这样的一段代码,用于将缓冲区中的数据推送出去。

	out.flush();

但是这一方法对于请求的输出流来说,却有别的意思,在请求里的输出流中这个方法表示执行请求

获取流、写入信息,并执行请求,可以理解为以下图解的过程:
在这里插入图片描述
其中3中的发送请求,是以请求的输出流fulsh()方法来实现的。

可以把请求看作一个缓冲区,我们写入的信息会先放到缓冲区中,等执行flush()方法后,才会将信息给到服务器

未写入标识

在键于值之间,必须写入两个换行符来表示键值对的界限。也就是工具类中的:

stringBuilder.append(ROW).append(ROW);

调用错误

如果没有写入或者漏写,则会导致以下报错:

java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:209)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:675)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1536)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)

总结

总体来说还是不难的,主要是格式找了好久。还有就是键值的分隔符那里浪费了比较久的时间。最重要的一点是,注释上写的东西与我理解的不一样。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习】推荐系统

推荐系统的工作原理 推荐模型如何进行推荐将取决于您拥有的数据类型。如果您只拥有过去发生的交互数据&#xff0c;您可能有兴趣使用协作过滤。如果您有描述用户及其与之交互过的物品的数据&#xff08;例如&#xff0c;用户的年龄、餐厅的菜系、电影的平均评价&#xff09;&a…

Windows线程 信号量 CreateSemaphore创建信号量、RelaseSemaphore设置信号量

信号量 相关问题 类似于事件&#xff08;作用类似&#xff09;&#xff0c;解决通知的相关问题。但提供一个计数器&#xff0c;可以设置次数。 信号量的使用 1.创建信号量 HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//参数作废&#xff0c;基本用N…

临近期末,这些题不来看看吗?(下)

目录 1、打印1~100之间所有3的倍数&#xff08;三种方法&#xff09; 2、写出3给整数从大到小输出 3、给定两个数&#xff0c;求这两个数的最大公约数 4、 递归实现n的k次方 5、写一个递归函数DigitSum(n),输入一个非负整数&#xff0c;返回组成它的数字之和 6、编写一个…

二叉树模板套题——相同的树的应用

文章目录力扣100. 相同的树递归展开图力扣572. 另一棵树的子树递归展开图力扣101. 对称二叉树递归展开图力扣100. 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xf…

四、nginx正向代理

一、正向代理 解释&#xff1a;正向代理指的是客户端通过访问目标服务端&#xff0c;再由目标服务端来转发流量访问互联网 结构图如下&#xff1a; 好处&#xff1a;这样做的好处是&#xff0c;当客户端通过服务端访问互联网某个网站时&#xff0c;该网站获得的IP地址是服务…

STEAM上的一款电路模拟神器 — CRUMB Circuit Simulator

摘要&#xff1a;这几天在逛steam商店时&#xff0c;发现了一款有意思的电路仿真软件CRUMB Circuit Simulator&#xff08;CRUMB电路模拟器&#xff09;&#xff0c;觉得挺有意思的&#xff0c;就下载了玩了一下。 这款模拟电路软件的东西不多&#xff0c;基础的元器件都有&…

关于赚钱这件事,必须做到「金钱场」、「认知场」和「人脉场」三场统一

每周末&#xff0c;我会将我付费星球内的精华文章&#xff0c;在每周六或周日以付费文章的方式在公众号分享给大家&#xff0c;如果你不想加入我的星球&#xff0c;还想看的话&#xff0c;可以在这里付费看。当然&#xff0c;加入星球会更划算&#xff0c;因为星球内内容更多&a…

ArcGIS基础:如何在大量数据里挑选随机样本(创建随机点工具)

【创建随机点】工具位于【采样】工具下&#xff0c;如下所示&#xff1a; 假如我们有一个需求&#xff0c;要在很多数据里随机选择10个数据&#xff0c;就可以使用该工具。 假如我这里有全国的县级数据&#xff0c;我想要在里面随机抽选10个县城。 原始数据如下&#xff1a; …

IPSec 基础介绍

IPSec是IETF&#xff08;Internet Engineering Task Force&#xff09;制定的一组开放的网络安全协议。它并不是一个单独的协议&#xff0c;而是一系列为IP网络提供安全性的协议和服务的集合&#xff0c;包括认证头AH&#xff08;Authentication Header&#xff09;和封装安全载…

【JavaScript高级】05-JavaScript中with、eval语句及严格模式的使用

with、eval及严格模式的使用with语句的使用&#xff08;了解&#xff09;eval函数严格模式了解严格模式开启严格模式严格模式的限制with语句的使用&#xff08;了解&#xff09; with语句的作用是将代码的作用域设置到一个特定的对象中。目的主要是为了简化多次编写同一个对象…

【学习笔记50】ES6的新增属性1

一、ES6 * ES6 * 其实就是JS发展过程中的某一个版本而已, 那个版本的版本号叫做ES6* JS* 在最初的时候, 是只有var关键可以声明变量* 随着版本的更新, 在某一个版本内推出了新的变量声明方式* * JS的更新* 在推出ES6的时候, 这个版本推出的新东西比较多…

[附源码]Python计算机毕业设计高校教材网上征订系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

SQL关键字详解

当前市场中&#xff0c;数据库在互联网整个技术链中的重要性是亘古不变的&#xff0c;站在实现业务功能的角度来说我们最常用的就是与数据库和缓存进行交互&#xff0c;而最终持久化存储最常见的依旧是关系型数据库。数据库中我们做常用的就是SQL基本查询语言&#xff0c;甚至有…

临近期末,这些题不来看看吗?(上)

目录 1、在屏幕上输入9*9乘法表 2、输入一个值&#xff0c;打印对应的乘法口诀表 3、求十个整数的最大值 4、分数求和&#xff1a;计算1/1 - 1/2 1/3 - 1/4 1/5 ... 1/99 - 1/100&#xff08;3种方法&#xff09; 5、编写程序数一下&#xff0c;1到100的所有整数中出现多…

ggrcs 包2.4绘图实际操作演示(1)

ggrcs 包2.4版本已经发布一段时间了&#xff0c;大概几个月了吧&#xff0c;收到不少好评&#xff0c; 没听说太大的问题&#xff0c;最主要的问题有两个&#xff1a; 1.是说变量不是数字变量。 2.是说数据超过10万&#xff0c;无法处理 第一个问题非常好处理&#xff0c;这…

【精品】k8s的存储PV与PVC详解

概述 PV(Persistent Volume)一般情况下PV由kubernetes管理员进行创建和配置,它与底层具体的共享存储技术有关,并通过插件完成与共享存储的对接。 PVC(Persistent Volume Claim)是用户对于存储需求的一种声明。换句话说,PVC其实就是用户向kubernetes系统发出的一种资源需…

vscode开发maven的javaweb项目,并部署到tomcat及配置

1、安装并配置JAVA环境 我的是用的jdk1.8.0_181&#xff08;安装自行解决&#xff0c;直接可以下载免安装配置环境&#xff09; 配置JAVA_HOME&#xff0c;设置路径为C:\Program Files\Java\jdk1.8.0_181, 添加bin到path环境变量&#xff1a; 2、安装Maven 1&#xff09;官网…

垃圾分类小程序系统毕业设计,垃圾分类小程序系统设计与实现,垃圾分类系统毕设参考

功能清单 【管理员功能】 会员管理&#xff1a;查看网站所有注册会员信息&#xff0c;支持删除 资讯录入&#xff1a;录入资讯标题、时间、资讯内容等 管理资讯&#xff1a;查看现有资讯列表&#xff0c;支持修改和删除功能 留言管理&#xff1a;查看小程序留言列表&#xff0…

Azide PEG2 Pyrene|2135330-58-2|Pyrene标记的PEG连接物

Pyrene-PEG2-azide是一种含有叠氮化物基团的Pyrene标记的PEG连接物&#xff0c;它可以用任何含炔分子进行点击化学标记&#xff0c;从而将任何分子转化为含Pyrene的探针。亲水性PEG连接剂可以增加水溶液中生物分子的溶解度并促进其附着。 西安凯新生物科技有限公司azide系列产品…

Python人工智能学习路线(万字长文)

前言 随着全球市场的饱和&#xff0c;以及模式创新的用尽&#xff0c;传统的互联网产业已经进入成熟阶段&#xff0c;不会再有突飞猛进的发展。 &#xff08;文末送读者福利&#xff09; 接下来&#xff0c;是人工智能和大数据展露锋芒的时候了&#xff0c;它们在未来 10 年…