文章目錄
同源政策
Ajax請求限制
Ajax 只能向自己的服務器發送請求。比如現在有一個A網站、有一個B網站,A網站中的 HTML 文件只能向A網站服務器中發送 Ajax請求,B網站中的 HTML 文件只能向 B 網站中發送 Ajax 請求,但是 A 網站是不能向 B 網站發送 Ajax請求的,同理,B網站也不能向 A 網站發送 Ajax請求。
什麼是同源
如果兩個頁面擁有相同的協議、域名和端口,那麼這兩個頁面就屬於同一個源,其中只要有一個不相同,就是不同源。
http://www.example.com/dir/page.html
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
https://www.example.com/dir/page.html:不同源(協議不同)
同源政策的目的
-
同源政策是爲了保證用戶信息的安全,防止惡意的網站竊取數據。最初的同源政策是指 A 網站在客戶端設置的Cookie,B網站是不能訪問的。
-
隨着互聯網的發展,同源政策也越來越嚴格,在不同源的情況下,其中一項規定就是
無法向非同源地址發送Ajax 請求
,如果請求,瀏覽器就會錯。
使用 JSONP 解決同源限制問題
jsonp 是 json with padding 的縮寫,它不屬於 Ajax 請求,但它可以模擬 Ajax 請求,是JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。
- 將不同源的服務器端請求地址寫在 script 標籤的 src 屬性中。
<script src="http://localhost:3001/test"></script>
- 服務器端響應數據必須是一個函數的調用,真正要發送給客戶端的數據需要作爲函數調用的參數。
app.get('/test', (req, res) => {
const result = 'fn({name: "張三",age:20})';
//fn是調用客戶端的函數名
res.send(result);
});
- 在客戶端
全局作用域
下定義函數 fn。
function fn (data) { }
- 在 fn 函數內部對服務器端返回的數據進行處理。
function fn (data) { console.log(data); }
//輸出一個對象 服務端返回的{name: "張三", age: "20"}
JSONP 代碼優化
-
客戶端需要將函數名稱傳遞到服務器端。
-
將 script 請求的發送變成動態請求。
客戶端代碼
// 爲按鈕添加點擊事件
btn.onclick = function () {
// 創建script標籤
var script = document.createElement('script');
// 設置src屬性 把調用的函數名發過去
script.src = 'http://localhost:3001/better?callback=fn2';
// 將script標籤追加到頁面中
document.body.appendChild(script);
// 爲script標籤添加onload事件
//當請求傳送過去後就觸發
script.onload = function () {
// 將body中的script標籤刪除掉
document.body.removeChild(script);
}
}
-
封裝 jsonp 函數,方便請求發送。
客戶端代碼
function jsonp (options){
// 動態創建 script 標籤
var script = document.createElement('script')
// 爲script標籤 添加src屬性
script.src = options.url;
// 將script 標籤追加到頁面上
document.body.appendChild(script);
// 爲script 標籤添加onload事件
script.onload = function () {
document.body.removeChild(script)
}
}
服務器端代碼優化之 res.jsonp 方法
app.get('/better', (req, res) => {
// 接收客戶端傳遞過來的函數的名稱
//const fnName = req.query.callback;
// 將函數名稱對應的函數調用代碼返回給客戶端
//const data = JSON.stringify({name: "張三"});
//const result = fnName + '('+ data +')';
// setTimeout(() => { // res.send(result);
// }, 1000)
res.jsonp({name: 'lisi', age: 20});
//express框架裏面的方法 客戶端success函數接受數據
});
完整代碼
客戶端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsonp應用</title>
</head>
<body>
<button id="btn">點我發送請求</button>
<script type="text/javascript">
// 獲取按鈕
var btn = document.getElementById('btn');
// 爲按鈕添加點擊事件
btn.onclick = function () {
jp({
// 請求地址
url: 'http://localhost:3001/better',
data: {
name: 'lisi',
age: 30
},
success: function (data) {
console.log(123)
console.log(data)
//輸出服務端返回的{name: 'lisi', age: 20}
}
})
}
//jsonp的封裝函數
function jp (options) {
// 動態創建script標籤
var script = document.createElement('script');
// 拼接字符串的變量
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}
// myJsonp0124741 生成一個一個隨機的函數名
var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
// 它已經不是一個全局函數了
// 將它變成全局函數
window[fnName] = options.success;
// 爲script標籤添加src屬性 添加函數名 傳遞參數
script.src = options.url + '?callback=' + fnName + params;
// 將script標籤追加到頁面中
document.body.appendChild(script);
// 爲script標籤添加onload事件 事件完成後刪除script標籤,避免請求發送多次產生多個標籤
script.onload = function () {
document.body.removeChild(script);
}
}
</script>
</body>
</html>
服務端代碼
// 引入express框架
const express = require('express');
// 創建web服務器
const app = express();
app.get('/better', (req, res) => {
res.jsonp({name: 'lisi', age: 20});
//這裏的jsonp是服務器方法
});
案例——使用jsonp獲取騰訊天氣信息
<!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>
<!-- 引入封裝的jsonp文件 -->
<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>
<!-- dataFormat 引入的用於格式化數據 -->
<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>
// 獲取table標籤
var box = document.getElementById('box');
function dateFormat(date) {
// substr 截取字符串 開始位置 個數
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', //未來24小時
// weather_type: 'forecast_1h|forecast_24h', //未來24小時|未來7天(未來7天接口字段與未來24小時不同,可自行查看)
province: '浙江省',
city: '杭州市'
},
success: function (data) {
console.log(data)
// 把 數據導入 模板
var html = template('tpl', {info: data.data.forecast_1h});
// 把 模板放到 頁面
box.innerHTML = html;
}
})
</script>
</body>
</html>
返回數據
頁面展示
CORS 跨域資源共享
CORS:全稱爲 Cross-originresource sharing,即跨域資源共享,它允許瀏覽器向跨域服務器發送 Ajax 請求,克服了 Ajax 只能同源使用的限制。
Node 服務器端設置響應頭示例代碼:
// 攔截所有請求
app.use((req, res, next) => {
//設置允許跨域請求的ip地址
// * 代表允許所有的客戶端訪問我
res.header('Access‐Control‐Allow‐Origin', '*');
//設置可以跨域的請求方式
res.header('Access‐Control‐Allow‐Methods', 'GET, POST');
//執行權移交下一個中間件
next();
})
demo
html頁面 所在服務器監聽端口爲3000 訪問端口爲3001
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn">點我發送請求</button>
<!-- ajax封裝文件-->
<script src="/js/ajax.js"></script>
<script>
// 獲取按鈕
var btn = document.getElementById('btn');
// 爲按鈕添加點擊事件
btn.onclick = function () {
ajax({
type: 'get',
url: 'http://localhost:3001/cross',
success: function (data) {
console.log(data)
}
})
};
</script>
</body>
</html>
監聽的3001端口的服務器
// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 創建web服務器
const app = express();
// 攔截所有請求
app.use((req, res, next) => {
// 1.允許哪些客戶端訪問我
// * 代表允許所有的客戶端訪問我
res.header('Access-Control-Allow-Origin', '*')
// 2.允許客戶端使用哪些請求方法訪問我
res.header('Access-Control-Allow-Methods', 'get,post')
next();
});
app.get('/cross', (req, res) => {
res.send('你得到了3001端口下返回的數據')
});
訪問非同源數據 服務器端解決方案
demo
html頁面 在監聽3000端口的服務器下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn">點我發送請求</button>
<!-- ajax封裝文件 -->
<script src="/js/ajax.js"></script>
<script>
// 獲取按鈕
var btn = document.getElementById('btn');
// 爲按鈕添加點擊事件
btn.onclick = function () {
ajax({
type: 'get',
url: 'http://localhost:3000/server',
success: function (data) {
console.log(data);
}
})
};
</script>
</body>
</html>
監聽3000端口的服務器
// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 向其他服務器端請求數據的模塊
const request = require('request');
// 創建web服務器
const app = express();
// 靜態資源訪問服務功能
app.use(express.static(path.join(__dirname, 'public')));
app.get('/server', (req, res) => {
request('http://localhost:3001/cross', (err, response, body) => {
res.send(body);
})
});
// 監聽端口
app.listen(3000);
// 控制檯提示輸出
console.log('服務器啓動成功');
監聽3001端口的服務器
// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 創建web服務器
const app = express();
// 攔截所有請求
app.use((req, res, next) => {
// 1.允許哪些客戶端訪問我
// * 代表允許所有的客戶端訪問我
res.header('Access-Control-Allow-Origin', '*')
// 2.允許客戶端使用哪些請求方法訪問我
res.header('Access-Control-Allow-Methods', 'get,post')
next();
});
app.get('/cross', (req, res) => {
res.send('你得到了3001端口下返回的數據')
});
案例——實現跨域登錄功能
cookie 複習
withCredentials屬性
- 在使用Ajax技術發送跨域請求時,默認情況下不會在請求中攜帶cookie信息。
- withCredentials:指定在涉及到跨域請求時,是否攜帶cookie信息,默認值爲false
- Access-Control-Allow-Credentials:true 允許客戶端發送請求時攜帶cookie
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>實現跨域功能</title>
<!-- 引入bootstrap框架 -->
<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">
<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 () {
// 將html表單轉換爲formData表單對象
var formData = new FormData(loginForm);
// 創建ajax對象
var xhr = new XMLHttpRequest();
// 對ajax對象進行配置
xhr.open('post', 'http://localhost:3001/login');
// 當發送跨域請求時,攜帶cookie信息
xhr.withCredentials = true;
// 發送請求並傳遞請求參數
xhr.send(formData);
// 監聽服務器端給予的響應內容
xhr.onload = function () {
console.log(xhr.responseText);
}
}
// 當檢測用戶狀態按鈕被點擊時
checkLogin.onclick = function () {
// 創建ajax對象
var xhr = new XMLHttpRequest();
// 對ajax對象進行配置
xhr.open('get', 'http://localhost:3001/checkLogin');
// 當發送跨域請求時,攜帶cookie信息
xhr.withCredentials = true;
// 發送請求並傳遞請求參數
xhr.send();
// 監聽服務器端給予的響應內容
xhr.onload = function () {
console.log(xhr.responseText);
}
}
</script>
</body>
</html>
跨域請求的服務器
// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 接收post請求參數
const formidable = require('formidable');
// 實現session功能
var session = require('express-session');
// 創建web服務器
const app = express();
// 實現session功能
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
// 靜態資源訪問服務功能
app.use(express.static(path.join(__dirname, 'public')));
// 攔截所有請求
app.use((req, res, next) => {
// 1.允許哪些客戶端訪問我
// * 代表允許所有的客戶端訪問我
// 注意:如果跨域請求中涉及到cookie信息傳遞,值不可以爲*號 比如是具體的域名信息
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
// 2.允許客戶端使用哪些請求方法訪問我
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();
// 解析表單
req.session.isLogin = false;
// 普通數據 文件數據
form.parse(req, (err, fields, file) => {
// 接收客戶端傳遞過來的用戶名和密碼 表單的name屬性
const { username, password } = fields;
// 用戶名密碼比對
if (username == 'oldsport' && 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(3001);
// 控制檯提示輸出
console.log('服務器啓動成功');