HTTP POST请求报文格式分析与Java实现文件上传

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

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

POST报文格式

POST /api/feed/ HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873 Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Host: www.myhost.com Connection: Keep-Alive --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="lng" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 116.361545 --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="lat" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 39.979006 --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg" Content-Type: application/octet-stream Content-Transfer-Encoding: binary 这里是图片的二进制数据 --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--

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

格式分析

请求头分析

我们先看 报文格式中的第一行:

POST /api/feed/ HTTP/1.1
这一行就说明了这个请求的请求方式,即为POST方式,要请求的子路径为/api/feed/,例如我们的服务器地址为www.myhost.com,然后我们的这个请求的完整路径就是www.myhost.com/api/feed/,最后说明了HTTP协议的版本号为1.1。
Accept-Encoding: gzip
Content-Length: 225873
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Host: www.myhost.com 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,那么参数分隔符则为:

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
不管boundary本身有没有这个"--",这个"--"都是不能省略的。

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

如上图中,左边的是关闭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。例如下面这个参数。

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="lng"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit 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”来换行的,最后一行和参数内容之间是两个换行。文件参数也是一样的格式,只是文件参数的内容是字节流。

这里要注意一下,普通文本参数和文件参数有如下两个地方的不同,因为其内容本身的格式是不一样的。

普通参数:

Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

文件参数:

Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

参数实体的最后一行是: --加上boundary加上--,最后换行,这里的 格式即为: --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--。

模拟文件上传请求

