
video
及audio
标签@font-face()
:该方法在不同的浏览器存在一定的差异,部分浏览器仍然有同源策略限制
及
script
标签请求的资源不受同源策略的限制;JSONP跨域是比较常见的,但JSONP跨域需要服务器端相应的处理才能支持;script
标签,然后在请求的url中带上一些处理参数,一般是一个回调函数;服务端在接收到请求后,从请求中获得处理参数,然后将处理结果返回给客户端;如果返回的是函数调用,那么客户端的回调函数就会被调用执行;GET
请求方式,本质上是因为script
标签请求资源的方式就是GET
;另外,根据实现过程可以看到,jsonp的返回是一个函数调用,容易导致XSS攻击等安全问题;http://localhost:8001/test/test1.html
;请求的服务器地址为:http://localhost:8080/
;为了跨域,在请求的url加入一个callback参数指定jsonp回调函数名称;服务端接收到请求后从中提取请求参数及回调函数名,然后将对应的回调函数包裹相应参数返回给客户端handleCallback(id)
;这时客户端的handleCallback()
函数就会被自动调用。
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Titletitle>
<meta name="viewport" content="width=device-width, initial-scale=1">
head>
<body>
<div class="hook">div>
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://localhost:8080/?callback=handleCallback';
document.head.appendChild(script);
// jquery请求方式
function ajax() {
$.ajax({
url: 'http://localhost:8080',
type: 'GET',
dataType: 'jsonp',
jsonpCallback: 'handleCallback',
})
}
function handleCallback(res) {
document.querySelector('.hook').innerHTML = `请求次数: ${res}`;
}
script>
body>
html>
var qs = require('querystring');
var http = require('http');
var server = http.createServer();
var id = 0;
server.on('request', function (req, res) {
console.log(req.url);
console.log(req.url.split('?'));
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(fn + '(' + id++ + ')');
res.end();
})
server.listen('8080');
console.log('server is running at port 8080...');
定义:CORS(Cross-ORigin Resource Sharing),即跨站资源共享机制,通过在服务端和客户端添加一定的标头进行跨域访问控制,;XHR
及Fetch
等API均支持CORS。
跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
在使用CORS时,如果请求的方法可能修改或影响服务端数据,那么浏览器通常会发送一个预检请求Option
,以确认服务器是否允许跨域,并且在允许跨域时,告知浏览器是否要携带相应的身份认证信息(cookie、token等);预检请求确认可以进行跨域请求时才会发起真正的请求;
适用情形:
XHR
或Fetch
请求;@font-face
加载跨域字体;drawImage
绘制audio
或video
;简单请求:
前面已经提到,触发预检请求的请求方式只有一部分,通常将不会触发CORS预检请求的称为简单请求,通常满足以下所有条件:
GET
、POST
或HEAD
请求;Content-Type
的值仅限于:text/plain
、multipart/form-data
、application/x-www-form-urlencoded
之一;Accept
、Accept-Language
、Content-Type
、Content-Language
Fetch API
的ReadableStream
二进制流对象;请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
非简单请求方式:
请求头包含Origin
请求头(包含协议、域名、端口号),用于指示请求来源;服务端设置`Access-Control-Allow-Origin: xxx’,可以设置为*或者指定的域名;
前端不需要特别处理:
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Titletitle>
<meta name="viewport" content="width=device-width, initial-scale=1">
head>
<body>
<div class="hook">div>
<script>
var target = document.querySelector('.hook');
var xhr = new XMLHttpRequest();
xhr.onload = function(){
if(xhr.readyState == 4) {
if(xhr.status >= 200 && xhr.status <300 || xhr.status == 304) {
target.innerHTML = xhr.responseText;
console.log(xhr.responseText);
} else {
target.innerHTML = 'Reuqest failed:' + xhr.status;
console.log("Request failed:", xhr.status);
}
}
}
// xhr.widthCredientials = true;
xhr.open('post', 'http://localhost:8080', true);
// xhr.setRequestHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST')
// xhr.setRequestHeader('Content-Type', 'application/x-wwww-urlencoded');
xhr.send('name=fn&id=12377');
script>
body>
html>
后端需要设定相应的响应头:
var qs = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function (req, res) {
var _data = '';
req.on('data', function (chunk){
_data += chunk;
});
req.on('end', function () {
res.writeHead(200, 'Success', {
'Content-Type': 'application/x-www-urlencoded',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'PUT, GET, POST',
'Access-Control-ALlow-Headers': 'Content-Type',
});
res.end("messages from client: \n" + _data);
})
});
server.listen(8080);
function finishedReq() {
console.log('write end...')
}
console.log('server is running at port 8080...');
document.domain跨域的基础是二级域名、协议及端口号要一致,通过document.domain
修改页面的域名,从而达到同一顶级域名下的子域名间的跨域;举个例子:
比如一主页面的域名是sale.game.abc.com
,内嵌一个iframe
,如下:
<iframe id="demo" src="http://gamestatic.abc.com/game/test/a.html">
由于主页面的域名与嵌入的子页面的二级域名是一致的,都是abc.com
,因此可以使用该方法进行跨域;需要在主页面和子页面的脚本中分别使用以下语句将页面的域名置为abc.com
;
document.domain = 'abc.com';
这样,父级页面就可以获取iframe
子页面的document
对象,并且获取和操作子页面的dom元素了;否则,受跨域限制直接在不同子域名下的两个关联页面也无法操作对方的dom;
window.name特性:
window.name
值,窗口关闭则对应的window.name
也被销毁;window.name
不能共享;window.name
数据格式可以自定义,大小一般不超过2M;跨域原理:利用同一窗口载入的多个页面共享同一个window.name
,结合iframe
载入不同域的页面,在iframe
子页面内使用window.name=data
赋值,data
是要传递的数据,然后在主页面通过window.name从子页面获取需要的数据;
// 主页面 域名为http://localhost:8001/test/index.html
// iframe内嵌页 域名为http://localhost:8888/test/iframe.html
// proxy.html:代理页是空页面,与主页面同域名
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<p>window.name + iframep>
<script>
window.flag = false;
var iframe = document.createElement('iframe');
var loadData = function () {
if (flag) {
var data = iframe.contentWindow.name;
console.log(data);
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else {
flag = true;
iframe.contentWindow.location = 'http://localhost:8001/test/proxy.html';
}
}
iframe.src = 'http://localhost:8888/test/iframe.html';
iframe.onload = loadData;
document.body.appendChild(iframe);
script>
body>
html>
// iframe内嵌页 iframe.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
window.name = 'message from iframe'
script>
body>
html>
跨域原理
location.hash
通过页面链接地址中的hash部分进行数据传递;缺点:数据明文,具有长度限制(收链接长度限制);下边的例子中,index.html
是主页面,域名是http://localhost:8080
;页面内部嵌入了一个src=http://localhost:8888/test/iframe.html#id=123
的iframe
;由于嵌入的iframe
是跨域的,无法直接通信,因此需要一个与index.html
同域名的中转代理页proxy.html
;
代码实现
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<p>window.name + iframep>
<iframe src="http://localhost:8888/test/iframe.html#id=123" frameborder="0">iframe>
<script>
window.onhashchange = function () {
console.log(location.hash);
}
script>
body>
html>
// iframe.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
var proxy = document.createElement('iframe');
proxy.src = 'http://localhost:8001/test/proxy.html#name=fn';
document.body.appendChild(proxy);
script>
body>
html>
// proxy.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
console.log('hash: ', location.hash);
window.parent.parent.location.hash = self.location.hash;
script>
body>
html>
postMessage API介绍
语法:
发送消息:targetWindow.postMessage(message, targetOrigin, [transfer]);
监听消息: window.onmessage = function (e) { // console.log(e.data); e对象下具有data,source及origin属性}
其中,targetWindow是想要通信的其他页面的window对象;targetOrigin是预通信的其他页面的域名,可以设置为*
与任何页面通信,但出于安全考虑不建议;mesage
是要进行通信的数据,已经可以支持字符串、对象等多种数据类型;transfer
是可选参数,表示一个与message
同时发送到接收方的Transferable对象,控制权由发送方转移到接收方;
onmessage监听消息,回调函数传入对象e,具有属性data
、source
、origin
;data
是传递的数据;source
是对发送方window对象的引用;origin
是发送方的域名;
代码实现:
// index.html 域名localhost:8001
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<h1>HTML5 postMessageh1>
<p>p>
<iframe src="http://localhost:8888/test/sub.html" frameborder="0" onload="load()">iframe>
<script>
function load() {
var iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('i need some msg from you.', 'http://localhost:8888');
// window.postMessage({'name': 'post', method: 'cors'}, 'http://localhost:8880/test/sub.html');
window.onmessage = function (e) {
console.log(e.data);
}
}
script>
body>
html>
// sub.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
window.onmessage = function (e) {
console.log(e.data);
e.source.postMessage({'name': 'post', method: 'cors'}, e.origin);
}
script>
body>
html>
运行结果:
i need some msg from you. // sub.html输出
{name: "post", method: "cors"} // index.html输出
http-proxy-middleware
是node中常用的设置代理的插件库,可以与express、connet等配合使用// server.js
const http = require('http');
const server = http.createServer();
const qs = require('querystring');
const url = require('url');
let count= 0;
server.on('request', (req, res) => {
count++;
const query = url.parse(req.url, true).query;
res.writeHead(200, {
'Set-Cookie': 'name=fn;Path:/;Domain:localhost:6688;Httponly'
});
query.count == 1 ? res.write(`Response from Server -- localhost:6688; visit count: ${count}`) : res.write(`Response from Server -- localhost:6688; no tracking...`);
res.end();
})
server.listen(6688);
console.log('server is running at port 6688...')
// proxy.js
const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
const options = {
target: 'http://localhost:6688',
changeOrigin: true,
onProxyRes: (proxyRes, req, res) => {
res.header('Access-Control-Allow-Origin', 'http://localhost');
res.header('Access-Control-Allow-Credentials', 'true');
proxyRes.headers['x-self-defined'] = 'node middleware';
},
}
app.use('/api', proxy(options));
app.use(express.static( './public'));
app.listen(8002);
console.log('proxy server is listen at port 8002');
运行结果:# nginx配置
worker_processes 1; // 工作进程数,与CPU数相同
events {
connections 1024; // 每个进程的最大连接数
}
http {
sendfile on; // 高效文件传输模式
server {
listen 80;
server_name localhost;
# 负载均衡与反向代理
location / {
root html;
index index.html index.htm;
}
location /test.html {
proxy_pass http://localhost:6688;
}
}
}
// server.js http://localhost:6688
const http = require('http');
const server = http.createServer();
const qs = require('querystring');
const url = require('url');
let count= 0;
server.on('request', (req, res) => {
// var params = url.parse(req.url, true);
var params = qs.parse(req.url.split('?')[1]);
res.write(JSON.stringify(params));
res.end(`port: 6688`);
})
server.listen(6688);
console.log('server is running at port 6688...')
此时,直接访问localhost/test.html域名时nginx会将请求代理到localhost:6688域名下,从而实现跨域;var Socket = new WebSocket(url, [protocol] );
readyState
—— 0 (WebSocket.CONNECTING) / 1(WebSocket.OPEN) / 2(WebSocket.CLOSING) / 3(WebSocket.CLOSED);WebSocket.bufferedAmount
—— 已调用send()
方法在缓冲区等待发送的数据字节数;onopen
、onmessage
、onclose
、onerror
;send(data)
、close([code [, reason]])
;可选的code及文本表明连接关闭的原因,默认code=1005;// server.js
const http = require('http');
const server = http.createServer();
const url = require('url');
const WebSocket = require('ws');
const socket = new WebSocket.Server({port: 5555});
socket.on('connection', ws => {
ws.on('message', (data) => {
console.log('data from client: ', data)
ws.send('hello websocket...');
console.log('server end send data...');
})
})
console.log('server is running at port 5555...')
// index.html
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Titletitle>
<meta name="viewport" content="width=device-width, initial-scale=1">
head>
<body>
<div class="hook">div>
<script>
SocketEvent = () => {
if (window.WebSocket) {
console.log('llll')
var socket = new WebSocket('ws://localhost:5555');
socket.onopen = function () {
console.log('client socket opening...');
socket.send(`I'm requesting some data from you...`);
}
socket.onmessage = function (e) {
console.log(e);
console.log(e.data);
}
socket.onclose = function (e) {
console.log('client socket closing...');
}
}
}
script>
<button onclick="SocketEvent()">socketbutton>
body>
html>