- 前话
网络请求是前端和服务端配合的重要工具, 不是前端或服务端的特有, 所以要真正了解网络请求
必须手写前端和服务端, 才能真正理解一些东西
- 前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ajax</title>
</head>
<body>
账号: <input id="user" type="text" /> 密码: <input id="pwd" type="password" />
<button id="submit">提交</button>
<p id="msg"></p>
<script>
const user = document.querySelector('#user');
const pwd = document.querySelector('#pwd');
const btn = document.querySelector('#submit');
const msg = document.querySelector('#msg');
btn.addEventListener('click', () => {
// 表单请求原生node很难解析, 需要正则切割, 错误处理
// const form = new FormData();
// form.append('user', user.value);
// form.append('pwd', pwd.value);
// request(form);
const data = {
user: user.value,
pwd: pwd.value
};
request(JSON.stringify(data));
});
function request(data) {
const ajax = new XMLHttpRequest();
ajax.open('POST', 'http://127.0.0.1:3001/ajax', true);
ajax.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
ajax.send(data);
ajax.onreadystatechange = function (res) {
if (ajax.readyState === 4) {
if (ajax.response === '密码错误') {
msg.innerHTML = '密码错误';
} else {
const userInfo = JSON.parse(ajax.response);
msg.innerHTML = `登陆成功欢迎您${userInfo.user}, 本次登录返回的token是${userInfo.token}, 请保存在cookie, 用来做身份令牌`;
}
}
};
}
</script>
</body>
</html>
- 后端代码 (额…有些引入的模块没用到)
// const { http, fs, querystring, url } = require('./module');
const http = require('http');
const fs = require('fs');
const path = require('path');
const querystring = require('querystring');
const url = require('url');
const multiparty = require('multiparty');
const app = http.createServer((req, res) => {
if (req.url === '/ajax') {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE');
if (req.method === 'OPTIONS') {
res.writeHead(200, 'text/plain;charset=utf-8');
// 完成预检请求, 客户端才会携带请求体, 服务端才能收到, 所以必须同步调用res.end()
// 预检请求:
// 如果服务器同意处理请求,那么它会进行响应,此响应的状态码应该为 200,没有 body,具有 header
// 所以res.end('receive ajax OPTIONS', 'utf-8') 写法等价于 res.end() 你必须给予预检回应 但是却无法回应请求体
res.end('receive ajax OPTIONS', 'utf-8');
} else {
//预检通过, 正式发起请求
req.on('data', chunk => {
const reqData = JSON.parse(chunk.toString('utf-8'));
if (reqData.pwd !== '456') {
res.setHeader('Content-Type', 'text/plain;charset=utf-8');
res.writeHead(403);
res.end('密码错误');
} else {
reqData.token = '10086';
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify(reqData));
}
});
}
}
});
app.listen(3001);
- 你真正理解如何解决js网络请求模块引起的跨域吗 ?
跨域, 为javascript而生, 为了javascript更加强大, 也为了其不能肆意妄为
跨域从来不限制html, 所以有人用 表单+ifram 和 script 标签(JSONP)的方式解决跨域, 但是不得不说
他们已经被淘汰了. 旧时代的洪水猛兽本应该逐渐消退
- 从服务端和前端解决跨域, 让js网络请求模块也具备跨域的能力, 而不仅仅是html可以跨域
- 这里有一个极好的教程, 简洁有力, 教程
- 阅读教程, 配合手写服务端, 你能理解OPTIONS预检请求, 理解了预检请求也就理解了, 跨域如何保证所谓的安全性
- 用js网络模块(XMLHttpRequest或fetch)发起一个跨域请求并且该请求是非安全请求, 需要先发送一个预检请求, 预检请求是浏览器实现的规范,预检请求发生在“幕后”,它对 JavaScript 不可见, javascript不可见, 意味着预检请求并不是一个XHR/Fetch, 阅读教程想必你已经知道, 预检请求之后才会发送真正的XHR请求, 服务端不仅要处理预检请求, 还要处理XHR请求, 这里又有诸多细节, 可以参考代码加以理解
- 注: 本文词汇–> 服务端亦是指后端, 网络请求以http协议为基础