Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

跨域解决方案实践 #15

Open
HolyZheng opened this issue Jul 1, 2018 · 2 comments
Open

跨域解决方案实践 #15

HolyZheng opened this issue Jul 1, 2018 · 2 comments

Comments

@HolyZheng
Copy link
Owner

HolyZheng commented Jul 1, 2018

作者:holyZheng
转载请注明出处

了解几个跨域的方案,并且通过简单实践进行体会。

如何实践?

但是,我们如何进行实践呢?在哪发请求?向什么服务器发请求?很简单,就在当前网页,打开控制台,输入请求的代码

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();

那么我们就可以以当前页面url作为origin,向http://127.0.0.1:8888/ ,发送请求GET请求了。
同时在本地创建一个node服务

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

这样我们就有服务器了,你可以很轻松的跟着这遍文章来实践了,然后从当前网页发送get请求到本地服务,理所当然跨域了。

ps: github网站不行(本文最初再github上编写),会引发csp错误,此错误是用于防止内容注入攻击的,不得不说,大网站安全措施做得就是好,转战segmentfault做实践。

cors_error

1. cors

cors(跨域资源共享 Cross-origin resource sharing),它允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

cors 分为两种请求,简单请求和非简单请求,关于cors的更详细介绍,推荐阮一峰老师的跨域资源共享 CORS 详解,本文注重实践。

简单请求

正如上方的例子便是一个简单请求

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();

如何解决此案例的跨域问题呢?

  1. 浏览器端,浏览器会自动在请求头中添加 origin 字段,我们不需要操作。
Request Headers: 
Origin: https://github.com
  1. 服务端,Access-Control-Allow-Origin属性,我们需要服务端设置此属性,指定允许的请求源域名,可以通过指定为 *来指定所以域名。后端动起来:
var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain',
      'Access-Control-Allow-Origin': '*'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

重启服务,再尝试

cors_error
这次没有再报错了,我们看看服务器放回了什么
response
nice!跨域成功!

非简单请求

同样我们在控制台输入一下代码进行put(非简单请求)

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.send();

毫无意外的报错

error_image

在进行非简单请求的时候,浏览器会先发送一次OPTION请求来“预检”(preflight)该请求是否被允许,请求头中会通过Access-Control-Request-MethodAccess-Control-Request-Headers来告诉服务器我需要用到的方法和字段,服务器通过返回的头部信息中的Access-Control-Allow-OriginAccess-Control-Allow-Method来告诉浏览器该跨域请求是否被允许。修改后端代码:

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

可以看到浏览器会先发送一个预检

option

当确认允许跨域之后,以后再发送该请求,就会省去预检处理,之间当作简单请求来操作。很明显,修改了后端代码后,这次的put请求时成功的。这里就不继续上图了。

cors总结

cors(跨域资源共享 Cross-origin resource sharing),它允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

  1. 浏览器端会自动向请求头添加origin字段,表明当前请求来源。
  2. 浏览器端需要设置响应头的Access-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Origin等字段,指定允许的方法,头部,源等信息。
  3. 请求分为简单请求和非简单请求,非简单请求会先进行一次OPTION方法进行预检,看是否允许当前跨域请求。

2. jsonp

jsonp的原理就是利用就是利用script标签没有跨域限制,可以通过script标签的src属性发送GET请求。我们继续尝试,先把后端有关跨域的设置去掉,并重启服务

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

打开我们的控制台输入一下代码,利用script标签进行jsonp请求

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = `http://127.0.0.1:8888/`;
document.head.appendChild(script);

可以看到,后端正常的返回了

request success !!!

而且该请求为GET请求

Request URL: http://127.0.0.1:8888/
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:8888
Referrer Policy: no-referrer-when-downgrade

但是我们现在只是成功发送了一个跨域请求,但是我们不像XMLHttpRequest那样可以在res.responseText中拿到数据,通过jsonp我们该怎么拿到请求的数据呢?方法就是前后端约定一个callback字段名,来传递函数名,前端通过该函数来拿到数据。前端代码修改为:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = `http://127.0.0.1:8888/?callback=onBack`;
document.head.appendChild(script);
function onBack (res) {
  console.log(JSON.stringify(res));
  // 请求完后删除添加到页面上的script标签
  var head = document.head
  head.removeChild(script)
}

通过callback字段来传递函数名onBack,后端代码修改为

var http = require('http')
var urlTool = require('url')
// json 数据
var data = {'methods': 'jsonp', 'result': 'success'};

http.createServer(function (request, response) {
    var params = urlTool.parse(request.url, true)
    console.log(params)
    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    if (params.query && params.query.callback) {

      // callback(data)
      var str = `${params.query.callback}(${JSON.stringify(data)})`
    }

    response.end(str);
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

重启后端服务,并且在控制台输入代码,可以看到结果:

jsonp

我们拿到了数据,并且通过onBack函数将他输出到了控制台上!

总结

  1. jsonp是一种跨域方案,他利用script标签没有跨域限制的特点,通过script标签的的src属性发送GET请求。
  2. 可以通过前后端约定一个字段名,比如callback,来传递一个函数名,后端通过拼凑出一条将数据传递给该函数并执行该函数的JavaScript语句返回给浏览器,浏览器解析执行,从而使得前端可以使用对应的callback函数,拿到数据,处理数据。

jsonp和cors比较

  1. CORS与JSONP的使用目的相同,但是比JSONP更强大。
  2. JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

同源策略:同源策略限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式,这是一个用于隔离潜在恶意文件的重要安全机制。如果两个页面拥有 相同 的 协议(protocol),端口(如果指定),和 主机,那么这两个页面就属于同一个源(origin)。

@HolyZheng
Copy link
Owner Author

HolyZheng commented Jul 26, 2018

webpack反向代理

实际开发过程中还可以通过webpack.devServer.proxy配置一个反向代理:

proxy: {
  "/api": {
    target: "https://other-server.example.com",
    secure: false
  }
}

他的原理就是,服务器之间的通信是没有同源限制的,让本地服务器充当一个代理服务器的角色,帮组我们向目标服务器获取数据。因为我们在本地开发的时候,我们的代码运行在本地服务器之上,所以我们向本地服务器发送请求是不跨域的,我们就可以实现跨域了。

@HolyZheng
Copy link
Owner Author

HolyZheng commented Aug 21, 2018

跨(同域)标签页通信

localStorage实现跨(同域)标签页通信。

window.addEventListener('storage', function (event) {
  console.log(event.key, event.newValue);
});

跨 不同子域 或 同域不同路径 通信

cookie来实现,设置 domain path,例如在 two.example.comexample.com 之间共享

Set-Cookie: name = value, domain = example.com, path =  /;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant