- 跨域资源共享CORS(Cross-Origin Resource Sharing)------xhr 需要服务端返回带有
Access-Control-Allow-Origin
的响应头 - 图像Ping------动态生成img标签
- JSONP------动态生成script标签
- Comet
['kɒmɪt]
------长链接 http 流 - 服务器发送事件------EventSource 对象
- Web Socket------WebSocket 对象
- script脚本跨域请求------Error事件读取报错信息
- script脚本跨域预加载------Error事件读取报错信息
cd server1
npm install
node ./bin/www
//new tab
cd server2
npm install
node ./bin/www
-
请求地址: http://localhost:3000/
-
server1服务器(localhost:3000) CORS.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>CORS(Cross Origin Resource Sharing)</h1>
<p></p>
<script>
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get", "http://localhost:4000/CORS");
if (request){
request.onload = function(){
if (request.readyState == 4){
//console.log(request.status, request.responseText);
document.getElementsByTagName("p")[0].innerHTML = "request.status: " + request.status + ", request.responseText: " + request.responseText;
}
};
request.send();
}
</script>
</body>
</html>
- server2服务器(localhost:4000) /CORS controller
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
if(req.hostname == "localhost"){
res.set('Access-Control-Allow-Origin',"http://localhost:3000");
res.send({cnt: "success"});
}
});
module.exports = router;
-
动态创建图像经常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的。
-
server1服务器(localhost:3000) imgPing.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>image Ping</h1>
<script>
var img = new Image();
img.onload = img.onerror = function(){
console.log("Done!");
}
img.src = "http://localhost:4000/imgPing?name=imgPing";
</script>
</body>
</html>
-
这里创建了一个Image的实例,然后将onload和onerror事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知。请求从设置src属性那一刻开始,而这个例子在请求中发送了一个name参数。
-
图像Ping最常用于跟踪用户点击页面或动态广告曝光次数。图像Ping有两个主要的缺点:
- 只能发送GET请求;
- 无法访问服务器的响应文本。
-
因此, 图像Ping只能用于浏览器与服务器间的单向通信。
-
server2 /imgPing controller
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
console.log(req.query.name);
res.send("got it.");
});
module.exports = router;
-
JSONP是JSON with padding (填充式JSON或参数式JSON) 的简写,是应用JSON的一种新方法。 被包含在函数调用中的JSON。
-
JSONP之所以在开发人员中极为流行, 主要原因是它非常简单易用。 与图像Ping相比, 它的优点在于能够直接访问响应文本,支持在浏览器与服务器之间的双向通信。
-
不过,JSONP也有两点不足:
- 首先,JSONP是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码, 而此时除了完全弃用JSONP调用之外, 没有办法追究。
- 其次,要确定JSONP请求是否失败并不容易。虽然HTML5给
<script>
元素新增了一个onerror事件处理程序, 但目前还没有得到任何浏览器的支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。但就算这样也不能尽如人意, 毕竟不是每个用户上网的速度和带宽都一样。
-
server1 中的 jsonp.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>JSONP</h1>
<h3></h3>
<script>
function handleResponse(response){
document.getElementsByTagName("h3")[0].innerHTML = "You're at IP address " + response.ip + ", which is in " +
response.city + ", " + response.region_name;
}
var script = document.createElement("script");
script.src = "http://localhost:4000/jsonp?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
</script>
</body>
</html>
- server2 /jsonp controller
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
console.log(req.query.callback);
res.send(req.query.callback + "({ip:'192.168.68.144',city:'hz',region_name:'zj'})");
});
module.exports = router;
-
Comet是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
-
有两种实现Comet的方式:长轮询和流。
-
页面发起一个到服务器的请求, 然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后, 浏览器关闭连接, 随即又发起一个到服务器的新请求。这一过程在页面 打开期间一直持续不断。
长连接、长轮询一般应用与WebIM、ChatRoom和一些需要及时交互的网站应用中。其真实案例有:WebQQ、Hi网页版、Facebook IM等。
-
longPolling.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Long Polling</h1>
<ul>
</ul>
<script>
var count = 0;
function LongPolling(){
var xhr = new XMLHttpRequest();
var url = 'http://localhost:4000/longPolling';
xhr.open('get', url, true); // async
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
console.log(xhr.status, xhr.responseText);
if(xhr.status == 200){
var li = document.createElement("li");
li.innerHTML = xhr.responseText;
document.getElementsByTagName("ul")[0].appendChild(li);
}
count++;
if(count < 5){
LongPolling();
}
}
}
xhr.send();
}
LongPolling();
</script>
</body>
</html>
- server2 /longPolling controller
var express = require('express');
var router = express.Router();
var count = 0;
/* GET home page. */
router.get('/', function(req, res, next) {
if(req.hostname == "localhost"){
setTimeout(function(){
count++;
res.set('Access-Control-Allow-Origin',"http://localhost:3000");
res.send("access success: " + count);
}, 5000);
}
});
module.exports = router;
-
所有服务器端语言都支持打印到输出缓存,然后刷新的功能。这正是实现HTTP流的关键所在。
-
在浏览器中,通过侦听readyStateChange事件及检测 readyState 的值是否为 3, 就可以利用XHR对象实现HTTP流。
-
httpStream.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>HTTP Stream</h1>
<ul>
</ul>
<script>
function createStreamingClient(url, progress, finished){
var xhr = new XMLHttpRequest();
var received = 0;
xhr.open("get", url, true);
xhr.onreadystatechange = function(){
var result;
if (xhr.readyState == 3){
//只取得最新数据并调整计数器
result = xhr.responseText.substring(received);
received += result.length;
//调用progress回调函数
progress(result);
} else if (xhr.readyState == 4){
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("http://localhost:4000/httpStreaming",function(data){
var li = document.createElement("li");
li.innerHTML = "Received: " + data;
document.getElementsByTagName("ul")[0].appendChild(li);
}, function(data){
var li = document.createElement("li");
li.innerHTML = data;
document.getElementsByTagName("ul")[0].appendChild(li);
});
</script>
</body>
</html>
- server2 /httpStreaming controller
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
if(req.hostname == "localhost"){
res.set('Access-Control-Allow-Origin',"http://localhost:3000");
res.set('Content-Type','text/html'); // html格式 可以边下载边解析
//res.send({cnt: "success"});
var count = 0;
function resWrite(){
res.write("server2 data: " + count + "\n");
count ++;
if(count <5){
delay(5000, resWrite);
}else {
resEnd();
}
}
function resEnd(){
res.end("server2 end");
}
delay(5000, resWrite);
}
});
function delay(time, ResWrite){
setTimeout(function(){
ResWrite();
}, time);
}
module.exports = router;
-
SSE(Server-Sent Events, 服务器发送事件) 是围绕只读Comet交互推出的API或者模式。SSE API用于创建到服务器的单向连接, 服务器通过这个连接可以发送任意数量的数据。 服务器响应的MIME类型必须是 text/event-stream, 而且是浏览器中的 Javascript API能解析格式输出。
-
SSE支持短轮询,长轮询和HTTP流, 而且能在断开连接时自动确定何时重新连接。
-
sse.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Server-Sent Events(SSE), 服务器发送事件</h1>
<ul>
</ul>
<script>
var source = new EventSource("http://localhost:4000/SSE");
source.onmessage = function(event){
var data = event.data;
//console.log(data);
// 处理数据
var li = document.createElement("li");
li.innerHTML = "Received: " + data;
document.getElementsByTagName("ul")[0].appendChild(li);
//source.close();
}
setTimeout(function(){
source.close();
}, 20 * 1000);
</script>
</body>
</html>
- server2 /SSE controller
var express = require('express');
var router = express.Router();
var count = 0;
var arrayData =["this","is","a","server","sent","event"];
var aDL = arrayData.length;
/* GET home page. */
router.get('/', function(req, res, next) {
if(req.hostname == "localhost"){
count++;
res.set('Access-Control-Allow-Origin',"http://localhost:3000");
res.set('Content-Type','text/event-stream');
res.set('Cache-Control','no-cache');
res.set('Connection','keep-alive');
//data字段后必须有空行
res.send("data: " + arrayData[count%aDL] + "\n\n id: " + count);
}
});
module.exports = router;
-
Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信。在JavaScript中创建了Web Socket之后,会有一个HTTP请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP升级,从HTTP协议交换为Web Socket协议。
-
使用标准的HTTP服务器无法实现 Web Sockets, 只有支持这种协议的专门服务器才能正常工作。
-
server1 WebSocket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Web Sockets</h1>
<ul>
</ul>
<script>
var socket = new WebSocket("ws://localhost:4000/WebSocket");
var message = { text: "hello server2", id: 1};
socket.onopen = function(){
socket.send(JSON.stringify(message)); // 发送字符串
}
socket.onmessage = function(event){
// MessageEvent 对象中的属性:
//{isTrusted: true, data: "{"data":"hello client"}", origin: "ws://localhost:4000", lastEventId: "", .... }
//console.log(event);
var data = JSON.parse(event.data);
//处理数据
var li = document.createElement("li");
li.innerHTML = "Received: " + data.data;
document.getElementsByTagName("ul")[0].appendChild(li);
socket.close();
}
socket.onclose = function(){
console.log("Connection closed.");
}
socket.onerror = function(){
console.log("Connection error.");
}
</script>
</body>
</html>
- server2 /WebSocket controller
var app = require('../app');
var debug = require('debug')('server2:server');
var http = require('http');
var url = require('url');
var WebSocketServer = require('ws').Server;
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '4000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
var wss = new WebSocketServer({ server: server })
wss.on('connection', function connection(ws) {
var location = url.parse(ws.upgradeReq.url, true);
// you might use location.query.access_token to authenticate or share sessions
// or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
//console.log(location);
if(location.path == "/WebSocket"){
ws.on('message', function incoming(message) {
//console.log(typeof message); // string
var msg = JSON.parse(message);
console.log('received: %s', msg.text);
ws.send(JSON.stringify({data:'hello client',id:msg.id}));
});
}
});
-
server1 crossoriginScript.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,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<title>Document</title>
<script>
window.errorList = [];
window.vconsoleLoaded = false;
window.addEventListener('error', function(e) {
if (window.vconsoleLoaded) {
console.error(e);
console.error('Erro type: ', e.type);
console.error('Error message: ', e.message);
console.error('Error filename: ', e.filename);
console.error('Error lineno: ', e.lineno);
console.error('Error colno: ', e.colno);
} else {
window.errorList.push(e, e.type, e.message, e.filename, e.lineno, e.colno);
}
}, false);
var s = document.createElement('script');
s.onload = function () {
var vConsole = new VConsole();
console.log('cxssdfs')
window.vconsoleLoaded = true;
if (window.errorList.length) {
window.errorList.forEach(it => {
console.error(it);
})
}
};
s.onerror = function() {};
s.src = './js/vconsole.min.js';
s.async = false;
document.getElementsByTagName("head")[0].appendChild(s);
</script>
<link href=http://localhost:4000/prefetch_demo.js rel=prefetch >
</head>
<body>
<h1>crossorigin script</h1>
<h3></h3>
<!-- <script src="http://localhost:4000/crossoriginScript.js"></script> -->
<!-- <script src="http://localhost:4000/prefetch_demo.js"></script> -->
<script>
// setTimeout(() => {
// foo();
// }, 3000)
test();
</script>
</body>
</html>
-server2 /crossoriginScript.js controller
var express = require('express');
var path = require('path');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.set('Access-Control-Allow-Origin', 'http://localhost:3000')
res.sendFile(path.join(__dirname, '/../static/crossoriginScript.js'));
});
module.exports = router;
-
server1 prefetch.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,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<title>Document</title>
<script>
window.vconsoleLoaded = false;
window.addEventListener('error', function(e) {
console.error(e);
console.error('Erro type: ', e.type);
console.error('Error message: ', e.message);
console.error('Error filename: ', e.filename);
console.error('Error lineno: ', e.lineno);
console.error('Error colno: ', e.colno);
}, false);
var s = document.createElement('script');
s.onload = function () {
var vConsole = new VConsole();
console.log('cxssdfs')
};
s.onerror = function() {};
s.src = './js/vconsole.min.js';
s.async = false;
document.getElementsByTagName("head")[0].appendChild(s);
</script>
<link rel="preload" as="script" href="http://localhost:4000/prefetch_demo.js" crossorigin="anonymous">
</head>
<body>
<h1>crossorigin script</h1>
<h3 onclick="ttt()">点击获取静态资源并执行相关方法</h3>
<!-- <script src="http://localhost:4000/crossoriginScript.js"></script> -->
<!-- <script src="http://localhost:4000/prefetch_demo.js"></script> -->
<script>
function ttt() {
var s = document.createElement('script');
s.onload = function () {
window.dd();
};
s.crossOrigin = true;
s.onerror = function() {};
s.src = 'http://localhost:4000/prefetch_demo.js';
s.async = false;
document.body.appendChild(s);
}
</script>
</body>
</html>
-server2 /prefetch_demo.js controller
var express = require('express');
var path = require('path');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.set('Access-Control-Allow-Origin', 'http://localhost:3000')
res.sendFile(path.join(__dirname, '/../static/prefetch_demo.js'));
});
module.exports = router;
- 本文参考《JavaScript 高级程序设计》第三版 21.4,21.5这两节, 并对每种跨域请求技术做了前端和后端代码实现。