当前位置:首页 > 服务端 > JAVA模拟HTTP post请求上传文件

JAVA模拟HTTP post请求上传文件

2022年11月07日 13:21:26服务端6

在开发中,我们使用的比较多的HTTP请求方式基本上就是GET、POST。其中GET用于从服务器获取数据,POST主要用于向服务器提交一些表单数据,例如文件上传等。而我们在使用HTTP请求时中遇到的比较麻烦的事情就是构造文件上传的HTTP报文格式,这个格式虽说也比较简单,但也比较容易出错。今天我们就一起来学习HTTP POST的报文格式以及通过Java来模拟文件上传的请求。

首先我们来看一个POST的报文请求,然后我们再来详细的分析它。

POST报文格式

 

[plain]  view plain  copy
  1. POST /api/feed/ HTTP/1.1  
  2. Accept-Encoding: gzip  
  3. Content-Length: 225873  
  4. Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  5. Host: www.myhost.com  
  6. Connection: Keep-Alive  
  7.   
  8. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  9. Content-Disposition: form-data; name="lng"  
  10. Content-Type: text/plain; charset=UTF-8  
  11. Content-Transfer-Encoding: 8bit  
  12.   
  13. 116.361545  
  14. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  15. Content-Disposition: form-data; name="lat"  
  16. Content-Type: text/plain; charset=UTF-8  
  17. Content-Transfer-Encoding: 8bit  
  18.   
  19. 39.979006  
  20. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  21. Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"  
  22. Content-Type: application/octet-stream  
  23. Content-Transfer-Encoding: binary  
  24.   
  25. 这里是图片的二进制数据  
  26. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--  

这里我们提交的是经度、纬度和一张图片(图片数据比较长,而且比较杂乱,这里省略掉了)。

格式分析

请求头分析

我们先看报文格式中的第一行:
 
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. POST /api/feed/ HTTP/1.1  
这一行就说明了这个请求的请求方式,即为POST方式,要请求的子路径为/api/feed/,例如我们的服务器地址为www.myhost.com,然后我们的这个请求的完整路径就是www.myhost.com/api/feed/,最后说明了HTTP协议的版本号为1.1。
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. Accept-Encoding: gzip  
  2. Content-Length: 225873  
  3. Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  4. Host: www.myhost.com  
  5. Connection: Keep-Alive  
这几个header的意思分别为服务器返回的数据需要使用gzip压缩、请求的内容长度为225873、内容的类型为"multipart/form-data"、请求参数分隔符(boundary)为OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp、请求的根域名为www.myhost.com、HTTP连接方式为持久连接( Keep-Alive)。
 
其中这里需要注意的一点是分隔符,即boundary。boundary用于作为请求参数之间的界限标识,例如参数1和参数2之间需要有一个明确的界限,这样服务器才能正确的解析到参数1和参数2。但是分隔符并不仅仅是boundary,而是下面这样的格式:-- + boundary。例如这里的boundary为OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,那么参数分隔符则为:
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
不管boundary本身有没有这个"--",这个"--"都是不能省略的。

我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后续请求时,Keep-Alive功能避免了建立或者重新建立连接。

JAVA模拟HTTP post请求上传文件 _ JavaClub全栈架构师技术笔记

如上图中,左边的是关闭Keep-Alive的情况,每次请求都需要建立连接,然后关闭连接;右边的则是Keep-Alive,在第一次建立请求之后保持连接,然后后续的就不需要每次都建立、关闭连接了,启用Keep-Alive模式肯定更高效,性能更高,因为避免了建立/释放连接的开销。

http 1.0中默认是关闭的,需要在http头加入"Connection: Keep-Alive",才能启用Keep-Alive;http 1.1中默认启用Keep-Alive,如果加入"Connection: close ",才关闭。目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看服务器设置情况。

请求实体分析

请求实体其实就是HTTP POST请求的参数列表,每个参数以请求分隔符开始,即-- + boundary。例如下面这个参数。
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp  
  2. Content-Disposition: form-data; name="lng"  
  3. Content-Type: text/plain; charset=UTF-8  
  4. Content-Transfer-Encoding: 8bit  
  5.   
  6. 116.361545  
上面第一行为--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,也就是--加上boundary内容,最后加上一个换行 (这个换行不能省略),换行的字符串表示为"\r\n"。第二行为Content-Disposition和参数名,这里的参数名为lng,即经度。Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名,这里我们不过多关注。第三行为Content-Type,即WEB 服务器告诉浏览器自己响应的对象的类型,还有指定字符编码为UTF-8。第四行是描述的是消息请求(request)和响应(response)所附带的实体对象(entity)的传输形式,简单文本数据我们设置为8bit,文件参数我们设置为binary就行。然后添加两个换行之后才是参数的具体内容。例如这里的参数内容为116.361545。
注意这里的每行之间都是使用“\r\n”来换行的,最后一行和参数内容之间是两个换行。文件参数也是一样的格式,只是文件参数的内容是字节流。
这里要注意一下,普通文本参数和文件参数有如下两个地方的不同,因为其内容本身的格式是不一样的。
普通参数:
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. Content-Type: text/plain; charset=UTF-8  
  2. Content-Transfer-Encoding: 8bit  
文件参数:
[plain]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1. Content-Type: application/octet-stream  
  2. Content-Transfer-Encoding: binary  
 
参数实体的最后一行是: --加上boundary加上--,最后换行,这里的 格式即为: --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--。

模拟文件上传请求

