搭建Web服务环境

之前讲过 Express 快速启动Web服务,这里把代码再重写一下;

需要复习的同学请看:

​AJAX(笔记03) - 原生AJAX - Node.js 和 Express 的简介、安装​

​AJAX(笔记04) - 原生AJAX - GET请求​

​Express 简介、安装、使用和案例​


新建 server.js 服务文件:这次多写一个 post 响应的路由:

const express = require('express')
const app = express()
// post 路由
app.post('/server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.send('Hello Ajax post!')
})
// get 路由 参考上一篇,本篇不需要 get 路由
app.get('/server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.send('Hello Ajax!')
})
app.listen('8000', () => {
console.log('Web服务已经启动,端口8000监听中... ...');
})

为了区分 get 路由的响应结果,在post 里修改一下响应体;

启动服务:需在 server.js 目录执行 node 命令;

> node server.js

测试服务:浏览器只能测试get请求,这里使用 apifox 来测试路由;

原生AJAX(笔记05) - POST请求_POST请求

提示:apifox 和 postman 在服务测试这块功能一样;

原生AJAX(笔记05) - POST请求_AJAX_02

可以正常显示响应体;


创建POST请求

需求:鼠标经过 div 时,向服务端发送 post 请求,并把响应体显示在 div 中;

新建一个 ajax_post.html页面,再写点结构样式:

<style>
#result {
width: 200px;
height: 100px;
border: 1px solid #385;
}
</style>
<div id="result">

</div>

看下效果:

原生AJAX(笔记05) - POST请求_AJAX_03

对,就是这么一个框,鼠标经过时发送POST请求,并把响应体显示在框里;

接下来写 js 逻辑:

// 获取对象
let result = document.getElementById('result')
// 绑定事件
result.addEventListener('mouseover',function(){
// 1. 创建对象
const xhr = new XMLHttpRequest
// 2. 初始化 设置类型与URL
xhr.open('POST', 'http://127.0.0.1:8000/server')
// 3. 发送请求
xhr.send()
// 4. 事件绑定
xhr.onreadystatechange = function () {
// 判断
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
// 返回结果
result.innerHTML = xhr.response
}
}
}
})

提示1:拿到DOM元素,创建监听事件,设置AJAX并返回结果;

看下效果:

原生AJAX(笔记05) - POST请求_POST请求_04

提示:鼠标经过,触发响应结果;控制台也输出结果;

network 查看下 post 请求:

请求报文:请求行,请求头

原生AJAX(笔记05) - POST请求_POST请求_05

提示:请求报文(红框),请求行(黄框),请求头键值对(蓝框)

响应报文:响应行,响应头

原生AJAX(笔记05) - POST请求_POST请求_06

提示:响应报文(红框),响应行(黄框),响应头键值对(蓝框),跨域设置(绿框);

响应报文:响应体

原生AJAX(笔记05) - POST请求_POST请求_07


POST发送参数

Post请求在 ​xhr.send()​ 方法中发送参数; 

参数的格式非常灵活,甚至可以任意写,但需要对应的服务来处理相应的数据;所以列出两种常用格式; 

格式一:

xhr.send('a=1&b=2&c=3')

看下结果:

原生AJAX(笔记05) - POST请求_POST请求_08

格式二:

xhr.send('a:1&b:2&c:3')

原生AJAX(笔记05) - POST请求_POST请求_09


POST设置请求头

上面说明了 Post 设置请求行和请求体的方法,接下来说明设置请求头信息;

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.setRequestHeader('name','Jacky')

改下服务端的代码:

const express = require('express')
const app = express()
// all 路由
app.all('/server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Access-Control-Allow-Headers', '*')
response.setHeader('Access-Control-Max-Age', '600')
response.send('Hello Ajax post!')
})
// get 路由
app.get('/server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.send('Hello Ajax!')
})
app.listen('8000', () => {
console.log('Web服务已经启动,端口8000监听中... ...');
})

提示1:后端添加了两行响应头代码;

Access-Control-Allow-Headers

是允许接收自定义头信息,“*” 代表任意;

Access-Control-Max-Age

缓存响应头信息的时长,600秒,即10分钟;

提示2:路由从之前的 post 修改为了 all ;

意思是说,可以接收所有类型的请求方式,包括 POST、GET、OPTIONS等;


几个重要问题

客户端向服务端发送请求过程中,会遇到使用什么请求方法、如何设置请求头、什么是浏览器同源策略、什么是跨域、什么是CORS机制、什么是OPTIONS预检请求等和HTTP协议相关的问题。


1)浏览器同源策略

如果两个 URL 的 协议域名 和 端口 都相同时,我们就称这两个 URL 同源。

同源策略是浏览器出于安全考虑的一种策略,会阻止不同源间的JS脚本交互,目的是保护用户信息安全,防止恶意的网站窃取、身份伪造等隐患。

