如何解决跨域问题(调试期&发布后)

问题的由来

跨域是正式项目的开发中无法回避的问题。

这个问题的起因在于现代浏览器默认都会基于安全原因而阻止跨域的ajax请求,这是现代浏览器中必备的功能,但是往往给开发带来不便。

解决这个问题分为两个部分:调试期间和发布后。

发布后的配置

发布后的配置比较简单,先说发布后的:在nginx等主流Web服务器中都内置了反向代理功能,仅以nginx为例:

server {
  server_name somename.somewhere.com;  # 写上你的虚拟主机域名
  location /api/ {
    proxy_pass https://service.somewhere.com/context/;  # 写上要转发到的地址,可以是http也可以是https,注意不要遗漏了最后的斜杠
    proxy_pass_header Set-Cookie;                           # 带cookie转发
    proxy_set_header Host service.somewhere.com;  # 转发的时候模拟成对方的主机
  }
}

这样,在访问 http://somename.somewhere.com/api 的时候,就会被转发到 https://service.somewhere.com/context 去,并带上所有参数和请求内容。
当然,还有很多可配置的选项,去搜一下就行了,讲nginx反向代理功能的有很多文章。

调试期的配置

调试期间的配置有些复杂,需要使用一个grunt的插件:grunt-connect-proxy,其官方网站在:https://github.com/drewzboto/grunt-connect-proxy 另有一篇英文文章介绍的也很详细:http://www.hierax.org/2014/01/grunt-proxy-setup-for-yeoman.html
这里给出一个中文简要说明,如果遇到问题可以参考上述两个链接。

1. 安装grunt-connect-proxy

npm install grunt-connect-proxy --save-dev

2. 添加proxy任务

在Gruntfile.js中的grunt.initConfig之前,插入一句:
grunt.loadNpmTasks('grunt-connect-proxy');

3. 配置proxy

在grunt.initConfig中的connect配置项下,插入一个子项:

      proxies: [
        {
          context: '/api', // 这是你希望出现在grunt serve服务中的路径,比如这里配置的是http://127.0.0.1:9000/api/
          host: 'www.ngnice.com', // 这是你希望转发到的远端服务器
          port: 80, // 远端服务器端口
          changeOrigin: true, // 建议配置为true,这样它转发时就会把host带过去,比如www.ngnice.com,如果远端服务器使用了虚拟主机的方式配置,该选项通常是必须的。
          rewrite: {
            '^/api/': '/remote_api/'  // 地址映射策略,从context开始算,把前后地址做正则替换,如果远端路径和context相同则不用配置。
          }
        }
      ],

4. 配置middleware

在grunt.initConfig.connect.livereload.options配置项下,插入一个子项(摘自官方例子):

                      middleware: function (connect, options) {
                        if (!Array.isArray(options.base)) {
                            options.base = [options.base];
                        }

                        // 设置代理
                        var middlewares = [require('grunt-connect-proxy/lib/utils').proxyRequest];

                        // 代理每个base目录中的静态文件
                        options.base.forEach(function(base) {
                            middlewares.push(connect.static(base));
                        });

                        // 让目录可被浏览(即:允许枚举文件)
                        var directory = options.directory || options.base[options.base.length - 1];
                        middlewares.push(connect.directory(directory));

                        return middlewares;
                      }

如果你对nodejs的connect框架比较熟,也可以在这里自己写middleware来定制这些行为。

5. 加入启动指令

上述配置都还没有生效,因为最重要的一步还没有做:启动proxy。

  grunt.registerTask('serve', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'bowerInstall',
      'concurrent:server',
      'autoprefixer',
      'configureProxies:server', // *** 务必插入这句 ***
      'connect:livereload',
      'watch'
    ]);
  });

这样,在grunt serve运行的同时就会也把这个proxy插件加载了。这个插件如果正常启动,你会在控制台看到下列信息:

Running "configureProxies:server" (configureProxies) task
Rewrite rule created for: [/^/api// -> /remote_api/].
Proxy created for: /api to www.ngnice.com:80

现在,你到 http://127.0.0.1:9000/api/ 及其子路径的所有请求,都会被转发到 http://www.ngnice.com/remote_api/ 及其子路径去了,调试期间再也不存在跨域问题。