1. 说明
app1是基于3001端口的服务器, app2是基于3002端口的服务器。
gitee地址:https://gitee.com/studyCodingEx/studys/
2. app1
2.0 app1.js
const express = require('express');
const path = require('path');
// 向其他服务器端请求数据的模块
const request = require('request');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.get('/server', (req, res) => {
console.log(req.session);
request('http://localhost:3002/serverIndex', (err, response, body) => {
res.send(body);
});
/*
此时向端口为3002的服务器发起get请求, 那么3002端口的服务器的结果最终会被传递到回调函数的body参数中;
最后再将body结果响应给客户端;
好比说: 客户端要访问3002端口(非同源)服务器的数据, 首先给自己的3001端口的服务器发起get请求,
然后再由3001端口的服务器 向 3002端口的服务器发起请求(服务器与服务器之间不存在同源限制的问题),
最后3002端口的服务器的响应到达3001端口的服务器,
最后由3001端口的服务器将数据响应给客户端;
*/
});
app.listen(3001, () => {
console.log('server1 is listening on 3001......');
});
2.1
Ajax请求限制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
Ajax请求限制:
Ajax只能向自己的服务器发送请求。
比如现在有一个A网站、有一个B网站,A网站中的HTML文件只能向A网站服务器中发送Ajax请求,
B网站中的HTML文件只能向B网站中发送Ajax请求,但是A网站是不能向B网站发送Ajax请求的,
同理,B网站也不能向A网站发送Ajax请求。
同源:
如果两个页面拥有相同的协议、域名和端口, 那么这两个页面就属于同一个源, 其中只要有一个不相同, 就是不同源!
同源政策:
同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。
最初的同源政策是指A网站在客户端设置的Cookie,B网站是不能访问的。
随着互联网的发展,同源政策也越来越严格,在不同源的情况下,
其中有一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错。
-->
</body>
</html>
2.2
00_测试非同源Ajax请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h2>这是服务器s1-public文件夹下的index.html文件</h2>
<script src="./js/ajax.js"></script>
<script type="text/javascript">
/*
向非同源的服务器发起 Ajax请求, 打开终端, 可以看到以下报错;
*/
/*
Access to XMLHttpRequest at 'http://localhost:3002/test?' from origin 'http://localhost:3001' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
*/
AJAX({
url: 'http://localhost:3002/test',
type: 'get',
success: function(result) {
console.log(result);
}
});
</script>
</body>
</html>
2.3
01_使用JSONP解决跨域1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
JSONP请求属于get请求;
-->
<!--
使用JSONP解决同源限制:
JSONP不属于Ajax请求, 但是它可以模拟Ajax请求;
使用步骤如下:
1. 将不同源的服务器端请求地址写在script标签的src属性中;
2. 服务器端响应数据必须是一个函数调用(就是客户端定义的函数,这里是fn(...)), 要发送给客户端的数据需要作为函数调用的参数即可;
res.send( fn({name: "ZhangSan"}) );
首先在客户端全局作用域下定义函数fn(), 在fn()函数内部对服务器端返回的数据进行处理;
function fn(data) { console.log(data); }
-->
</head>
<body>
<script src="./js/ajax.js"></script>
<script>
function fn(data) {
console.log(data);
}
</script>
<script src="http://localhost:3002/test"></script>
<!-- 相当于向服务器2的地址http://localhost:3002/test发起get请求 -->
<!-- 而后服务器2 res.send('fn({name: "ZhangSan"})'); -->
<!-- 也即相当于返回fn( {name: 'ZhangSan'} ), 这不就是函数调用吗! -->
</body>
</html>
2.4
02_使用JSONP解决跨域2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!--
优化:客户端将函数名字通过请求参数传递到服务器; (解决了服务器端返回的函数名字必须和客户端函数名字保持一致的情况)
-->
<button id="btn">点我发送请求</button>
<script>
function fn (data) {
console.log(data);
}
</script>
<script type="text/javascript">
// 获取按钮
var btn = document.getElementById('btn');
// 为按钮添加点击事件
btn.onclick = function () {
// 创建script标签
var _script = document.createElement('script');
// 设置src属性
_script.src = 'http://localhost:3002/better?callback=fn';
// 将script标签追加到页面中
document.body.appendChild(_script);
// 为script标签添加onload事件
_script.onload = function () {
// 将body中的script标签删除掉
document.body.removeChild(_script);
}
}
</script>
</body>
</html>
2.5
03_封装JSONP函数.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn1">点我发送请求(btn1)</button>
<button id="btn2">点我发送请求(btn2)</button>
<script type="text/javascript">
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
btn1.onclick = function () {
JSONP({
url: 'http://localhost:3002/better',
data: { /* 请求参数 */
name: 'good',
age: 30
},
success: function(data) {
console.log(123);
console.log(data);
}
});
}
btn2.onclick = function () {
JSONP({
url: 'http://localhost:3002/better',
success: function(data) {
console.log(456);
console.log(data);
}
});
}
function JSONP(options) {
var params = '';
for(let attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
} // &name=Zhang&age=30
console.log(options.data);
// 动态创建script标签
let _script = document.createElement('script');
let randomFunName = 'myJSONP' + Math.random().toString().replace('.', ''); // myJSONP0231421
// 挂载到window下, 使其成为全局函数
window[randomFunName] = options.success;
// 为script标签添加src属性
_script.src = options.url + '?callback=' + randomFunName + params;
document.body.appendChild(_script);
_script.onload = function () {
document.body.removeChild(_script);
}
/*
// 此种情况下将会出现window.fn函数覆盖问题;
// 解决方案:见上(随机函数名字);
// 配合app2中的 setTimeout(() => { res.send(result); }, 1000); 来进行演示此种现象的发生;
// 在1秒内进行2次Ajax请求, 然后在客户端查看结果; 发现确实覆盖了...;
var _script = document.createElement('script');
window.fn = options.success;
_script.src = options.url + '?callback=fn';
document.body.appendChild(_script);
_script.onload = function () {
document.body.removeChild(_script);
}
*/
}
</script>
</body>
</html>
2.6
04_使用JSONP获取腾讯天气信息.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用jsonp获取腾讯天气信息</title>
<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
<style type="text/css">
.container {
padding-top: 60px;
}
</style>
</head>
<body>
<div class="container">
<table class="table table-striped table-hover" align="center" id="box">
</table>
</div>
<script src="/js/jsonp.js"></script>
<script src="/js/template-web.js"></script>
<script type="text/html" id="tpl">
<tr>
<th>时间</th>
<th>温度</th>
<th>天气</th>
<th>风向</th>
<th>风力</th>
</tr>
{{each info}}
<tr>
<td>{{dateFormat($value.update_time)}}</td>
<td>{{$value.degree}}</td>
<td>{{$value.weather}}</td>
<td>{{$value.wind_direction}}</td>
<td>{{$value.wind_power}}</td>
</tr>
{{/each}}
</script>
<script>
var box = document.getElementById('box');
function dateFormat(date) {
// "20211004210000"
var year = date.substr(0, 4);
var month = date.substr(4, 2);
var day = date.substr(6, 2);
var hour = date.substr(8, 2);
var minute = date.substr(10, 2);
var seconds = date.substr(12, 2);
return year + '年' + month + '月' + day + '日' + hour + '时' + minute + '分' + seconds + '秒';
}
// 向模板中导入全局变量
template.defaults.imports.dateFormat = dateFormat;
//
JSONP({
url: 'https://wis.qq.com/weather/common',
data: {
source: 'pc',
weather_type: 'forecast_1h',
// weather_type: 'forecast_1h|forecast_24h',
province: '黑龙江省',
city: '哈尔滨市'
},
success: function (data) {
console.log(data);
var html = template('tpl', {
info: data.data.forecast_1h
});
box.innerHTML = html;
}
});
</script>
</body>
</html>
2.7
05_CORS跨域资源共享.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
CORS: 跨域资源共享(非同源共享), 它允许浏览器向跨域服务器发送Ajax请求; 克服了Ajax只能同源的限制;
实现方法非常简单:只需在服务器端设置 允许哪些客户端可以访问服务器和允许客户端使用哪些请求方式访问服务器即可;
客户端代码无需任何改变;
-->
</head>
<body>
<button id="btn">点我发送请求</button>
<script src="/js/ajax.js"></script>
<script>
var btn = document.getElementById('btn');
btn.onclick = function() {
AJAX({
url: 'http://localhost:3002/cross',
type: 'get',
success: function(result) {
console.log(result);
}
})
};
</script>
</body>
</html>
2.8
06_访问非同源数据的服务器解决方案.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
访问非同源端数据, 服务器端解决方案:
同源政策是浏览器给予Ajax技术的限制, 服务器端是不存在同源政策的限制;
请求 请求
``````> ``````>
A浏览器端 A服务器端 B服务器端
<``````<``````
响应 响应
此时就绕过了浏览器的同源政策限制;
-->
<!--
点击按钮, 客户端向自己的服务器app1发送get请求;
当服务器app1接收到请求之后执行:request('http://localhost:3002/serverIndex', ... ...);
表示紧接着向app2服务器发送get请求(服务器与服务器之间不存在同源限制),
-->
<!--
好比说: 客户端要访问3002端口(非同源)服务器的数据,
首先自己的3001端口的服务器发起Ajax - get请求,
然后再由3001端口的服务器 向 3002端口的服务器发起请求,
最后3002端口的服务器的响应到达3001端口的服务器,
最后再由3001端口的服务器将数据响应给客户端;
-->
</head>
<body>
<button id="btn">点我发送请求</button>
<script src="/js/ajax.js"></script>
<script>
var btn = document.getElementById('btn');
btn.onclick = function() {
AJAX({
url: 'http://localhost:3001/server',
type: 'get',
success: function(data) {
console.log('##################data is:> ' + data);
}
})
};
</script>
</body>
</html>
2.9
07_实现跨域登录功能.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>实现跨域登录功能</title>
<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
<style type="text/css">
.container {
padding-top: 60px;
}
</style>
<!--
withCredentials属性:
在使用Ajax技术发送跨域请求时, 默认情况下不会在请求中携带cookie信息;
指定在涉及到跨域请求时, 是否携带cookie信息, 默认值为false;
为了使得携带cookie信息, 需同时在客户端和服务端进行相应的设置, 二者缺一不可;
a. xhr.withCredentials = true; (client)
b.res.header('Access-Control-Allow-Credentials', true); (server)
-->
</head>
<body>
<div class="container">
<form id="loginForm">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" class="form-control" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" class="form-control" placeholder="请输入用密码">
</div>
<input type="button" class="btn btn-default" value="登录" id="loginBtn">
<input type="button" class="btn btn-default" value="检测用户登录状态" id="checkLogin">
</form>
</div>
<script type="text/javascript">
var loginBtn = document.getElementById('loginBtn');
var checkLogin = document.getElementById('checkLogin');
var loginForm = document.getElementById('loginForm');
// 登录按钮
loginBtn.onclick = function () {
var formData = new FormData(loginForm);
var xhr = new XMLHttpRequest();
xhr.open('post', 'http://localhost:3002/login');
// 当发送跨域请求时,携带cookie信息
xhr.withCredentials = true;
xhr.send(formData);
xhr.onload = function () {
console.log(xhr.responseText);
}
}
// 状态监测按钮
checkLogin.onclick = function () {
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:3002/checkLogin');
xhr.withCredentials = true;
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText);
}
}
</script>
</body>
</html>
3. app2
app2.js
const express = require('express');
const path = require('path');
const formidable = require('formidable');
// 引入session模块
var session = require('express-session');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
// 拦截所有请求
// app.use((req, res, next) => {
// // 允许哪些客户端可以访问服务器(*表所有客户端)
// res.header('Access-Control-Allow-Origin', '*');
// // 允许客户端使用哪些请求方法访问服务器
// res.header('Access-Control-Allow-Methods', 'get, post');
// // 允许客户端发送跨域请求时携带cookie信息
// res.header('Access-Control-Allow-Credentials');
// next();
// });
// 实现session功能
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
/* [0.] 通过jsonp实现非同源访问; */
app.get('/test', (req, res) => {
res.send('fn({name: "ZhangSan"})');
});
/* [1.] jsonp实现非同源访问
*/
app.get('/better', (req, res) => {
// way1
// 接收客户端传递过来的函数名
/*const funName = req.query.callback;
// 将函数名对应的函数调用代码返回给客户端
const data = JSON.stringify({name: "ZhangSan"});
const result = funName + '('+ data +')';
console.log('result is:> ' + result);
setTimeout(() => {
res.send(result);
}, 1000); */
// way2
res.jsonp({
name: 'lisi',
age: 20
}); // jsonp()更为方便
});
/* [2.] CORS实现非同源访问 */
app.get('/cross', (req, res) => {
// // 允许哪些客户端可以访问服务器(*表所有客户端)
res.header('Access-Control-Allow-Origin', '*');
// // 允许客户端使用哪些请求方法访问服务器
res.header('Access-Control-Allow-Methods', 'get, post');
res.send('88888888888888888888888888 -> ok!');
});
/* [3.] 见05_访问... */
app.get('/serverIndex', (req, res) => {
res.send('***************************serverIndex...888');
});
/* [4.] 见06_实现... */
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3001')
res.header('Access-Control-Allow-Methods', 'get, post')
// 允许客户端发送跨域请求时携带cookie信息
res.header('Access-Control-Allow-Credentials', true);
next();
});
app.post('/login', (req, res) => {
// 创建表单解析对象
var form = formidable.IncomingForm();
// 解析表单
form.parse(req, (err, fields, file) => {
const {
username,
password
} = fields;
if (username == 'root' && password == '123456') {
// 设置session
req.session.isLogin = true;
res.send({
message: '登录成功'
});
} else {
res.send({
message: '登录失败, 用户名或密码错误'
});
}
})
});
app.get('/checkLogin', (req, res) => {
// 判断用户是否处于登录状态
if (req.session.isLogin) {
res.send({
message: '处于登录状态'
})
} else {
res.send({
message: '处于未登录状态'
})
}
});
app.listen(3002, () => {
console.log('server2 is listening on 3002......');
});