ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

React动态切换本地代理地址(devServer.proxy)

2022-04-20 21:03:02  阅读:262  来源: 互联网

标签:http res devServer React proxy test router 8001


背景需求

在本地跑React项目时,调用的接口往往是跨域的,一般常用的是webpack-dev-server提供的prxoy代理功能。然而在每次切换代理环境后,都需要重新跑项目,对于开发人员来说太麻烦了。如果能够在切换代理环境后,不用重跑项目,岂不是提升了开发体验和减少了项目编译的时间呢?

● webpack.devServer.config.js


'use strict';
const fs = require('fs');
// ...
module.exports = function(proxy, allowedHost) {
    return {
        // ...
        proxy: {
          '/test': {
              target: 'http://47.115.13.227:8001',
              changeOrigin: true,
          },
          '/user': {
              target: 'http://47.115.13.227:8002',
              changeOrigin: true,
          },
        },
    };
};

需求分析

根据项目的背景需求,分析了一下原因,之所以需要重跑项目,是因为修改配置后,webpack不会重新读取我们修改后的代理配置文件(webpack.devServer.config.js)。

那么,这么一分析下来话,想要解决这个问题,有两种思路:

  1. 让webpack监听我们代理的配置文件(webpack.devServer.config.js),一旦文件有修改就重新热加载;
  2. 让webpack实时读取配置文件中的proxy配置,能够在每次代理的时候实时读取,不用每次重新加载。

基于这两种思路,我去查阅了下webpack的官方文档。

查阅文档

devServer.proxy

在查阅了webpack官网中devServer.proxy的内容后,发现这个devServer是用了http-proxy-middleware包去实现的,并且给出了它的官方文档

devs-server-proxy-01.png

http-proxy-middleware

http-proxy-middleware中发现了这样一个router配置参数,意思是可以针对特殊的一些请求重新定位/代理

  • option.router: object/function, re-target option.target for specific requests.
// Use `host` and/or `path` to match requests. First match will be used.
// The order of the configuration matters.
router: {
  'integration.localhost:3000' : 'http://localhost:8001',  // host only
  'staging.localhost:3000'     : 'http://localhost:8002',  // host only
  'localhost:3000/api'         : 'http://localhost:8003',  // host + path
  '/rest'                      : 'http://localhost:8004'   // path only
}

// Custom router function (string target)
router: function(req) {
  return 'http://localhost:8004';
}

// Custom router function (target object)
router: function(req) {
  return {
    protocol: 'https:', // The : is required
    host: 'localhost',
    port: 8004
  };
}

// Asynchronous router function which returns promise
router: async function(req) {
  const url = await doSomeIO();
  return url;
}

其中router可以传递函数并且支持async函数,那么意味着,是不是webpack能够实时读取proxy的配置呢。

验证想法

为了验证这个API,我先搭建了两个node服务,再通过配置webpack.devServer.config.js中的proxy中动态的请求代理地址参数去验证想法。

服务端口8001

如下,搭建端口为8001的node服务有以下功能:

  • /getRouterProxyUrl随机返回80018002端口的代理地址,
  • /test,返回8001 succesed get test word!
const http = require('http');
const server8001 = http.createServer(function(req, res) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "Content-type,Content-Length,Authorization,Accept,X-Requested-Width");
    res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

    let proxyUrl = '';

    if (req.url == "/getRouterProxyUrl") {
        if (Math.random() * 10 > 5) {
            proxyUrl = 'http://47.115.13.227:8001';
        } else {
            proxyUrl = 'http://47.115.13.227:8002';
        }
        res.writeHead(200, {
            'Content-type': 'text/plain;charset=UTF8',
        });
        res.end(proxyUrl);
    } else if (req.url == "/test") {
        res.writeHead(200, { 'Content-type': 'text/plain;charset=UTF8' });
        res.end('8001 succesed get test word!');
    } else {
        res.writeHead(200, { 'Content-type': 'text/plain;charset=UTF8' });
        res.end(`8001 hello,your request url is ${req.url}`);
    }
    console.debug(new Date(), `8001 req.url:${req.url}`);
    console.debug(new Date(), `8001 proxyUrl:${proxyUrl}`);
});

