表单提交 multipart/form-data 和 x-www-form-urlencoded的区别

表单提交

表单有两种提交方式,POST和GET。通常我们会使用POST方式,一是因为形式上的安全 ;二是可以上传文件。

我之前经常忽略掉表单的编码类型,觉得它特别长比较难记,而且不设置也似乎不影响什么。表单的编码类型,用来控制表单向服务器发送表单数据之前如何对其进行编码。POST 提交方式 默认 enctype=“application/x-www-form-urlencoded”,数据以键值对的方式传送到服务器,这种方式适合于大多数场景。

表单(POST请求)支持下面两种编码:

enctype
application/x-www-form-urlencoded默认编码方式, key1=value1&key2=value2
multipart/form-data普通表单提交,以及表单文件上传
text/plain该方式不常用,数据获取方式 getInputStream

表单(GET请求) 编码方式:

enctype
application/x-www-form-urlencodedkey1=value1&key2=value2

Content-Type :http/https发送信息至服务器时的内容编码类型,contentType用于表明发送数据流的类型,服务器根据编码类型使用特定的解析方式,获取数据流中的数据。
上面仅仅是html表单支持的编码方式,实际上面还有存在各种文件类型的编码方式:

image/jpeg, image/png, image/gif,text/css, text/javascript,text/html,text/xml等等

在浏览器下 使用GET 方式进行表单提交 getContentType 始终返回 null

ServletRequest中获取参数常用方法:

Map<String,String[]> maps= request.getParameterMap(); //获取所有的键值对
Enumeration<String> names= request.getParameterNames();//获取所有的参数名
String[] values = request.getParameterValues(parameterName); //获取某个参数下的所有值,适合多选组件
String value = request.getParameter(parameterName); //获取某个参数对应的值,适合文本组件,单选等
ServletInputStream stream=request.getInputStream();//需要注意的是,该方法只能被调用一次,再次调用返回结果为空
BufferedReader reader=request.getReader();//该方法也是只能调用一次
Collection<Part> parts = request.getParts();// 获取 multipart/form-data 编码方式的数据

需要额外注意的是:getInputStream 方法和getReader方法互斥。最多只能调用其中的一个,如果这两个方法都被调用则会抛出异常。

request.getInputStream();//需要注意的是,该方法只能被调用一次,再次调用返回结果为空
request.getReader();//该方法也是只能调用一次
1.application/x-www-form-urlencoded
  1. GET方式,会将表单中的数据(键值对)经过urlencode编码后追加到url中。
  2. POST方式,会将表单中的数据经过urlencode编码后放在request body 中。

GET请求方式:

<form action="/xxxx" method="get"  enctype="application/x-www-form-urlencoded">
  name: <input type="text" name="name"><br>
  password: <input type="text" name="password"><br>
  <input type="submit" value="提交">
</form>

name=%E6%B5%8B%E8%AF%95%E4%BA%BA&password=test

通过浏览器自带功能可以看到参数编码前后的数据
get请求
在这里插入图片描述

此外经过测试发现get请求下,浏览器(IE,Chrome)在请求头中不会添加Content-Type字段。所以 通过getContentType方法 始终返回null。

 System.out.println(request.getParameter("name"));
 System.out.println(request.getParameter("password"));

测试人
test



POST 请求方式:

<form action="/xxxx" method="post"  enctype="application/x-www-form-urlencoded">
  name: <input type="text" name="name"><br>
  password: <input type="text" name="password"><br>
  <input type="submit" value="提交">
</form>

POST 提交方式: name 传递 “测试人”,password 传递“test”

Content-Type: application/x-www-form-urlencoded

name=%E6%B5%8B%E8%AF%95%E4%BA%BA&password=test

接下来就是获取值,可以通过以下方法获取到值。

Map<String,String[]> maps= request.getParameterMap(); //获取所有的键值对
Enumeration<String> names= request.request.getParameterNames();//获取所有的参数名
String[] values = request.getParameterValues(parameterName); //获取某个参数下的所有值,适合多选组件
String value = request.getParameter(parameterName); //获取某个参数对应的值,适合文本组件,单选等
  String contentType  = request.getContentType();
  System.out.println("请求数据类型:"+contentType);
  System.out.println(request.getParameter("name"));
  System.out.println(request.getParameter("password"));

请求数据类型:application/x-www-form-urlencoded
测试人
test

如果需要在表单中上传文件则不能使用该编码方式,否则只会传递文件名(Chrome浏览器下)。
在这里插入图片描述

application/x-www-form-urlencoded:该编码方式,会将数据中的非西欧字符转化为十六进制数字的形式。utf8字符集中,每个汉字占3个字节,每个汉字会转化成3个十六进制的数 %XX%XX%XX 的形式。

经常处理的下载文件名中文乱码很多时候也是使用这种方式,对于IE,谷歌等浏览器(火狐浏览器有些差异)用到的就是编码方式。

try {
	System.out.println(URLEncoder.encode("测试人","UTF8"));
	//%E6%B5%8B%E8%AF%95%E4%BA%BA
} catch (UnsupportedEncodingException e) {
	e.printStackTrace();
}
2.multipart/form-data

