webpack-dev-server原理解析及其中跨域解决方法

webpack proxy ,就是 webpack 提供的解决跨域的方案。其基本行为是接受客户端发送的请求后转发给其他的服务器,目的是为了解决在开发模式下的跨域问题。

原理

webpack中的proxy 工作原理是利用了 http-proxy-middleware 这个http 代理中间件,实现将请求转发给其他的服务器。

如下:在开发阶段,本地地址是 Http://loaclhost:3000 , 该浏览器发送一个前缀带有 /api 标识的向服务器请求数据,但是这个服务器只是将这个请求转发给另一台服务器:

const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();

app.use('/api', proxy({target: 'http://www.test.org', changeOrigin: true}));
app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.test.org/api/foo/bar

在开发阶段,webpack-dev-server 会自动启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost 的一个端口上的,而后端服务器又是运行在另一个地址上.。

所以在开发阶段中,由于浏览器的同源策略,当本地浏览器访问后端服务器的时候就会出现跨域请求资源的问题。

在这里插入图片描述
通过设置 webpack proxy 实现代理请求后,相当于浏览器和服务器之间添加了一个代理服务器。
当本地发送请求的时候,中间服务器会接受这个情求,并将这个请求转发给目标服务器(也就是后端服务器),目标服务器返回数据后,中间服务器又会将数据返回给浏览器,当中间服务器将数据返回给服务器的时候,它们两者是同源的,并不会存在跨域的问题。

服务器和服务器之间是不会存在跨域资源的问题的。

webpack-dev-server

webpack 提供代理服务器的工具是webpack-dev-server,但只适用于开发阶段,线上要实现代理一般通过 nginx 来配置。

webpack 中的服务器工具 webpack-dev-server,实质上是启动了一个 express 服务器。

proxy 代理是利用 http-proxy-middleware 这个http代理中间件实现将请求转发给其他服务器。
(vite 是用的 http-proxy,其实 http-proxy-middleware 也是基于 http-proxy 的)

背后其实都是使用 node 来启动 server 服务器,这也是为什么我们常说这种代理只能在开发阶段使用,因为 build 生产包时我们并不会打包一个 node 服务器进去,线上要实现代理一般直接通过 nginx 来配置。

可以在webpack 配置对象属性中通过 devServer 属性来配置:

// ./webpack.config.js
const path = require('path')

module.exports = {
    // ...
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 9000,
        proxy: {
            '/api': {
                target: 'https://api.github.com'
            }
        }
        // ...
    }
}

在这里插入图片描述

webpack-dev-server常用的配置项

Proxy代理

它的目的是设置代理来解决跨域访问的问题。举例:
我们的一个api请求地址(也就是服务器地址)是 http://localhost:8888
但是本地启动服务器的域名是 http://localhost:8000
这个时候发送网络请求就会出现跨域的问题(端口不同)。
所以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了。

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {// 以 /api 开头的请求,会转发到下面的 target 配置
        target: 'http://localhost:8888',// 目标服务器
         pathRewrite: { 
          "^/api": "", // 重写路径为空,即请求路径中没有/api字符串
          "^/api": "/abcd" // 重写路径为abcd,即将请求路径中的/api字符串替换成/abcd
        },
        secure: false,// https接口,需要配置这个参数
        changeOrigin: true,// 将请求头中的host 配置为 target
      },
    },
     host: '0.0.0.0', //用于指定devDerve使用的host,如果你希望服务器外部可以访问,设定如 host: '0.0.0.0'
     https: true, // 默认情况下dev-server使用http协议,通过配置可以支持https,
     // 注意:默认使用自签名证书,也可以配置自己的证书
     // https: {
     //     ca: './path/to/server.pem',
     //     pfx: './path/to/server.pfx',
     //     key: './path/to/server.key',
     //     cert: './path/to/server.crt',
     //     passphrase: 'webpack-dev-server',
     //     requestCert: true,
     // },
     // 开启热模块替换
     hot: true,
  },
};
  • target:表示的是代理到的目标地址(也就是要访问的服务器地址),比如 /api会被代理到 http://localhost:8888/api
  • pathRewrite:默认情况下,我们的 /api 也会被写入到URL中,即:http://localhost:8888/api, 如果希望删除,可以使用pathRewrite比如:
    配置成 "^/api": "",那么/api/user => http://localhost:8888/user,地址中没有/api,因为重新路径是空的;
    配置成"^/api": "/abcd" ,那么/api/user => http://localhost:8888/abcd/user,即将请求路径中的/api字符串替换成/abcd
  • secure: 默认情况下,不接受在 HTTPS 上运行且证书无效的后端服务器。 https接口,需要配置这个参数为false;
  • changeOrigin:它表示是否更新代理后请求的headers中host地址,一般设置为true

proxy配置中 changeOrigin: true,// 将请求头中的host 配置为 target。
那为什么要更改请求头中的host呢?

因为一台服务器上可以部署多个项目,当我们发送请求时,DNS 会将域名解析为 IP 地址,此时,也就确定了我们目标服务器,那如何确定我们要访问的是哪个项目呢?这就需要用到 host 字段了。

请求头中Host用来指定请求的 域名/ip地址和端口号,通常用于指定服务器的地址,端口号可以省略,省略时默认为80端口。

此时我们将host指定为a.com就会访问到淘宝服务了。
在这里插入图片描述
所以如果一台服务器上部署多个项目时,我们需要更改请求头中的host,来指定我们要访问的项目。

一台服务器只有一个项目时,我们可以不用更改请求头中的host,因为DNS解析后,就确定了我们要访问的项目。

但为了以后用起来方便,就统一将这个配置项changeOrigin设置为true。

host

devServer.host 配置项?于配置 DevServer 服务监听的地址,默认使? 8080 端?。 如果 8080 端? 已经被其它程序占有就使? 8081,如果 8081 还是被占?就使? 8082,以此类推

例如:
你想要局域?中的其它设备访问你本地的服务,可以在启动 DevServer 时带上 --host 0.0.0.0
也可以在webpack配置项devServer.host中设置0.0.0.0
host 的默认值是 127.0.0.1 即只有本地可以访问 DevServer 的 HTTP 服务。

localhost 和 0.0.0.0 的区别:

  • localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1。
  • 127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收。

正常的数据库包经过 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ,而回环地址,是在网络层直接就被获取到了,是不会经过数据链路层和物理层的。

比如:
我们监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的。
0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序,比如我们监听 0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的

port、open、compress

devServer.port设置监听的端口,默认情况下是8080,不能设置为null,可以设置自动为auto

module.exports = {
  //...
  devServer: {
    port: 8080, 
  },
};

devServer.open告诉 dev-server 在服务器已经启动后打开浏览器。设置其为 true 以打开你的默认浏览器。

module.exports = {
  //...
  devServer: {
    open: true,
    //在浏览器中打开指定页面:open: ['/my-page']
    //提供要使用的浏览器名称,而不是默认名称
  // open: {
      //   app: {
      //  name: 'google-chrome',
    //  },
   // },
  },
};

devServer.compress告诉 dev-server 在服务器端启用 gzip 压缩,用于减少服务器向前端传输的数据量,提高浏览的速度。

module.exports = {
  //...
  devServer: {
    compress: true,
  },
};

在这里插入图片描述
参考