server8001.listen('8001', function() {
    console.log((new Date()) + 'Server is listening on port:', 8001);
})

服务端口8002

如下,端口为8002的node服务有以下功能:

  • /test返回8002 succesed get test word!
const http = require('http');

const server8002 = http.createServer(function(req, res) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "Content-type,Content-Length,Authorization,Accept,X-Requested-Width");
    res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

    if (req.url == "/test") {
        res.writeHead(200, { 'Content-type': 'text/plain;charset=UTF8' });
        res.end('8002 succesed get test word!');
    } else {
        res.writeHead(200, { 'Content-type': 'text/plain;charset=UTF8' });
        res.end(`8002 hello,your request url is ${req.url}`);
    }
    console.debug(new Date(), `8002 req.url:${req.url}`);

});

server8002.listen('8002', function() {
    console.log((new Date()) + 'Server is listening on port:', 8002);
})

配置proxy

webpack.devServer.config.js文件如下,通过getFecth去请求动态的代理地址,router中拿到getFecth中请求到的代理地址再返回

'use strict';
const fs = require('fs');
const http = require('http');
// ...
const getFetch = () => {
    return new Promise((resolve, reject) => {
        http.get('http://47.115.13.227:8001/getRouterProxyUrl', res => {
            let todo = '';
            res.on('data', chunk => {
                todo += chunk;
            });
            res.on('end', () => {
                resolve(todo);
            });
            res.on('error', (error) => {
                reject(error);
            });
        });
    });
};

module.exports = function(proxy, allowedHost) {
    return {
        // ...
       proxy: {
          '/test': {
              target: 'http://localhost:3000',
              changeOrigin: true,
              router: async function() {
                  const url = await getFetch();
                  return url;
              },
          },
      }
    };
};

前端请求

fetch('/test')
  .then(response => {
    if (!response.ok) {
      throw 'Error';
    }
    return response.text();
  })
  .then(res => console.debug(res))
  .catch(err => console.error(err));

请求报错500 Internal Server Error

前端请求后,发现报了500 Internal Server Error 的错误

devs-server-proxy-02.png

为了排除服务端的错误,使用postman对8001端口的/getRouterProxyUrl/test,还有8002端口的/test,均发起请求验证了下,都能正常返回正常的响应数据!

devs-server-proxy-03.png

这就很纳闷,可能还是在router代理地址这里除了问题,但是通过打console.log,发现getFetch是能正常返回数据的。再根据错误提示,可以大致判断是在router里边调用接口导致出错的。

TypeError: Cannot read property 'split' of undefined
    at required (D:\workspace\Web\react-app\node_modules\requires-port\index.js:13:23)
    at Object.common.setupOutgoing (D:\workspace\Web\react-app\node_modules\http-proxy\lib\http-proxy\common.js:101:7)
    at Array.stream (D:\workspace\Web\react-app\node_modules\http-proxy\lib\http-proxy\passes\web-incoming.js:127:14)
    at ProxyServer.<anonymous> (D:\workspace\Web\react-app\node_modules\http-proxy\lib\http-proxy\index.js:81:21)
    at middleware (D:\workspace\Web\react-app\node_modules\http-proxy-middleware\lib\index.js:46:13)
    at handle (D:\workspace\Web\react-app\node_modules\webpack-dev-server\lib\Server.js:322:18)
    at D:\workspace\Web\react-app\node_modules\webpack-dev-server\lib\Server.js:330:47
    at Layer.handle_error (D:\workspace\Web\react-app\node_modules\express\lib\router\layer.js:71:5)
    at trim_prefix (D:\workspace\Web\react-app\node_modules\express\lib\router\index.js:315:13)
    at D:\workspace\Web\react-app\node_modules\express\lib\router\index.js:284:7

再回到http-proxy-middleware的官方文档

  • option.router: object/function, re-target option.target for specific requests.
