跨域访问资源

此生再无相见时 提交于 2020-02-25 14:40:26

同源政策

同源的概念:如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,只要有一个不相同,就是不同源。
http://www.example.com/dir/page.html作比较(没写端口名就默认为80端口)
http://www.example.com/dir/other.html (同源)
http://example.com/dir/other.html (不同源,域名不同)
http://www.example.com:81/dir/page.html (不同源,端口号不同)
https://www.example.com/dir/page.html (不同源,协议不同)

同源政策
浏览器的同源策略,限制了来自不同源的"document"或脚本,对当前"document"读取或设置某些属性。从一个域上加载的脚本不允许访问另外一个域的文档属性。

Ajax请求限制

Ajax只能向自己的服务器发送请求。若是向非同源的服务器发送请求将会被拒绝。

解决方法

使用JSONP解决同源限制问题

1.将不同源的服务端请求地址写在script标签的属性中

<script src="www.example.com"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

2.服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。(数据已返回回来就会用script标签标起来,当成js代码来执行)

const data = 'fn({name:'张三',age:'20'})';
//一到客户端页面就将在客户端页面中找到名为fn的函数并执行
res.send(data);

3.在客户端全局作用域下定义函数fn(一定要在script标签之前定义函数,否则会因为找不到fn函数而报错)

function fn (data) { /*处理服务器端返回的数据的代码*/}

4.在函数内部对服务器端返回的数据进行处理

function fn (data) { console.log(data);}

原理:jsonp(json with padding),不属于Ajax请求,它是利用script标签中的src属性,src可以跨域加载js脚本,返回的值为js代码,在数据请求回来后,会把内容放在script标签中当作JavaScript代码来执行。这完全避开了同源政策。

Jsonp代码的封装
要考虑的有:
1.动态发送请求(script要动态创建)
2.可复用性高
3.为jsonp与fn函数解绑,不要事先定义,由调用者传递
4.客户端的要定义的函数的改变不影响后端的代码
客户端的代码如下:

function jsonp (options) {
            //解决每一次发送请求的回调函数名不能一样的问题,否则会发生覆盖
            var funName = 'myfunction' + 
                Math.random().toString().replace('.','');
            //将调用者传过来的success挂载到全局作用域上
            Window[funName] = options.success;
            var param = '';
            //拼接请求参数
            for (let key in options.data) {
                param += '&' + key + '=' + options.data[key];
            }
            options.url = '?' + 'callback=' + funName + param;
            //创建script标签
            var script = document.createElement('script');
            script.src = options.url;
            //将script标签追加到页面中
            document.body.appendChild(script);
            script.onload = function () {
                /*等到数据被加载完成后就删除script标签,
                因为不删除的话有发送多次请求就会有多个script标签在body中,这是没有用处的
                */
                document.body.removeChild(script);
            }
        }
        var btn2 = document.getElementById('btn2');
        //为按钮注册点击事件
        btn2.onclick = function () { 
           jsonp({
               url: 'http://www.baidu.com',
               data:{
                   name:'lisi',
                   age:34
               },
               success:function (data){
                   console.log("success2")
               }
           })
        }

服务器端的代码

import path from 'path';
import express from 'express'
//使用express框架创建web服务
const app = express();
//设置静态资源访问服务器功能,
//设置了之后,public目录下的文件可以直接用url访问
app.use(express.static(path.join(__dirname,'public')));
//设置路由
app.get('/',(req,res) =>{
    const fnName = req.query.callback;
    const data = JSON.stringify({name:'zhangsan',sex:'man'})
    const result = fnName + '(' + data + ')'
    setTimeout(() => {
        res.send(result)
    }, 1000);
})
app.listen(3001);//监听端口

服务器端代码的优化:

import path from 'path';
//使用express框架创建web服务
const app = express();
//设置静态资源访问服务器功能,
//设置了之后,public目录下的文件可以直接用url访问
app.use(express.static(path.join(__dirname,'public')));
//设置路由
app.get('/',(req,res) =>{
    res.jsonp({name:'zhangsan',age:34})//实际上是做了上面一样的操作
})
app.listen(3001);//监听端口