[java]  view plain  copy
 在CODE上查看代码片派生到我的代码片
  1.     public static void uploadFile(String fileName) {  
  2.         try {  
  3.   
  4.             // 换行符  
  5.             final String newLine = "\r\n";  
  6.             final String boundaryPrefix = "--";  
  7.             // 定义数据分隔线  
  8.             String BOUNDARY = "========7d4a6d158c9";  
  9.             // 服务器的域名  
  10.             URL url = new URL("www.myhost.com");  
  11.             HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  12.             // 设置为POST情  
  13.             conn.setRequestMethod("POST");  
  14.             // 发送POST请求必须设置如下两行  
  15.             conn.setDoOutput(true);  
  16.             conn.setDoInput(true);  
  17.             conn.setUseCaches(false);  
  18.             // 设置请求头参数  
  19.             conn.setRequestProperty("connection", "Keep-Alive");  
  20.             conn.setRequestProperty("Charsert", "UTF-8");  
  21.             conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);  
  22.   
  23.             OutputStream out = new DataOutputStream(conn.getOutputStream());  
  24.   
  25.             // 上传文件  
  26.             File file = new File(fileName);  
  27.             StringBuilder sb = new StringBuilder();  
  28.             sb.append(boundaryPrefix);  
  29.             sb.append(BOUNDARY);  
  30.             sb.append(newLine);  
  31.             // 文件参数,photo参数名可以随意修改  
  32.             sb.append("Content-Disposition: form-data;name=\"photo\";filename=\"" + fileName  
  33.                     + "\"" + newLine);  
  34.             sb.append("Content-Type:application/octet-stream");  
  35.             // 参数头设置完以后需要两个换行,然后才是参数内容  
  36.             sb.append(newLine);  
  37.             sb.append(newLine);  
  38.   
  39.             // 将参数头的数据写入到输出流中  
  40.             out.write(sb.toString().getBytes());  
  41.   
  42.             // 数据输入流,用于读取文件数据  
  43.             DataInputStream in = new DataInputStream(new FileInputStream(  
  44.                     file));  
  45.             byte[] bufferOut = new byte[1024];  
  46.             int bytes = 0;  
  47.             // 每次读1KB数据,并且将文件数据写入到输出流中  
  48.             while ((bytes = in.read(bufferOut)) != -1) {  
  49.                 out.write(bufferOut, 0, bytes);  
  50.             }  
  51.             // 最后添加换行  
  52.             out.write(newLine.getBytes());  
  53.             in.close();  
  54.   
  55.             // 定义最后数据分隔线,即--加上BOUNDARY再加上--。  
  56.             byte[] end_data = (newLine + boundaryPrefix + BOUNDARY + boundaryPrefix + newLine)  
  57.                     .getBytes();  
  58.             // 写上结尾标识  
  59.             out.write(end_data);  
  60.             out.flush();  
  61.             out.close();  
  62.   
  63.             // 定义BufferedReader输入流来读取URL的响应  
  64. //            BufferedReader reader = new BufferedReader(new InputStreamReader(  
  65. //                    conn.getInputStream()));  
  66. //            String line = null;  
  67. //            while ((line = reader.readLine()) != null) {  
  68. //                System.out.println(line);  
  69. //            }  
  70.   
  71.         } catch (Exception e) {  
  72.             System.out.println("发送POST请求出现异常!" + e);  
  73.             e.printStackTrace();  
  74.         }  
  75.     }  

作者:huangcqen
来源链接:https://www.cnblogs.com/woolhc/p/6123975.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/68358.html

标签: HTTPJava
分享给朋友:

“JAVA模拟HTTP post请求上传文件” 的相关文章

深入理解 Java 并发锁

深入理解 Java 并发锁

📦 本文以及示例源码已归档在 javacore 一、并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的...

JDBC连接时所犯错误1.字符集设置不合适2.连接MySQL8.0社区版时时区不一致3..包名不能以Java.命名4.驱动被弃用

Microsoft JDBC Driver 的主页为:https://msdn.microsoft.com/en-us/data/aa937724.aspx 下载所需驱动 今天连接时报了四次错,记录下来 1.java.sql.SQLException:...

Java实现素数的判断

素数的定义只能被1和它本身整除,不包括1 例 2.3.5.7.11.13 实现代码 Scanner in=new Scanner(System.in); int n ; n=in.nextInt(); for(int n1=2;n1&l...

枚举法 之Java实现凑硬币

问题? 如何利用1元五元十元凑硬币 Scanner in=new Scanner(System.in); int amout ; amout=in.nextInt(); for(int one =0;one<=amout;one+...

Java对象的大小

基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。 在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。看 下面语句: Object ob = new Ob...

在JAVA 中将堆与栈分开的原因

栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么 放、放在哪儿。 注意:在Java中一个线程就会相应有一个线程栈与之对应 栈因为是运行单位,因此里面存储的信息都是跟...

Java实现1到n的倒数的累加和

Java实现1到n的倒数的累加和

从键盘读入一个数,然后进行运算 实现代码: public static void main(String[] args) { Scanner in=new Scanner(System.in); int n ; n=in....

java提高篇(十六)

java提高篇(十六)

     Java的基本理念是“结构不佳的代码不能运行”!!!!!       大成若缺,其用不弊。       大...

java空指针异常:java.lang.NullPointException

一.什么是java空指针异常     我们都知道java是没有指针的,这里说的"java指针"指的就是java的引用,我们不在这里讨论叫指针究竟合不合适,而只是针对这个异常本身进行分析。空指针就是空引用,java空指针异常就是引用本身为空,却调用了方...

JAVA的文件操作【转】

11.3 I/O类使用          由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择。因为文件是一种常见的数据源,而且读写文件也是程...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。