// Asynchronous router function which returns promise
router: async function(req) {
    const url = await doSomeIO();
    return url;
}

这里 await doSomeIO()引起了我的注意,这个函数命名是不是意味着这里异步的路由函数只能是做一些I/O操作,并不支持调用接口呢?抱着这个疑问,我再查了下资料

devs-server-proxy-04.png

有没有可能是router返回的参数不正确,异步函数中不应该是返回string字符串。
于是代码改为如下,在router函数中调用异步接口,测试后是不报错的。

router: function() {
    getFetch();
    return {
        protocol: 'http:', // The : is required
        host: '47.115.13.227',
        port: 8001,
    };
},

然后再把router改为异步函数,在里边调用getFetch,测试后是报错的!难道router不支持异步函数???离离原上谱!

router: async function() {
    getFetch();
    return {
        protocol: 'http:', // The : is required
        host: '47.115.13.227',
        port: 8001,
    };
},

http-proxy-middleware版本问题

我再去查了下资料https://github.com/chimurai/http-proxy-middleware/issues/153

devs-server-proxy-05.png

发现http-proxy-middleware是在0.21.0版本才支持async router,那么我们再检查下webpack中webpack-dev-server的版本

devs-server-proxy-06.png

好家伙,webpack-dev-server里边引用的http-proxy-middleware中间件是0.19.1版本,我说试了半天咋没有用。那这个async router在咱们项目里就不能用了,要用还得升级下中间件的版本。

支持I/O操作

正当想放弃的时候,刚刚中间件文档提到的router一个用法async doSomeIO,要不试试I/O操作,看下在router里边调用文件流能否成功。

  • test.env.json
{
  "protocol": "http:",
  "host": "47.115.13.227",
  "port": 8002
}
  • proxy
'/test': {
    target: 'http://47.115.13.227:8001',
    changeOrigin: true,
    router: function() {
        const envStr = fs.readFileSync('./test.env.json', 'utf-8');
        const { protocol, host, port } = JSON.parse(envStr);
        return {
            protocol,
            host,
            port,
        };
    },
}

在页面里点击调用fetch("/test"),发现请求通了,并且是从端口为8002的服务器返回的结果!

devs-server-proxy-07.png

果然可以做I/O操作,那如果在不重启项目的情况下,修改test.env.json的代理配置,把port改为8001,再继续调用fetch("/test"),请求的结果会变成8001端口服务器返回的吗?

{
  "protocol": "http:",
  "host": "47.115.13.227",
  "port": 8001
}

修改完test.env.json的配置后,继续调用fetch("/test"),发现请求的结果变成了8001端口服务器返回的了!

devs-server-proxy-08.png

到这一步,就验证了咱们最初的想法——“希望能够在修改代理环境后,不用重新跑项目即可”,是可行的!

实现代码

  • test.env.json
{
  "protocol": "http:",
  "host": "47.115.13.227",
  "port": 8001
}
  • webpack.devServer.config.js
'use strict';
const fs = require('fs');
// ...
const getEvnFilesJSON = (context) =>{
  // const = {req,url,methods } = context;
  // ...可根据req,url,methods来获取不同的代理环境
  const envStr = fs.readFileSync('./test.env.json, 'utf-8');
  const { protocol, host, port } = JSON.parse(envStr);
  return { protocol, host, port } 
};

module.exports = function(proxy, allowedHost) {
    return {
        // ...
        proxy: {
            '/test': {
                target: 'http://47.115.13.227:8001',
                changeOrigin: true,
                router: getEvnFilesJSON
            },
        },
    };
};

总结

  1. webpack的web-dev-server是基于http-proxy-middleware实现的
  2. http-proxy-middleware中 options.routerasync router功能是在0.21.0版本开始支持的
  3. http-proxy-middleware中options.router功能支持I/O操作

后续有时间可以用Electron开发一个管理React本地devServer.Proxy的工具!像SwitchHosts一样!

链接

标签:http,res,devServer,React,proxy,test,router,8001
来源: https://www.cnblogs.com/zzcyeah/p/dev-server-proxy.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有