当需要在表单内上传文件时(二进制流数据)时,就需要使用multipart/form-data 编码方式。

<form action="/xxxx" method="POST"  enctype="multipart/form-data">
  	<label>姓名</label><input type="text" name="name"><br>
    <label>密码</label> <input type="text" name="password"><br>
	 <label>头像</label><input type="file" name="file"><br/>
  	<input type="submit" value="提交">
</form>

请求头的内容

Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySOoxs1FmM0JjR5EM

请求体的内容

------WebKitFormBoundarySOoxs1FmM0JjR5EM
Content-Disposition: form-data; name="name"

测试人
------WebKitFormBoundarySOoxs1FmM0JjR5EM
Content-Disposition: form-data; name="password"

12
------WebKitFormBoundarySOoxs1FmM0JjR5EM
Content-Disposition: form-data; name="file"; filename="v.jpg"
Content-Type: image/jpeg

����
.......此处省略1000......

multipart,顾名思义 多部分。使用该编码方式会将表单进行分割成每个控件(以----boundary为分隔符,将分割控件数据分隔开,在最后以—boundary—结尾)。每个部分必须加上Content-Disposition(form-data) ,对于上传文件还会设置Content-Type。

对于上传的不同类型的文件,会自动识别文件类型。对于无法识别的类型设置为application/octet-stream

  1. 上传jpeg格式图片

    Content-Type: image/jpeg
    
  2. 上传pdf文件

    Content-Type: application/pdf
    
  3. 上传MP3

    Content-Type: audio/mp3
    
  4. 上传 Excel (.xlsx)

    Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    

对于使用该方式提交的表单,在服务器端的参数获取通常有以下两种:

getParameterMap() ,getParameter()// 仅能获取键值对数据,无货获取上传的文件信息
getParts()// 获取表单中的全部数据(key-value键值对 和 二进制文件流)  。

Part 接口提供了以下方法:
在这里插入图片描述

  1. getName 获取属性名
  2. getInputStream 获取 对应的值
  3. getContentType 获取内容编码方式

使用getParts()方法,要求Content-Type:multipart/form-data,否则抛出异常。
通过Part 获取上传的文本内容

 		
		System.out.println(request.getParameter("name"));
        System.out.println(request.getParameter("password"));
        System.out.println(request.getParameter("file"));
	     System.out.println("----------------getParts获取参数-------------------------");
		//如果是multipart/form-data 提交方式,则此时 contentType:multipart/form-data; boundary=----WebKitFormBoundarySOoxs1FmM0JjR5EM
		String contentType=request.getContentType();
		
		if(contentType !=null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {//判断是否是multipart/form-data 提交方式
		
			 InputStream inputStream = request.getInputStream();
             byte[] bytes = new byte[1024];
             int length1;
             StringBuffer buffer1 = new StringBuffer();
             for (; (length1 = inputStream.read(bytes)) != -1; ) {
                 buffer1.append(new String(bytes, 0, length1));
             }
             System.out.println("字节流数据:" + buffer1);
             // multipart/form-data
             Collection<Part> parts = request.getParts(); //获取所有的部分
             for(Part part :parts) {
                 System.out.println("数据类型:" + part.getContentType());
                 InputStream  inPart = part.getInputStream();
                 String name=part.getName();//请求名称
                 StringBuilder buffer=new StringBuilder(); //保存请求值
                 byte[] buff =new byte[1024];
                 int length=0;
                 while((length=inPart.read(buff))!=-1) {
                     buffer.append(new String(buff,0,length,"utf8"));
                 }
                 System.out.println("name = "+name+"\tvalue="+buffer);
             }
		}

输出结果

测试人
test
null
----------------getParts获取参数-------------------------
字节流数据:
数据类型:null
name = name	value=测试人
数据类型:null
name = password	value=test
数据类型:image/jpeg
name = file	value=L        �      F�      ��t�b�h��>� ��t�(�                    A P�O� �:i� +00� /D:\                   J 1     �N� svn 8 	  ��N��N�.   �d                 ��� s v n    J 1     �N� bin 8 	  

注意
在判断contentType时,multipart/form-data提交方式的contentType不单纯是multipart/form-data,还会包含 boundary=----WebKitFormBoundaryXXXXX

小结

测试代码 用的框架是 springmvc

使用不同的编码方式,服务端获取参数的方式也不尽相同。

  1. application/x-www-form-urlencoded。 该方式就是常规的方法, getParameterMap(),getParameter()等方式获取参数

  2. multipart/form-data

    键值对二进制数据
    1getParameter(),getParameterMapgetParts();
    2getParts()

    注意: 使用 getParts() 方法 必须保证 ContentType 包含 ”multipart/form-data“ ,否则抛出ServletException,如果上传文件特别大则会抛出IllegalStateException

补充

在使用elasticsearch 的时候,发现 GET请求可以有请求体。当时就很懵逼,查阅资料后发现http标准中 GET请求确实可以有请求体。之前使用POSTMAN的时候都没有注意过,也是心大了。
在这里插入图片描述
请求体的编码方式和POST请求一致,但是GET方式请求体可以传输的数据要远小于POST 请求的。

在这里插入图片描述

使用请求体的方式传递参数,以下是在postman下的测试结果:

内容编码方式描述GET 请求获取数据POST 请求获取数据
application/x-www-form-urlencoded键值对getInputStreamgetParameter
multipart/form-datagetParameter, getPartsgetParameter, getParts
raw文本数据。比如常用的 application/jsongetInputStreamgetInputStream
binary单个文件,内容的编码方式对应上传文件的格式getInputStream ,getReader XgetInputStream ,getReader X

POST的请求的参数基本都是放在请求体中的,所以和表单的获取方式基本没有差异。GET 请求方式将参数放到请求体,大多数情况下只能通过流的方式获取数据。

注意:
1. getInputStream() 不仅限于 getInputStream方法,还包括 getReader()方法
2. getParameter() 方式还包括 getParameterMap(),getParameterValues(),getParameterNames()
3. getReader()和getInputSteam() 互斥
4. 对于 binary ,ContentType 的值与其上传时文件格式一致(判断文件后缀,而不是文件的头部标识),无法识别的文件类型则设置为 application/octet-stream
5. row ,文本类型 包含text/plain、text/xml、text/html等,当然还包括使用最频繁的application/json

row type

常见的文本格式:

  • text/plain: 纯文本
  • text/xml :传递xml语法格式的字符串
  • text/html: 传递html字符串
  • application/json : 传递序列化后的 JSON 字符串。

获取数据

ServletInputStream  in = request.getInputStream();//该方法也是只能调用一次
BufferedReader reader=request.getReader();//该方法也是只能调用一次

文本数据格式,会使用流的方式传递数据。在SpringMVC框架下@RequestBody注解 可以将 传入的row类型二进制流转化为对应的对应实体类。 SpringMVC会根据 ContentType 选择合适的转化器,将文本字符串转化为对象。

比如经常配置的fastjson转化器。使用 fastjson 取代 Jackson 作为默认的json 转化器

    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        FastJsonHttpMessageConverter fastConvert = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
                SerializerFeature.WriteMapNullValue);
        //处理日期时间格式化问题
        fastJsonConfig.setDateFormat("yyyy-MM-dd hh:mm:ss");
        //处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConvert.setSupportedMediaTypes(fastMediaTypes);
        fastConvert.setFastJsonConfig(fastJsonConfig);
        return new HttpMessageConverters((HttpMessageConverter<?>) fastConvert);
    }

