发送 POST 请求时常见 Content-Type 的设置以及参数传递

0. 前置须知

1. 你需要知道

  1. 此文是归来仍是制杖专用笔记,主打我88岁也能看懂;
  2. 此文只考虑请求,且 get 请求因语义,一般没有请求体,故不需要指定 Content-Type
  3. 对于请求,Content-Type 的作用是:告诉服务端我携带的 请求体容器 是什么格式;
  4. 对于响应,Content-Type 的作用是:告诉浏览器按什么格式解析,如返回一个静态页面,服务端需要指定 媒体类型为 text/html,如果指定 text/plain 则会错误的按照文本格式,渲染成文档标签;
  5. Content-Type: media type,charest,boundary;即:媒体类型,字符编码,边界;
  6. 此文只关注 post 请求的三个媒体类型,即:
    1. application/x-www-form-urlencoded
    2. multipart/form-data
    3. application/json
  7. 标头 Content-Length: number 注明请求体长度;
  8. 关于 HTML 的 charset:
    其实是编码而不是字符集,说明如何把实体内容的二进制码转换为特定字母表即对应字符集中的字符。
    如:charset=UTF-8,请求时是告诉应该按照 UTF-8 编码方式编码,在响应里是告诉浏览器要按照 UTF-8 编码方式解码,即把 二进制码 ==> Unicode字符集中的字符。

2. 备注

postman的四种请求方式,参考链接

关于第3点的示例,参考链接

1. 指定媒体类型为 text/html

2. 指定媒体类型为 text/plain

1. application/x-www-form-urlencoded

application/x-www-form-urlencoded 为 http 请求默认;
请求体在 Form Data 中;
数据被编码成键值对,如 username=admin&id=1

1. 表单构建

原生 form 表单,请求头的 Content-Type<form> 元素上的 enctype 属性指定;
不指定默认为 application/x-www-form-urlencoded
不需要文件上传时才使用这种媒体类型,因为使用此种媒体类型,文件也会被编码为 key-value 的形式,多个 key-value 之间用&分隔(注意 value 为 String),如: username=admin&id=1&file=test.png

<form action="http://127.0.0.1:8888/formdata" method="post">
  <input type="text" name="username" />
  <input type="text" name="id" />
  <input type="file" name="file" class="file" />
  <input type="submit" />
</form>


2. 手动构建,直接拼接

不想写form表单的情况~但模拟

<input type="text" name="username" id="username" />
<input type="text" name="id" id="id" />
<button class="submit">提交</button>

<script>
  const btn = document.querySelector('.submit');
  btn.addEventListener('click', () => {
	const username = document.getElementById('username').value;
	const id = document.getElementById('id').value;
	const http = new XMLHttpRequest();
	http.open('post', 'http://localhost:8888/formdata');
	http.setRequestHeader(
	  'Content-Type',
	  'application/x-www-form-urlencoded'
	);
	// 手动拼接!!!
	http.send(`username=${username}&id=${id}&obj=${JSON.stringify({ name: '1' })}`);
	http.onreadystatechange = () => {
	  if (http.readyState === 4 && http.status === 200) {
		console.log('数据返回成功!');
	  }
	};
  });
</script>


因为键值对的 value 要求为 String,如果有复杂结构,那就需要对复杂 value 先 JSON.stringify 一下

// 错误写法:
http.send(`username=${username}&id=${id}&obj=${{ name: '1' }}`);

错误写法:

2. multipart/form-data

Multipart 消息结构允许客户端在一次 HTTP 请求中发送多个部分数据,每部分数据用 boundary 分隔,且 Content-Type 可以不同。Multipart 并不是一种专一的数据类型,它有很多子类型,例如:multipart/mixed,multipart/form-data 等。Multipart 是一种常见的数据格式,常用于上传文件和发送包含多种数据类型的单个请求。正确地使用 Multipart 可以方便地实现多种数据的传输,提高数据传输效率和用户的使用体验,减少服务器的请求次数。
传输内容某部分数据可以有二进制类型,如:图片、mp3、文件;
请求体在 Form Data 中;
原生 form 指定 encType 为 'multipart/form-data',浏览器会自动生成一个 boundary,用于分割报文的请求头和请求体以及请求头内容各字段;
文件上传,File 对象通常来自于用户在 <input>元素上选择文件后返回的 FileList 对象!!!对象!对象!;
JS 中,<input class="file"/>选择文件后,FileList 对象可通过document.querySelector('#file').files 获取到,多选文件时,多个文件的属性名为 index;

0. 备注

关于第四点的多选的情况:

1. 表单构建

<form
  action="http://127.0.0.1:8888/formdata"
  method="post"
  enctype="multipart/form-data"
>
  <input type="text" name="username" id="username" />
  <input type="text" name="id" id="id" />
  <input type="file" name="file" class="file" />
  <input type="submit" />
</form>




2. 手动构建,涉及formData对象的使用

观察原生表单发请求的Content-Type,发现后面还有个自动生成的 boundary !
在发送请求时将请求头中 Content-Type 属性给写死为 multipart/form-data,浏览器无法自动给我们的报文添加 boundary,后端接收后,无法识别传递来的文件中的boundary,从而无法区分一个文件的内容从报文的哪个地方开始,又从报文的哪个地方结束,最终导致文件上传失败;
虽然我们需要 Content-Typemultipart/form-data,但是发送请求时不设置,浏览器识别参数类型为 formData,会自动添加!

这里只考虑了原生如何写,axios可参考,咱暂时没遇到过这种需求捏

<input type="text" name="username" id="username" /> 
<input type="text" name="id" id="id" />
<input type="file" name="file" class="file" />
<button class="submit">上传</button>

<script>
  const btn = document.querySelector('.submit');
  const formData = new FormData();
  btn.addEventListener('click', () => {
	const files = document.querySelector('.file');
	const username = document.getElementById('username').value;
	const id = document.getElementById('id').value;
	formData.append('username', username);
	formData.append('id', id);
	formData.append('file', files.files[0], files.files[0].name);
	// 注意:formData 打印为空,可通过此种方式查看~
	for (let val of formData.values()) {
	  console.log(val);
	}
	const http = new XMLHttpRequest();
	http.open('post', 'http://localhost:8888/formdata');
	// 不设置 content-type!!!
	// http.setRequestHeader('Content-Type', 'multipart/form-data');
	http.send(formData);
	http.onreadystatechange = () => {
	  if (http.readyState === 4 && http.status === 200) {
		console.log('数据返回成功!');
	  }
	};
  });
</script>

注意: formData.append(name,value[,filename)

  • 第一个参数对应表单的 name,即后端需要接收的参数名;
  • 第二个参数对应为表单值,即接收的value;
  • 第三个参数在第二个参数值为 blob 类型时,可选,为文件名。

3. application/json

请求体在 Request Payload 中;
数据为 JSON 字符串,原生 ajax 需手动序列化,axios 不需要~

<button class="submit">提交</button>
<script>
  const btn = document.querySelector('.submit');
  btn.addEventListener('click', () => {
	const params = {
	  name: 'ouo',
	  age: 18,
	};
	const http = new XMLHttpRequest();
	http.open('post', 'http://localhost:8888/json');
	http.send(JSON.stringify(params));
	http.setRequestHeader('Content-Type', 'application/json');
	http.onreadystatechange = () => {
	  if (http.readyState === 4 && http.status === 200) {
		console.log('数据返回成功!');
	  }
	};
  });
</script>

posted @ 2024-01-26 10:29  雨宮莲  阅读(623)  评论(0编辑  收藏  举报