webpack解决本地开发跨域问题

微信扫一扫,分享到朋友圈

webpack解决本地开发跨域问题

在本地开发前端项目时,经常会遇到需要本地请求测试环境或是生产环境地址,但是这样会导致跨域问题,如果我们使用了webpack,通常会通过代理解决跨域问题,那么接下来我们一起看看什么是跨域,使用webpack如何解决该问题,而webpack解决跨域的实现原理。

什么是跨域?

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

那么同源策源具体又阻止了哪些交互呢,如下

  • 1.无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB,既无法跨域去获取另一页面下的缓存数据;

  • 2.无法接触非同源网页的 DOM;

  • 3.无法向非同源地址发送 AJAX 请求;此点也是最关键最重要的一点,通常我们关注的也是如何通过请求跨域获取数据。

如何解决呢

本文着重讲述webpack解决跨域问题,当然解决问题的方式远远不止这一种,其他方式就不在此一一讲述。
话不多说,直接上代码:

  • 在项目根目录config/index.js中

  module.exports = {
    dev: {
      // Paths
      assetsSubDirectory: 'static',
      assetsPublicPath: '/',
      proxyTable: {
        '/api': {
          target: 'https://hkapp-sit.xxxxxxxx.com',
          pathRewrite: {'^/api': '/api'},
          changeOrigin: true,     // target是域名的话,需要这个参数,
          secure: false,          // 设置支持https协议的代理
        }
      },
      ...
  }
复制代码

主要是配置dev对象下proxyTable对象,’/api’表示匹配所有以/api开始的路径,若匹配中了,执行该字段下的配置:

  • target: 表示目标资源的地址;

  • pathRewrite: 表示对该字段的重写;

  • changeOrigin:该字段的主要作用是将请求头中host字段改写成target,以防服务器会对host字段做逻辑判断;

  • secure: 设置HTTPS协议的代理,由于target默认不支持https协议,需要将该字段设置为false;
    然后在打包配置文件夹下build下webpack.dev.conf.js引入之前请求配置:

const devWebpackConfig = merge(baseWebpackConfig, {
     ...
    devServer: {
      ...
      proxy: config.dev.proxyTable,
      ...
    }
    ...
}
复制代码

是不是到这个就大功告成了呢,原先我也天真的以为是的,但是在实际本地开发中,还是疯狂报跨域请求出错,后来通过某度我才知道,还缺了关键一步,等登等登:
在config文件夹下dev.env.js文件:

  const merge = require('webpack-merge')
  const prodEnv = require('./prod.env')

  module.exports = merge(prodEnv, {
    NODE_ENV: '"development"',
    api: '"/api/"'
  })
复制代码

将需要匹配的接口字段设置成开发环境下的变量,在利用axios请求时设置url,在url前统一加上变量api,实现请求匹配代理,这样就真正大功告成了。。。
至于为啥'”/api”‘是双层引号,大胆猜测,在webpack,合并配置merge时,内部是使用eval语句解析的;

最后我们来探索下webpack是如何实现跨域功能的呢

通俗的描述为:通过一些方法设置代理,在请求发送(接收)之前加入中间层,将不同的域名转换成相同的就解决了跨域的问题。

举个栗子:客户端发送请求时不直接到服务器而是先到代理的中间层在这里将 localhost:8080的这个域名转换成 www.xiaoxuosheng.com,再将请求发送到服务器这样在服务器端收到的请求就是使用的www.xiaoxuosheng.com域名同理,当服务器返回数据的时候,也是先到代理的中间层将www.xiaoxuosheng.com转换成www.xiaoxuosheng.com;这样在客户端也是在相同域名下访问的了。

这里就需要提到http-proxy-middlerware这个中间件了:

  import { HttpProxyMiddleware } from './http-proxy-middleware';
  import { Filter, Options } from './types';

  export function createProxyMiddleware(context: Filter | Options, options?: Options) {
    const { middleware } = new HttpProxyMiddleware(context, options);
    return middleware;
  }

  export { Filter, Options, RequestHandler } from './types';
复制代码

实际上通过HttpProxyMiddleware类创建新对象并返回新对象下的middleware对象,那么我们再看看HttpProxyMiddleware类的具体实现:

  public middleware: RequestHandler = async (
      req: Request,
      res: Response,
      next: express.NextFunction
    ) => {
      if (this.shouldProxy(this.config.context, req)) {
        try {
          const activeProxyOptions = await this.prepareProxyRequest(req);
          this.proxy.web(req, res, activeProxyOptions);
        } catch (err) {
          next(err);
        }
      } else {
        next();
      }

      if (this.proxyOptions.ws === true) {
        // use initial request to access the server object to subscribe to http upgrade event
        this.catchUpgradeRequest((req.connection as any).server);
      }
  };
复制代码

根据上面代码可以知道,核心部分就是 this.proxy.web(req, res, activeProxyOptions) 这行代码,而this.proxy又是通过this.proxy = httpProxy.createProxyServer({})创建,最终是利用了httpProxy库,那继续往下看httpProxy的核心内容:

function ProxyServer(options) {
  ...
  ...
  this.web = this.proxyRequest           = createRightProxy('web')(options);
  this.webPasses = Object.keys(web).map(function(pass) {
    return web[pass];
  });
  ...
  ...
}

// 而createRightProxy的实现 :

// 参数 type 用于区分请求类型,'web' 为普通 http, https 请求,'ws' 为 websocket 请求
function createRightProxy(type) {
  // 参数 options 为全局配置项
  return function(options) {
    return function(req, res /*, [head], [opts] */) {
      // passes 任务队列
      var passes = (type === 'ws') ? this.wsPasses : this.webPasses,
          args = [].slice.call(arguments),
          cntr = args.length - 1,
          head, cbl;
      // 解析回调函数
      if(typeof args[cntr] === 'function') {
        cbl = args[cntr];
        cntr--;
      }
      // 混入该请求中特定的配置项 opts
      var requestOptions = options;
      if(
        !(args[cntr] instanceof Buffer) &&
        args[cntr] !== res
      ) {
        requestOptions = extend({}, options);
        extend(requestOptions, args[cntr]);
        cntr--;
      }
      // head
      if(args[cntr] instanceof Buffer) {
        head = args[cntr];
      }

      // 请求的目标地址
      ['target', 'forward'].forEach(function(e) {
        if (typeof requestOptions[e] === 'string')
          requestOptions[e] = parse_url(requestOptions[e]);
      });
      if (!requestOptions.target && !requestOptions.forward) {
        return this.emit('error', new Error('Must provide a proper URL as target'));
      }

      // 挨个执行任务队列,处理消息头,转发请求
      for(var i=0; i < passes.length; i++) {
        if(passes[i](req, res, requestOptions, head, this, cbl)) {
          break;
        }
      }
    };
  };
}
复制代码

后面的内容有点晦涩难懂,大家想了解更多更深层次的解析,如代理的建立,请求事件的监听、转发,请求队列的执行部分,可访问最下方node-http-proxy源码解析链接

参考资料

微信扫一扫,分享到朋友圈

webpack解决本地开发跨域问题

webpack中include和exclude

上一篇

后疫情时代,失去主动权的投资机构该如何破局?——募资云加速助力机构募资

下一篇

你也可能喜欢

webpack解决本地开发跨域问题

长按储存图像,分享给朋友