本例的请求地址是:

http://127.0.0.1:5500/ajax_post.html

服务端的地址是:

http://127.0.0.1:8000/server

这两个URL的协议和域名(IP)相同,但是端口不一样,就是说两个地址并不同源,按照同源策略的规范是不能进行交互的。

这就引出跨域的问题。

2)跨域和CORS机制

我们把两个不同源的资源之间非要进行交互称为跨域(或跨源);

跨域本质就是绕过同源策略的严格限制,安全与实用往往有时候会有一定的矛盾性,开发人员更注重的是功能的开发使用。

例如有时候同二级域名下的不同三级域名需要进行一些信息数据传输时,共享一些资源时,同源策略将其限制,但是又要实现该功能,此时就诞生了一些跨越请求的技术。

为了解决这个问题,浏览器给出了 CORS机制,即跨域资源共享的概念,可以通过正确设置 CORS 头信息的方式允许服务端响应客户端的请求;

CORS 由一系列 HTTP 头组成,这些 HTTP 头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。同源安全策略默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。

response.setHeader('Access-Control-Allow-Origin', '*')

服务端代码中添加这行 CORS 头信息,就等于允许接收不同源的请求;

但是这还不够,同样是出于完全考虑,当在跨域的情形下使用了自定义头信息的请求时,还会触发 OPTIONS 请求。

3)OPTIONS预检请求

OPTIONS请求是用于请求服务器对于某些接口等资源的支持情况的,包括各种请求方法、头部的支持情况,仅作查询使用。

他的作用可以理解为安检,因为客户端发起的请求已经触碰到安全的红线,在正式请求发起前,需要核对一下服务器是否允许这次请求的各种条件,例如:是否支持 OPTIONS请求方法、是否支持自定义头信息等;

response.setHeader('Access-Control-Allow-Headers', '*')

提示:本例使用了这条 CORS头信息来确认可以接收客户端的自定义头信息;

同时,修改了路由为all:

app.all('/server', (request, response) => {
// ...
})

提示:在 express 框架里修改路由为​ app.all ​的本意是接收所有请求方法,其中也包括 OPTIONS请求;这样就能即满足跨域又满足自定义头信息的需求了。

新的问题是,每次请求都要先来一次 OPTIONS请求,太麻烦了,影响性能。所以又加了一条 CORS头信息:

response.setHeader('Access-Control-Max-Age', '600')

提示:缓存这次 OPTIONS请求的响应结果,有效期10分钟。

在有效期内的所有 POST请求就只需经过一次 OPTIONS预检请求就可以,从而提高性能。这也叫 OPTIONS请求的优化。

4)HTTP信息头的组成

HTTP的头域包括通用头请求头响应头实体头四个部分;

每个头域由一个域名,冒号(:)和域值三部分组成。如:​Connection: keep-alive

通用头部:是客户端和服务器都可以使用的头部,可以在客户端、服务器和其他应用程序之间提供一些通用功能。

比如:Date头部。

Date: Sun, 18 Dec 2022 04:19:05 GMT

请头头部:是请求报文特有的,它们为服务器提供了一些额外信息。

比如:Accept头部,客户端希望接收什么类型的数据。

Accept: */*

响应头部:便于给客户端提供信息。

比如:Server头部,客户端在与哪种类型的服务器交互。

Server:Apache/2.0.61 (Unix)

实体头部:指的是用于应对实体主体部分的头部。

比如:Content-Type头部,可以用实体头部来说明实体主体部分的数据类型。

Content-Type: application/json;charset:utf-8;


5)为什么要设置请求头

设置了请求头,可以影响服务器响应的数据。

比如:把身份检验的信息放在头信息中,传递给服务器,由服务器对参数做提取,对用户的身份做校验。 

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.setRequestHeader('name','Jacky')

注意:设置预定义和自定义请求头信息必须要写在 ​xhr.open()​​ 方法和 ​xhr.send()​ 方法的语句中间,如果该方法多次调用,设定同一个字段,则每一次调用的值会被合并成一个单一的值发送。

比如:Content-Type :在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。告诉服务端如何处理请求的数据,告诉客户端(浏览器)如何解析响应的数据,比如显示图片,解析并展示html等; 

格式:

Content-Type:type/subtype ;parameter

type:主类型,如text,如果是*号代表所有;

subtype:子类型,如html,如果是*号代表所有,用“/”与主类型隔开;

parameter:可选参数,如charset,boundary等。

Content-type 类型(有几百个):

text/plain  : 纯文本格式

application/x-www-form-urlencoded : 表单默认提交数据格式

multipart/form-data : 表单上传文件格式


application/json: JSON数据格式


扩展阅读:

​HTTP 头信息解读​

​HTTP访问控制(CORS)、同源策略、跨域和预检请求等问题​

​了解更多 Content-Type​​