Form 表单数据编码解码--encodeURIComponentURLSearchParamsFormData

Posted 奋飛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Form 表单数据编码解码--encodeURIComponentURLSearchParamsFormData相关的知识,希望对你有一定的参考价值。

本文主要讲解,通过 web api 来处理各种参数问题,防止产生安全问题,以及更便利的操作。

先看一个示例:

const response = await fetch(url, {
  method: 'POST',
  body: `text=${text}`,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
const json = await response.json()

上述代码会出现一些“安全问题”,如通过 text=${text} 进行 SQL 或 html 注入。

开始之前,先罗列一下我们日常开发中经常用到的“内容类型 – Content-Type,用于指定资源的MIME类型 media type ,定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。

Content-Type 常用类型说明
application/x-www-form-urlencoded默认,表单数据
multipart/form-data表单数据(可包含文件数据)
application/jsonjson 数据格式
image/pngpng 图片格式
text/htmlHTML格式
text/plain纯文本格式

更多类型,可参考 MIME types 列表

encodeURIComponent

<form>表单请求默认格式 x-www-form-urlencoded,将表单内的数据转换为键值对,如 title=%E4%BD%A0%E5%A5%BD&content=this+post+about+x-www-form-urlencoded

<form action="" method="post" target="" enctype="application/x-www-form-urlencoded">
  <p><label>文章标题:<input type="text" name="title" value="" /></label></p>
  <p><label>文章内容:<textarea name="content" rows="5" cols="33"></textarea></label></p>
  <p><button type="submit">提交</button></p>
</form>

注意: 由于历史的原因,表单使用的 Url 编码实现并不符合最新的标准,将空格处理成了 + 。

// href:https://example.com/?title=%E4%BD%A0%E5%A5%BD&content=this%20post%20about%20x-www-form-urlencoded
new URL('https://example.com/?title=你好&content=this post about x-www-form-urlencoded')

MIME 类型的数据是 application/x-www-form-urlencoded 时,在 HTML 和 XForms 规范中定义仍然采用早期版本,用“+”代替“%20”替换空格。-- URL encoding the space character: + or %20?

业务中,我们通常不是通过 action 的方式发送,而是通过 ajax/fetch 方式进行封装处理,此时需要对数据进行编码或解码操作。

// title=%E4%BD%A0%E5%A5%BD&content=this%20post%20about%20x-www-form-urlencoded
params = `title=${encodeURIComponent('你好')}&content=${encodeURIComponent('this post about x-www-form-urlencoded')}`

注意: 空格的处理结果 encodeURIComponent(" ") // %20

encodeURI:自身无法产生能适用于HTTP GET 或 POST 请求的URI,例如对于 XMLHTTPRequests,因为 “&”, “+”, 和 “=” 不会被编码,然而在 GET 和 POST 请求中它们是特殊字符

URLSearchParams

通过encodeURIComponent()decodeURIComponent() 可以完成相关参数的编码、解码工作,但整体操作和处理都比较复杂,特别是在参数众多,需要获取指定参数的过程中。

function enhanceUrlArgs(query){
	var args = {};
	query.replace(/([^?&=]+)=([^&]+)/g, function(full, key, value){
		args[key] = decodeURIComponent(value);
		return "";
	});
	return args;
}

// {title: "你好", content: "this post about x-www-form-urlencoded"}
enhanceUrlArgs(new URL('https://example.com/?title=你好&content=this post about x-www-form-urlencoded').search)

可以通过 URLSearchParams 处理编码和解码 application/x-www-form-urlencoded 数据,处理方式大大简化。

示例:模拟上述 from 表达提交形式

const searchParams = new URLSearchParams()
searchParams.set('title', '你好')
searchParams.set('content', 'this post about x-www-form-urlencoded')

// title=%E4%BD%A0%E5%A5%BD&content=this+post+about+x-www-form-urlencoded
console.log(searchParams.toString()) 

注意:这个和 form 表单默认处理一致!

构造函数也可以接受“键/值对数组”

new URLSearchParams([
  ['title', '你好'],
  ['content', 'this post about x-www-form-urlencoded']
])

再者,也可以是“对象”

new URLSearchParams({
  title: '你好',
  content: 'this post about x-www-form-urlencoded'
})

还可以是“字符串”

new URLSearchParams('title=你好&content=this post about x-www-form-urlencoded')	// location.search

读取方式

和设置方式一一对应

示例:获取上述表单数据

for (const [key, value] of searchParams) {
  console.log(key, value)
}

得到“数组”

// [ ['title', '你好'], ['content', 'this post about x-www-form-urlencoded']]
[...searchParams] 

得到“对象”

// {title: "你好", content: "this post about x-www-form-urlencoded"}
Object.fromEntries(searchParams) 

Object.fromEntries(iterable) 方法把键值对列表转换为一个对象。

需要注意,对象的key是唯一的,可能出现有损转换

const searchParams2 = new URLSearchParams([
  ['category', 'javascript'],
  ['category', '前端']
])

// "category=javascript&category=%E5%89%8D%E7%AB%AF"
searchParams2.toString()
// {category: "前端"}
Object.fromEntries(searchParams2)

后者覆盖前者。对于表达 from 提交时,类似 select multiple 是真实存在的,需要格外注意。

避免有损转换:

Object.fromEntries(
	[...new Set(searchParams2.keys())].map(key => [key, searchParams2.getAll(key)])
)

获取指定数据

方法说明
searchParams.entries()返回一个iterator可以遍历所有键/值对的对象。
searchParams.get(key)获取指定搜索参数的第一个值
searchParams.getAll(key)获取指定搜索参数的所有值,返回是一个数组
searchParams.has(key)判断是否存在此搜索参数
searchParams.keys()返回一个iterator包含了键/值对的所有键名
searchParams.values()返回一个iterator包含了键/值对的所有值

示例改写

const response = await fetch(url, {
  method: 'POST',
  body: new URLSearchParams({ text })
})
const json = await response.json()

使用 URLSearchParams 作为 body,则 Content-Type 标头会自动设置为 application/x-www-form-urlencoded。

FormData

如果表单中包含文件怎么办?application/x-www-form-urlencoded 不支持文件,可以设置为 multipart/form-data 来支持。如果此时需要通过 ajax/fetch 发送请求,可以借助 FormData 进行封装数据。

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

示例:模拟上述 from 表达提交形式

const formData = new FormData()
formData.set('title', '你好')
formData.set('content', 'this post about multipart-form-data')
formData.set('logo', document.forms[1].logo.files[0])	// document.forms[1].logo => fileInputElement

构造函数支持通过 form 表单元素,自动将form中的表单值也包含进去,包括文件内容也会被编码之后包含进去。

new FormData(document.forms[0])

读取方式

示例:获取上述表单数据

for (const [key, value] of formData) {
  console.log(key, value)
}

其他方式暂时不支持,获取指定数据方式类似 **URLSearchParams **,且也提供了想对应的方法,可自行查阅

改写示例

const formData = new FormData();
formData.set('text', text);

const response = await fetch(url, {
  method: 'POST',
  body: formData
})
const json = await response.json()

使用 FormData 作为 body,则 Content-Type 标头会自动设置为 multipart/form-data。

FormData 转换为 URLSearchParams

form 表单想通过 application/x-www-form-urlencoded 发送。

  1. 通过上述示例,直接在 form 中增加 action

  2. 进行转换

    const formElement = document.querySelector('form')
    const formData = new FormData(formElement)
    const searchParams = new URLSearchParams(formData)
    
    fetch(url, {
      method: 'POST',
      body: searchParams,
    })
    

    该方式,文件类型会被丢失。

其他类型

Blobs

fetch(url, {
  method: 'POST',
  body: blob
})

Content-Type 标头会自动设置为 Blob.type

Strings

fetch(url, {
  method: 'POST',
  body: JSON.stringify({ hello: 'world' }),
  headers: { 'Content-Type': 'application/json' }
})

Buffers

fetch(url, {
  method: 'POST',
  body: new Uint8Array([]),
  headers: { 'Content-Type': 'image/png' }
})

总结

如果不包含文件,且带有查询参数,可以使用 **URLSearchParams **;如果包含文件,需要使用 FormData

以上是关于Form 表单数据编码解码--encodeURIComponentURLSearchParamsFormData的主要内容,如果未能解决你的问题,请参考以下文章

客户端编码与服务器解码全过程

form表单提交后结果乱码的解决方法

编码乱码问题

jQuery使用serialize()表单序列化时出现中文乱码问题的解决办法

form表单提交中文乱码(前台中文到JAVA后台乱码)问题及解决

玩转web之ajax---使用表单的serialize()方法中文乱码解决