美文网首页
前端跨域总结(持续更新)

前端跨域总结(持续更新)

作者: 采姑娘的大白菜 | 来源:发表于2017-04-28 15:51 被阅读0次

    同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
      源于同源策略,我们有时需要跨域处理。

    一、jsonp解决方案

    jsonp是利用script标签没有跨域限制的特性,通过在src的url的参数上附加回调函数名字,然后服务器接收回调函数名字并返回一个包含数据的回调函数
    function doSomething(data) {
      // 对data处理
    }
    var script = document.createElement("script");
    script.src = "http://www.b.com/b.html?callback=doSomething";
    document.body.appendChild(script);
    
    // 1.生成一个script标签,将其append在body上,向服务器发出请求
    // 2.服务器根据 callback 这个参数生成一个包含数据的函数 doSomething({"a", "1"})
    // 3.页面事先已声明doSomething函数,此时执行 doSomething(data) 这个函数,获得数据
    
    
    示例
    前端以jq封装的jsonp为例
            $.ajax({
                 url:'http://192.168.9.5/jsonp_test1.jsp',
                 dataType:"jsonp",
                 jsonp:"jsonpcallback",
                 success:function(data){
                     var $ul = $("<ul></ul>");
                     $.each(data,function(i,v){
                         $("<li/>").text(v["id"] + " " + v["name"]).appendTo($ul)
                     });
                     $("#res").append($ul);
                 }
            });
    
    
    服务器的处理
    c#
    [HttpGet]
            public ActionResult getSurvey_Contact(String contact_id,string survey_id)
            {
                var callback = Request.QueryString["callback"];   //获取回调函数的名字
                var contactColl = MyMongoDb.CurrentDB.GetCollection("Survey_Contact");
                if (contact_id == null)
                    contact_id = String.Empty;
    
                var doc = contactColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("Survey_Id", survey_id))).FirstOrDefault();
                if (doc == null)
                    doc = new BsonDocument();
                FormateJSON(new[] { doc });
                var answerColl = MyMongoDb.CurrentDB.GetCollection("Survey_Answers");
                var answerDocs = answerColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("survey_id", survey_id))).ToArray();
                FormateJSON(answerDocs);
                doc.Set("answers", new BsonArray(answerDocs));
                var result = new ContentResult();
                result.ContentEncoding = System.Text.Encoding.UTF8;
                result.ContentType = "application/json";
                //Regex reg = new Regex("ObjectId\\(([a-z0-9])\\)");
                if (String.IsNullOrWhiteSpace(callback))
                {
                    result.Content = doc.ToJson();
                    return result;
                }
                else
                {
                    result.Content = callback + "(" + doc.ToJson() + ");";   //前端用js文件格式解析结果,通过callback(data)获取数据
                    return result;
                }
            }
    
    
    //题外
    jquery的$.Jsonp或者$.ajax的jsonp和ajax没有关系,只是jquery封装起来方便使用。
    实际也是利用script标签没有跨域限制的特性实现的
    
    JSONP的优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
    
    JSONP的缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
    
    

    二、window.name+iframe

    //window.name的原理是利用同一个窗口在不同的页面共用一个window.name,这个需要在a.com下建立一个代理文件c.html,使同源后a.html能获取c.html的window.name
    //需要目标服务器响应window.name
    
    

    三、window.location.hash+iframe

    //需要目标服务器作处理
    //b.html将数据以hash值的方式附加到c.html的url上,在c.html页面通过location.hash获取数据后传到a.html(这个例子是传到a.html的hash上,当然也可以传到其他地方)
    
    

    四、document.domain

    这种方式适用于主域相同,子域不同,比如http://www.a.com和http://b.a.com
    假如这两个域名下各有a.html 和b.html,
    
    a.html
    
        document.domain = "a.com";
        var iframe = document.createElement("iframe");
        iframe.src = "http://b.a.com/b.html";
        document.body.appendChild(iframe);
        iframe.onload = function() {
            console.log(iframe.contentWindow....); // 在这里操作b.html里的元素数据
        }
    
    b.html
    
        document.domain = "a.com";
    
    注意:document.domain需要设置成自身或更高一级的父域,且主域必须相同。
    
    

    五、html5的 postMessage+ifrme

    //这个需要目标服务器或者说是目标页面写一个postMessage,主要侧重于前端通讯。
    
    假设在a.html里嵌套个<iframe src=""http://www.b.com/b.html" rel="nofollow">http://www.b.com/b.html" frameborder="0"></iframe>,在这两个页面里互相通信
    
    a.html
    
        window.onload = function() {
            window.addEventListener("message", function(e) {
                alert(e.data);
            });
    
            window.frames[0].postMessage("b data", "http://www.b.com/b.html");
        }
    
    b.html
    
        window.onload = function() {
            window.addEventListener("message", function(e) {
                alert(e.data);
            });
            window.parent.postMessage("a data", "http://www.a.com/a.html");
        }
    
    这样打开a页面就先弹出 a data,再弹出 b data
    
    

    六、CORS解决方案

    CORS是XMLHttpRequest Level 2 里规定的一种跨域方式。在支持这个方式的浏览器里,javascript的写法和不跨域的ajax写法一模一样,只要服务器需要设置Access-Control-Allow-Origin: *

    1、apache设置header : Access-Control-Allow-Origin

    //httpd.conf
    找到这行
    #LoadModule headers_module modules/mod_headers.so
    把#注释符去掉
    LoadModule headers_module modules/mod_headers.so
    目的是开启apache头信息自定义模块
    
    <Directory />
    AllowOverride none
    Require all denied
    </Directory>
    
    改为下面代码
    
    <Directory />
    Require all denied
    Header set Access-Control-Allow-Origin *
    </Directory>
    
    设置请求头
    
    

    2、nginx设置header : Access-Control-Allow-Origin

    在nginx的conf文件中加入以下内容:
    location / {
      add_header Access-Control-Allow-Origin *;
    }
    
    

    3、服务器设置header :Access-Control-Allow-Origin

    //示例
    php
    //header("Access-Control-Allow-Origin:*");
    //header("Access-Control-Allow-Origin:http://www.a.com");
    
    c#
    //在Web.config设置
        <httpProtocol>
          <customHeaders>
            <add name="Access-Control-Allow-Origin" value="*" />
            <add name="Access-Control-Allow-Headers" value="Content-Type" />
            <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
          </customHeaders>
        </httpProtocol>
    
    
    nodejs
    
    var express = require('express');
    var app = express();
    //设置跨域访问
    app.all('*', function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        res.header("Access-Control-Allow-Headers", "X-Requested-With");
        res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
        res.header("X-Powered-By",' 3.2.1')
        res.header("Content-Type", "application/json;charset=utf-8");
        next();
    });
    
    app.get('/getdata', function(req, res) {
        res.send({id:req.params.id, name: req.params.password});
    });
    
    app.listen(3000);
    console.log('Listening on port 3000...');
    
    
    
    //题外
    如果需要跨域设置cookie,要设置Access-Control-Allow-Credentials,例如
    
    c#
       x.Headers.Add("Access-Control-Allow-Origin", "http://localhost:8080");
       x.Headers.Add("Access-Control-Allow-Credentials", "true");
    
    
    //题外
    cors缺点:
    需要服务器配合
    设置Access-Control-Allow-Origin 存在一定的风险
    
    

    七、chrome插件跨域方案

    //CORS Toggle
    

    八、代理服务器解决方案

    1、nignx反向代理

    //搭建一个中转nginx服务器转发请求,如果是自己的主机就比较好操作。但是用了Nginx配置之后,webpack的hot reload会存在比较大的延迟
    //nginx.conf
    http {
        include       mime.types;
        default_type  application/octet-stream;
        server {
            listen       8080;
            charset utf-8;
            access_log on;
            access_log  logs/host.access.log  ;
            location / {
                #8080端口号是开启的本地服务端口号
                proxy_pass http://localhost:8080;
            }
            location /api  { 
                #每有一个新的代理需求,就新增一个location
                #反向代理,达到前后端分离开发的目的
                proxy_pass http://192.168.60.225:7003;
            }
        }
    }
    
    /***********
    关于proxy_pass
    
    既是把请求代理到其他主机,其中 http://www.b.com/ 写法和 http://www.b.com写法的区别如下:
    不带/
    location /html/
    {
      proxy_pass http://b.com:8300;
    }
    带/
    location /html/
    {
        proxy_pass http://b.com:8300/;
    }
    
    上面两种配置,区别只在于proxy_pass转发的路径后是否带 “/”。
    
      针对情况1,如果访问url = http://server/html/test.jsp,则被nginx代理后,请求路径会便问http://proxy_pass/html/test.jsp,将test/ 作为根路径,请求test/路径下的资源。
    
      针对情况2,如果访问url = http://server/html/test.jsp,则被nginx代理后,请求路径会变为 http://proxy_pass/test.jsp,直接访问server的根资源。
    
    ***********/
    

    2、apache反向代理

    用 apache 的 mod_proxy 模块开启反向代理功能来实现:
    1 修改 apache 配置文件 httpd.conf ,去掉以下两行前面 # 号,加载反向代理模块
    
        LoadModule proxy_module modules/mod_proxy.so
        LoadModule proxy_http_module modules/mod_proxy_http.so
    
    
    2 在站点目录中修改:
    <VirtualHost *:80>
        DocumentRoot "E:\study-environment\www"
        #正向代理
        #ProxyRequests On
        #ProxyVia On
        #反向代理时设置为Off
        ProxyRequests Off  
    
        <Proxy "*">
          Order deny,allow
          #Deny from all  
          #Allow from 127.0.0.1 188.1.8.1
          Allow from all
        </Proxy>
    
        #proxy setting  
        ProxyPass           /api  http://188.1.8.1/api
        #ProxyPassReverse    浏览器的地址栏不会显示反向代理的地址
        ProxyPassReverse    /api  http://188.1.8.1/api 
        #如果路径的名称/api代理后没有变化,session不会丢失,则可以不用ProxyPassReverseCookiePath 
        #ProxyPassReverseCookiePath /api  /api  
          
        ProxyPass           /log http://188.1.8.1/apilog
        ProxyPassReverse    /log http://188.1.8.1/apilog
        ProxyPassReverseCookiePath /log /apilog
     
        #另一种写法
        <Location / >
             ProxyPass        http://188.1.8.1/home connectiontimeout=5 timeout=300
             ProxyPassReverse http://188.1.8.1/home
         </Location>
    </VirtualHost>
    重启 apache
    
    
    //题外
    
    正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器Z向服务器B转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
    
    反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端。
    
    简单点说就是
    正向代理:客户向代理服务器发请求,代理服务器再把请求到指定服务器
    反向代理:客户向目标服务器发请求,代理服务器拦截请求,再把请求转发到指定的目标服务器
    
    

    3、webpack+webpack-dev-server

    //webpack.config.js
    var path = require("path");
    
    module.exports = {
        entry: {
            index: './src/index.entry.js',
            authManage: './src/authManage.entry.js',
            reports: './src/reports.entry.js'
        },
        output: {
            path: path.join(__dirname, 'public'),
            filename: '[name].bundle.js'
        },
        module: {
            loaders: [
                {
                    test: /\.jsx?$/,
                    loader: 'babel',
                    exclude: /node_modules/,
                    query: {
                        presets: ['es2015', 'react', "stage-2"]
                    }
                },
                { test: /\.css$/, loader: 'style!css' },
                { test: /\.(png|jpg|jpeg|gif|woff)$/, loader: 'url-loader?limit=8192' }
            ]
        },
        devServer: {
            //代理
            proxy: {
                '/api/*': 'http://localhost:7003/'
                //'/api':  {
                //   target: 'http://localhost:7003/'
                //}
            }
      }
    };
    
    
    //在cmd敲入命令运行webpack-dev-server,例如
    >>webpack-dev-server --inline --hot --progress --colors --port 8082
    //也可以在package.json的scripts配置命令方便启动服务或者配置new WebpackDevServer来启动服务
    
    
    
    
    //在页面请求本地接口即可,devServer会拦截/api/的请求
    //action.js
    fetch(`/api/map?exhibition=${data._id}`)
          .then(res => res.json())
          .then(data => {
            let sourceId = data[0]._id;
            return fetch(`/api/exhibitor/${sourceId}`);
          })
          .then(res => res.json())
          .then(data => {
            return dispatch(addexhibition(data)) ;
          })
          .catch(error => {
            console.log(error);
          })
    
    

    4、node+express+webpack+webpack-dev-middleware+http-proxy-middleware

    //webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时.所以我们也可以不用webpack-dev-server,自己来搭一个
    //server.js
    var path = require('path')
    var express = require('express')
    var webpack = require('webpack')
    var proxyMiddleware = require('http-proxy-middleware')
    var webpackConfig = require('./webpack.dev.conf')
    var port = 7003
    // 定义HTTP代理到自定义的API后端
    var proxyTable = {
           '/api': {
            target: 'http://localhost:7003/',
            changeOrigin: true,
            // pathRewrite: {
            //   '^/api': '/api' 
            // }
          },
          '/Uploads': {
            target: 'http://localhost:7003/',
            changeOrigin: true
          }
        }
    
    var app = express()
    var compiler = webpack(webpackConfig)
    
    var devMiddleware = require('webpack-dev-middleware')(compiler, {
      publicPath: webpackConfig.output.publicPath,
      stats: {
        colors: true,
        chunks: false
      }
    })
    
    var hotMiddleware = require('webpack-hot-middleware')(compiler)
    // force page reload when html-webpack-plugin template changes
    compiler.plugin('compilation', function (compilation) {
      compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
        hotMiddleware.publish({ action: 'reload' })
        cb()
      })
    })
    
    // proxy api requests
    Object.keys(proxyTable).forEach(function (context) {
      var options = proxyTable[context]
      if (typeof options === 'string') {
        options = { target: options }
      }
      app.use(proxyMiddleware(context, options))       //使用代理中间件
    })
    
    // handle fallback for HTML5 history API
    app.use(require('connect-history-api-fallback')())
    
    // serve webpack bundle output
    app.use(devMiddleware)
    
    // enable hot-reload and state-preserving
    // compilation error display
    app.use(hotMiddleware)
    
    // serve pure static assets
    var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
    app.use(staticPath, express.static('./static'))
    
    module.exports = app.listen(port, function (err) {
      if (err) {
        console.log(err)
        return
      }
      var uri = 'http://localhost:' + port
      console.log('Listening at ' + uri + '\n')
    
      // when env is testing, don't need open it
      if (process.env.NODE_ENV !== 'testing') {
        opn(uri)
      }
    })
    
    
    

    5、node+proxy-middleware

    //主要针对采用https协议的服务器
    var connect = require('connect');
    var url = require('url');
    var proxy = require('proxy-middleware');
    
    
    var app = connect();
    app.use('/api', proxy(url.parse('https://example.com/endpoint')));
    // now requests to '/api/x/y/z' are proxied to 'https://example.com/endpoint/x/y/z'
    
    
    //same as example above but also uses a short hand string only parameter
    app.use('/api-string-only', proxy('https://example.com/endpoint'));
    

    6、node+cors

    7、node+node-http-proxy

    8、node+node-reverse-proxy

    9、node+reverse-proxy

    //通过pem和SNI 解决了 HTTPS 证书认证的问题
    

    10、http-proxy-middleware+broswerSync+gulp

    相关文章

      网友评论

          本文标题:前端跨域总结(持续更新)

          本文链接:https://www.haomeiwen.com/subject/iypozttx.html