JSONP
实现背景
浏览器通常允许跨域资源嵌入,例如:
- < script >< /script > 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
- < link rel="stylesheet" > 标签嵌入 CSS。由于 CSS 的松散的语法规则,CSS 的跨域需要一个设置正确的 Content-Type 消息头。不同浏览器有不同的限制
- < img >嵌入图片。支持的图片格式包括 PNG,JPEG,GIF,BMP,SVG,...
- < video\ > 和 < audio\ >嵌入多媒体资源。
- < object\ >, < embed\ > 和 < applet\ > 的插件。
- @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
- < frame\ > 和 < iframe\ > 载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。
所以可以用它来实现跨域,用破解的方式.
当服务端支持 JSONP 技术时,会做如下一些设置:
- 识别请求的 URL,提取 callback 参数的值,并动态生成一个执行该参数值(一个函数)的 JavaScript 语句;
- 将需要返回的数据放入动态生成的函数中,等待其加在到页面时被执行.
JSONP 的客户端具体实现
让我们直接看看 JSONP 的使用方式:
// 创建 Jsonp 类
// 初始化时传入两个参数, url 是接口的url
// cb 是对于接口返回的参数的处理
function Jsonp(url, cb) {
this.callbackName = 'jsonp_' + Date.now();
this.cb = cb;
this.url = url;
this.init();
}
// 初始化方法 用于拼接 url
Jsonp.prototype.init = function() {
if (this.url.indexOf('?')) {
this.url = this.url + '&callback=' + this.callbackName;
} else {
this.url = this.url + '?callback=' + this.callbackName;
}
this.createCallback();
this.createScript();
};
// 创建 script 标签, src 取接口请求的url
Jsonp.prototype.createScript = function() {
var script = document.createElement('script');
script.src = this.url;
script.onload = function() {
this.remove();
// 删除 window 下定义的无用方法
delete window[this.callbackName];
};
document.body.appendChild(script);
};
// 绑定回调函数
Jsonp.prototype.createCallback = function() {
window[this.callbackName] = this.cb;
};
// 创建 jsonp 实例, 并指定回调函数
new Jsonp('http://localhost:8888/', function(data) {
console.log(data);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
jQuery 如何实现 jsonp 调用?
jQuery(document).ready(function() {
$.ajax({
type: 'get',
async: false,
url: 'http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998',
dataType: 'jsonp',
jsonp: 'callback', //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback: 'flightHandler', //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(json) {
alert(
'您查询到航班信息:票价: ' +
json.price +
' 元,余票: ' +
json.tickets +
' 张。'
);
},
error: function() {
alert('fail');
}
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果不想把整个 jQuery 引入,可以下载一个 npm 模块 jsonp-client(更小)或者 jsonp-retry(更强大)
JSONP 的服务端具体实现
npm install jsonp-body --save
1
var koa = require('koa');
var jsonp = require('jsonp-body');
js;
var app = koa();
app.use(function*() {
this.set('X-Content-Type-Options', 'nosniff');
if (this.query.callback) {
this.set('Content-Type', 'text/javascript');
} else {
this.set('Content-Type', 'application/json');
}
this.body = jsonp({ foo: 'bar' }, this.query.callback);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
CORS
客户端细节
服务端细节
node
const http = require('http');
const PORT = 8888;
// 协议名必填, 如果同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'http://localhost:8080', 'https://www.baidu.com'];
// 创建一个 http 服务
const server = http.createServer((request, response) = > {
const { method, headers: { origin, cookie } } = request;
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin);
}
response.setHeader('Access-Control-Allow-Methods', 'PUT');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Headers', 'token');
response.setHeader('Access-Control-Expose-Headers', 'token');
response.setHeader('token', 'quanquan');
if (method === 'OPTIONS') {
response.writeHead(204);
response.end('');
} else if (!cookie) {
response.setHeader('Set-Cookie', 'quanquan=fe');
}
response.end("{name: 'quanquan', friend: 'guiling'}");
});
// 启动服务, 监听端口
server.listen(PORT, () = > {
console.log('服务启动成功, 正在监听: ', PORT);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
express4
// 跨域处理
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', true);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Max-Age', 5);
next();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Nginx 反向代理
nginx 配置
server {
# 监听80端口号
listen 80;
# 监听访问的域名
server_name a.com;
# 根据访问路径配置
location / {
# 把请求转发到 http://127.0.0.1:9999
proxy_pass http://127.0.0.1:9999;
# 兼容websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 监听根目录下的 /api 路径
location /api/ {
# 把请求转发到 http://127.0.0.1:8888
proxy_pass http://localhost:8888;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
不过有 cookie 相关问题,需要注意
WebSocket
HTML5 标准推出了 WebSocket 协议,使浏览器和服务器实现了双向通信,更妙的是,除了 IE9 及以下的 IE 浏览器,所有的浏览器都支持 WebSocket 协议。WebSocket 协议本身就不受浏览器“同源策略”的限制
下面是客户端告知服务端要升级为 WebSocket 协议的报头:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
下面是服务端向客户端返回的响应报头:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
1
2
3
4
5
2
3
4
5
var ws = new WebSocket('wss://echo.websocket.org');
ws.send('Hi, server!');
1
2
2
传入的参数为响应 WebSocket 请求的地址。
同样类似 AJAX 的是,WebSocket 对象也有一个 readyState 属性,用来表示对象实例当前所处的链接状态,有四个值:
- 表示正在连接中(CONNECTING);
- 表示连接成功,可以通信(OPEN);
- 表示连接正在关闭(CLOSING);
- 表示连接已经关闭或打开连接失败(CLOSED);
除此之外,WebSocket 对象还提供给我们一系列事件属性,使我们控制连接过程中的通信行为:
- onopen:用于指定连接成功后的回调函数;
- onclose:用于指定连接关闭后的回调函数;
- onmessage:用于指定收到服务器数据后的回调函数;
- onerror:用于指定报错时的回调函数;
ServerProxy
浏览器有同源策略的限制服务器没有. 我们的前端项目托管在后端项目中所以访问我们自己的后端不跨域. 我们的后端请求第三方服务没有限制.
所以 ServerProxy 的原理大概就是通过页面通过 ajax 访问同域后端服务,后端服务访问目标服务并将目标服务返回的内容透传给前端.