服务器端的解决方案

原理:同源政策只限制客户端,服务器端可以跨域访问资源,所以我们可以在客户端照常发送Ajax请求,在服务器端借助requests模块跨域访问资源,然后将资源返回给客户端。
客户端代码:

ajax({
            type:'get',
            url:'http://localhost:3000/server',
            data:{name:'zhangsan',age:23},
            header:{
                    'Content-type':'application/json'
                },
                sucess:function (data,object) {console.log('success!');
                },
                error:function (data,object) {console.log('error!');
                }
        })

服务器端代码:

import path from 'path';
import express from 'express'
import request from 'request'
//使用express框架创建web服务
const app = express();
//设置静态资源访问服务器功能,
//设置了之后,public目录下的文件可以直接用url访问
app.use(express.static(path.join(__dirname,'public')));
//设置路由
app.get('/',(req,res) =>{
    request('http://localhost:3001/server',(error,response,body){
        res.send(body);
    })
})
app.listen(3000);//监听端口

CORS 跨域资源共享

CORS:全称为Cross-origin resource sharing,它允许浏览器向跨域服务器发送Ajax请求,克服了Ajax只能同源使用的限制。

原理:实际上,客户端向浏览器端发送请求,会在请求头上注明origin(来源),无论是同源还是跨域请求资源,服务器端都会给出响应。但是跨域服务器会根据Acceess-Control-Access-Origin来判断origin(来自客户端),若不是可允许的源,服务器端返回的便不是数据而是错误信息,所以我们可以在服务器端设置Access-Control-Allow-Orgin属性,将客户端的地址写进去,便能够获取数据了。
在客户端照常发送数据,在服务器端配置,服务器端的代码如下:

import path from 'path';
import express from 'express'
//使用express框架创建web服务
const app = express();
//设置静态资源访问服务器功能,
//设置了之后,public目录下的文件可以直接用url访问
app.use(express.static(path.join(__dirname,'public')));
//拦截所有请求,为所有的请求设置响应头
app.use((req,res,next) => {
    //配置允许的请求源,可以是多个地址,用逗号隔开,*表示允许所有的源
    res.header('Access-Control-Allow-Orgin','*');
    res.header('Access-Control-Allow-Methods','get,post');
    next();//为请求放行,若是没有调用这个函数,那么请求不会往下去匹配
})
//设置路由
app.get('/',(req,res) =>{
    var data = {name:'zhangsan',age:34};
    res.send(data);
})
app.listen(3000);//监听端口

cookie

传统的客户端访问服务端使用http协议,这是无状态的,即客户端每次向服务器端响应都是以陌生主机的身份,这在某些情况下是不方便的,比如有时候需要服务器保留一些身份信息,为客户端保留某些状态,如添加的购物车,以及客户端的登录状态。为达到这个目的,我们可以使用cookie来保存信息,客户端首次访问浏览器,服务器会给客户端发送一个cookie,客户端下次访问时携带这个cookie,服务器就能认识了。

在使用Ajax技术发送跨域请求,默认情况下不会在请求中携带cookie信息。
withCredentials:指定在涉及跨域请求时,是否携带cookie信息,默认值为false
Access-Control-Allow-Credentials:true允许客户端发送请求时携带cookie

客户端核心代码:

var loginForm = document.getElementById('loginform') ;
//将html表单对象转换为fromData表单对象
var formData = new FormData(loginForm);
var xhr = new XMLHttpRequest();
xhr.open('post','http://localhost:3301/login');
//当发送跨域请求时携带cookie
xhr.withCredentials = true;
xhr.send(formData);
xhr.onload = function () {
    console.log(xhr.responseText);
    
}

服务器端核心代码:

app.use((req,res,next) => {
    //配置允许的请求源,可以是多个地址,用逗号隔开,*表示允许所有的源
    res.header('Access-Control-Allow-Orgin','*');
    res.header('Access-Control-Allow-Methods','get,post');
    //允许客户端发送跨域请求时携带cookie信息
    res.header('Access-Control-Allow-Credentials',true);
    next();//为请求放行,若是没有调用这个函数,那么请求不会往下去匹配
})
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!