注意:
我们使用ajax 发送请求时,传入的数据通常为json数据。之前我想当然的认为 ajax 传递的数据类型就是 application/json(row)。后面发现如果ajax请求中没有设置contentType参数,默认情况下会使用 Content-Type: application/x-www-form-urlencoded

	$.ajax('http://localhost/hello',{
		data:{
			name:"测试人",
			password:12
		},
		// contentType: "application/json; charset=utf-8",
		dataType:'json',//服务器返回json格式数据
		type:'POST',//HTTP请求类型
		timeout:10000,//超时时间设置为10秒;
		success:function(data){
			console.log(data)
		},
		error:function(xhr,type,errorThrown){			
		}
	});

服务器获取到的结果:

数据类型:application/x-www-form-urlencoded

通过getParameter 获取参数(getInputStream无法获取数据):

  System.out.println(request.getParameter("name"));
  System.out.println(request.getParameter("password"));

测试人
12


	$.ajax('http://localhost/hello',{
		data:{
			name:"测试人",
			password:12
		},
		contentType: "application/json; charset=utf-8",
		dataType:'json',//服务器返回json格式数据
		type:'POST',//HTTP请求类型
		timeout:10000,//超时时间设置为10秒;
		success:function(data){
			console.log(data)
		},
		error:function(xhr,type,errorThrown){			
		}
	});

服务器获取到的结果:

数据类型:application/json; charset=UTF-8

通过getInputStream 方法获取传入参数:

 InputStream inputStream = request.getInputStream();
 byte[] bytes = new byte[1024];
 int length;
 StringBuffer buffer = new StringBuffer();
 for (; (length = inputStream.read(bytes)) != -1; ) {
     buffer.append(new String(bytes, 0, length));
 }
 System.out.println("字节流数据:" + buffer.toString());
 //解码
 System.out.println(URLDecoder.decode(buffer.toString(), "UTF-8"));

输出结果

字节流数据:name=%E6%B5%8B%E8%AF%95%E4%BA%BA&password=12
name=测试人&password=12

在这里插入图片描述
以上jquery测试 使用的版本时 jQuery v1.7.1

ajax请求的使用建议

  1. 对于简单的数据格式不配置 contentType,后端使用getParameter 等方法获取参数,当然SpringMVC 对于该操作做了简化处理。我们可以直接通过 方法参数拿到值
  2. 对于复杂的数据结构建议设置 contentType=application/json 同时 服务端需要使用 带有@RequestBody 修饰的方法参数来接受参数
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值