public static void uploadFile(String fileName) {try {
  // 换行符  final String newLine = "\r\n";  final String boundaryPrefix = "--";  // 定义数据分隔线  String BOUNDARY = "========7d4a6d158c9";  // 服务器的域名  URL url = new URL("www.myhost.com");  HttpURLConnection conn = (HttpURLConnection) url.openConnection();  // 设置为POST情  conn.setRequestMethod("POST");  // 发送POST请求必须设置如下两行  conn.setDoOutput(true);  conn.setDoInput(true);  conn.setUseCaches(false);  // 设置请求头参数  conn.setRequestProperty("connection", "Keep-Alive");  conn.setRequestProperty("Charsert", "UTF-8");  conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);  OutputStream out = new DataOutputStream(conn.getOutputStream());  // 上传文件  File file = new File(fileName);  StringBuilder sb = new StringBuilder();  sb.append(boundaryPrefix);  sb.append(BOUNDARY);  sb.append(newLine);  // 文件参数,photo参数名可以随意修改  sb.append("Content-Disposition: form-data;name=\"photo\";filename=\"" + fileName  + "\"" + newLine);  sb.append("Content-Type:application/octet-stream");  // 参数头设置完以后需要两个换行,然后才是参数内容  sb.append(newLine);  sb.append(newLine);  // 将参数头的数据写入到输出流中  out.write(sb.toString().getBytes());  // 数据输入流,用于读取文件数据  DataInputStream in = new DataInputStream(new FileInputStream(  file));  byte[] bufferOut = new byte[1024];  int bytes = 0;  // 每次读1KB数据,并且将文件数据写入到输出流中  while ((bytes = in.read(bufferOut)) != -1) {  out.write(bufferOut, 0, bytes);  }  // 最后添加换行  out.write(newLine.getBytes());  in.close();  // 定义最后数据分隔线,即--加上BOUNDARY再加上--。  byte[] end_data = (newLine + boundaryPrefix + BOUNDARY + boundaryPrefix + newLine)  .getBytes();  // 写上结尾标识  out.write(end_data);  out.flush();  out.close();  // 定义BufferedReader输入流来读取URL的响应 // BufferedReader reader = new BufferedReader(new InputStreamReader( // conn.getInputStream())); // String line = null; // while ((line = reader.readLine()) != null) { // System.out.println(line); // } } catch (Exception e) {  System.out.println("发送POST请求出现异常!" + e);  e.printStackTrace(); } } 

使用Apache Httpmime上传文件

/*** @param fileName 图片路径*/public static void uploadFileWithHttpMime(String fileName) { // 定义请求url String uri = "www.myhost.com"; // 实例化http客户端 HttpClient httpClient = new DefaultHttpClient(); // 实例化post提交方式 HttpPost post = new HttpPost(uri); // 添加json参数 try {  // 实例化参数对象  MultipartEntity params = new MultipartEntity();  // 图片文本参数  params.addPart("textParams", new StringBody(  "{'user_name':'我的用户名','channel_name':'却道明','channel_address':'(123.4,30.6)'}",  Charset.forName("UTF-8")));  // 设置上传文件  File file = new File(fileName);  // 文件参数内容  FileBody fileBody = new FileBody(file);  // 添加文件参数  params.addPart("photo", fileBody);  params.addPart("photoName", new StringBody(file.getName()));  // 将参数加入post请求体中  post.setEntity(params);  // 执行post请求并得到返回对象 [ 到这一步我们的请求就开始了 ]  HttpResponse resp = httpClient.execute(post);  // 解析返回请求结果  HttpEntity entity = resp.getEntity();  InputStream is = entity.getContent();  BufferedReader reader = new BufferedReader(new InputStreamReader(is));  StringBuffer buffer = new StringBuffer();  String temp;  while ((temp = reader.readLine()) != null) {  buffer.append(temp);  }  System.out.println(buffer); } catch (UnsupportedEncodingException e) {  e.printStackTrace(); } catch (ClientProtocolException e) {  e.printStackTrace(); } catch (IOException e) {  e.printStackTrace(); } catch (IllegalStateException e) {  e.printStackTrace(); } } 

HttpMime.jar下载地址 ,

下载httpClient的压缩包即可,httpmime.jar包含在其中。

 

 

转自:链接

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

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

相关文章

网络层:IP协议详解(IP协议真的得看这篇)

1、IP协议概念 IP(Internet Protocol , 互联网协议)主要用于互联网通信。IP协议用于将多个包交换网络连接起来,他在原地址和目的地址之间传输数据报,还提供对数据大小的重新组装功能,以适应不同网络的要求。 IP协议是…

传输层两大协议:TCP与UDP详解(两者的联系与区别)

一、TCP协议 1、TCP协议报文格式 TCP协议报文格式详解 2、TCP“三次握手”建立连接 位码即tcp标志位,有6种标示: SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急) Sequence number(顺序号码) Ac…

docker二进制安装mysql_Docker搭建MySQL读写分离主从模式 分布式数据库中间件Mycat分库分表应用...

一、MySQL读写分离主从模式1. 下载镜像docker pull mysql当前最新版本:mysql Ver 8.0.19 for Linux on x86_64 (MySQL Community Server - GPL)2. 启动主节点并修改配置文件docker run -it --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql bash修改/…

五种I/O模型详解

1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不…

存储过程 while is null_4.2 串的存储实现(2)

返回目录:Chilan Yu:《数据结构》目录链接​zhuanlan.zhihu.com4.2.2 堆串字符串包括串名与串值两部分,而串值采用堆串存储方式存储,串名用符号表存储。堆串存储方式:这种存储方法以一组地址连续的存储单元存放串的字符…

高光谱图像pca降维_高光谱图像的数据特性之探讨

图像是获取信息以及探知世界的重要媒介。近年来,传感科技与成像技术实现了跨越式发展,促使图像获取在质与量上均获得了显著提升。在多样化成像手段中,光谱成像技术是成像科技的重要组成部分,是人类借助光这一能量手段探测物质特性…

注册Tomcat服务为系统服务

2019独角兽企业重金招聘Python工程师标准>>> 将解压版的Tomcat服务注册为系统服务之后,就不用每次启动机器之后都要手动去启动"startup.bat",就方便多了,下面就是具体步骤(以我的机器为例,我的To…

【Spring学习笔记-MVC-17】Spring MVC之拦截器

作者:ssslinppp 1. 拦截器简介及应用场景2. 拦截器接口及拦截器适配器3. 运行流程图正常运行中断流程4. 程序实例控制层:ControllerRequestMapping(value "/test")public class TestController {RequestMapping(value "/intercept…

iOS之地理位置及定位系统 -- 入门笔记(用Swift)

前言:关于地理位置及定位系统,在iOS开发中也比较常见,比如美团外面的餐饮店铺的搜索,它首先需要用户当前手机的位置,然后在这个位置附近搜索相关的餐饮店铺的位置,并提供相关的餐饮信息,再比如最…

PHP监測memcache服务端的执行状况

. 代码例如以下,代码为memcache官方代码,引用在此。做一下简单的说明: 1、设置username和password define(ADMIN_USERNAME,admin); // Admin Username define(ADMIN_PASSWORD,123456); // Admin Password 2、配置server及其port&#xff0…

linux grep 匹配空格_17 个案例,5 分钟简单搞定 Linux 正则表达式!

正则表达式是一种字符模式,用于在查找过程中匹配制定的字符。元字符通常在Linux中分为两类:Shell元字符,由Linux Shell进行解析;正则表达式元字符,由vi/grep/sed/awk等文本处理工具进行解析;正则表达式一般…

micropython web ws2812_MicroPython实例之TPYBoard v102炫彩跑马灯WS2812B

一、实验目的了解ws2812b的工作原理学习ws2812b的驱动方法二、实验器材TPYBoard v102 1块ws2812b RGB-Ring-8 1个micro USB数据线 1条杜邦线 若干三、WS2812B的介绍WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源。 其外型与一个5050LED灯珠相同, 每个元…

mysql数据库表复用_MySQL 数据库之表操作

一、创建表的完整语法create table 表(字段名1 类型 [(宽度) 约束条件],字段名2 类型 [(宽度) 约束条件],字段名3 类型 [(宽度) 约束条件]);1.类型:使用限制字段必须以什么样的数据类型传值约束条件:约束条件是在类型之…

Android有道词典查询功能

有道词典 任务要求&#xff1a;完成查词等功能 因为需要申请API key&#xff0c;这里直接给出地址供使用&#xff1a;http://fanyi.youdao.com/openapi?pathdata-mode 1、activity_main.xml基本格局&#xff08;不做任何说明&#xff09; 代码如下&#xff1a; 1 <Relative…

从零开始学Axure原型设计(高级篇)

如果你熟悉了Axure的部件库&#xff0c;那么你可以得心应手地画出心目中产品的线框图&#xff1b;如果你会用Axure的母版、动态面板功能&#xff0c;那么你应该能够画出一些简单网站的原型图&#xff1b;但只有你精通了Axure的条件逻辑、变量、函数等高级交互&#xff0c;才能将…

python vector 初始化_从零开始搭建机器学习算法框架(python)--计算框架

介绍今天开始一个新的系列&#xff0c;这个系列的目标是用python在不使用任何第三方库的情况下去实现各类机器学习或者深度学习的算法。之所以会有这种想法是因为每当我想提高编程技巧的时候&#xff0c;我总希望能够做一些简单又有趣的小项目练手。我一直对机器学习算法颇感兴…

windows phone 页面主题设计

达到如图这个效果&#xff1a; 1.保证状态栏背景色与主题栏颜色相同需设置状态栏的透明度&#xff0c;代码如下&#xff1a;shell:SystemTray.IsVisible"True" shell:SystemTray.Opacity"0.01" 2.顶部状态栏高度为25&#xff0c;字的上下要留部分空白3.内容…

JAVA如何才能导出这样的EXCEL?

2019独角兽企业重金招聘Python工程师标准>>> 最近的项目有个需求&#xff0c;需要做个报表&#xff0c;excel如上所示。没有很好的办法&#xff0c;求指教。 转载于:https://my.oschina.net/secret620/blog/611450

Xcode 修改工程名以及注意事项

1、先把整个工程文件夹名改为新的工程名。 2、打开工程&#xff0c;单击&#xff0c;输入新的工程名,会出现&#xff0c;点击确定。 3、回到工程界面&#xff0c;在中选择 Manage Schemes,然后再弹出的对话框&#xff0c;把工程名改为新的名字。 4、最好在工程中&#xff0c;把…

脚本命令配置mysql_MySQL 自动化部署脚本

一、环境说明操作系统&#xff1a;CentOS数据库版本&#xff1a;MySQL 5.7/8.0参数&#xff1a;buffer pool 会根据系统内存指定、默认双一、GTID、SlowLog脚本默认安装路径&#xff1a;/usr/local/mysql脚本默认数据路径&#xff1a;/data/mysql*(根据安装包版本适应 比如 5.7…