💻 一个请求代理服务器

676 阅读2分钟

一、为什么需要?

当有线上bug的时候,测试一般是看现象,开发是看接口和数据。但是由于线上没有相关的调试断点,开发定位问题的时候相对困难。其实环境的本质区别就是数据的不同,如果能把线上的接口请求数据,弄到本地,就可以快速定位问题。

二、开发用到的包

  • koa
  • koa-proxy
  • browser-sync

三、实现思路

  1. 定义一个配置文件,配置对应的代理数据及相关的辅助数据
  2. 使用koa启动一个服务器处理请求和静态资源
  3. 使用browser-sync启动一个服务器处理资源监听和页面刷新

四、代码实现

1.配置文件

  • config.json
{
  "server": {
    // 浏览器默认打开的页面资源
    "opnPath": {
      "local": "/public/index.html",
      "build": "/product/demo/index.html"
    },
    // 资源监听数据的路径
    "livereload": {
      "watch": "./public/*"
    },
    // 代理配置
    "proxy": [
      {
        "method": "post",
        // 路由
        "route": "/proxy/xxx",
        // 传给 koa-proxy处理的参数
        "options": {
          "host": "https://xxx.com",
          "url": "https://xxx.com/xxx",
          "requestOptions": {
            "strictSSL": false
          }
        }
      }
    ]
  }
}

2.服务器类

  • serve.js
const path = require('path');
const http = require('http');
const Koa = require('koa');
const serve = require('koa-static');
const bodyParser = require('koa-bodyparser');
const koaProxy = require('koa-proxy');
const Router = require('@koa/router');
const browserSync = require('browser-sync');

class Server {
  constructor(options) {
    this.$options = options;
    this.cwd = options.cwd || '';
    this.port = options.port;

    this.app = new Koa();
    
    // 一些配置项
    this.config = {};
    this.routerStore = [];
    this.proxy = koaProxy;
  }

  init () {
    const currentPath = path.join(process.cwd(), this.cwd);

    // 启动一个静态资源服务器,监听路径为 currentPath
    this.app.use(serve(currentPath));
  }

  setConfig(options) {
    Object.assign(this.config, options);
  }

  // 注册代理路由
  registerRouter(method, route, middlewares) {
    if (arguments.length !== 3) {
      throw new Error('registerRouter params error.');
    }

    // 收集对应的路径、方法、中间件
    this.routerStore.push({
      path: route,
      method,
      middlewares
    })
  }

  // 同步更新浏览器
  browserSync(options) {
    const liveReloadConfig = this.config.livereload || {};
    const bs = browserSync.create();

    bs.init(Object.assign({
      https: this.$options.ssl,
      open: 'external',
      port: 12306,
      notify: false,
      proxy: options.proxy
    }, liveReloadConfig.init));

    // 监听对应的文件路径,有改变1s后 reload
    bs.watch(liveReloadConfig.watch, {
      interval: 1000
    }).on('change', bs.reload);
  }

  start() {
    this.init();

    const router = new Router();
    // 处理代理请求 router
    const proxyRouter = new Router();
    // 代理请求的收集器
    const routerStore = this.routerStore;
    const opnPath = this.config.opnPath || '';

    routerStore.forEach(rs => {
      /**
          rs = {
            path: rpath, //route
            method,
            middlewares
          }
       */

      let middlewares = rs.middlewares;

      if (!Array.isArray(middlewares)) {
        middlewares = [middlewares];
      }

      // koa-proxy 执行返回的函数名称为 proxy
      const proxyFunc = middlewares.filter(fn => fn.name === 'proxy');
      if (proxyFunc.length > 0) {
        if (proxyFunc.length === middlewares.length) {
          // router.post('/proxy/Trans/', **处理中间件**);
          proxyRouter[rs.method](rs.path, ...middlewares);
        } else {
          throw new Error('不能同时存在proxy');
        }
      } else {
        // 如果没有使用koa-proxy的正常处理对应的路由
        router[rs.method](rs.path, ...middlewares);
      }
    })

    // 代理路由
    this.app.use(proxyRouter.routes()).use(proxyRouter.allowedMethods());
    // body parser
    this.app.use(bodyParser());
    // 正常路由
    this.app.use(router.routes()).use(router.allowedMethods());

    const port = parseInt(this.port, 10);
    let server = null;
    let listenURL = '';

    listenURL = `http://127.0.0.1:${port}`;
    server = http.createServer(this.app.callback());

    const opnURL = `${listenURL}${opnPath}`;

    server.listen(port, () => {
      console.log(`Server running at ${listenURL}`);
      this.browserSync({ proxy: opnURL })
    })
  }
}

module.exports = Server;

3.入口文件

  • index.js
const path = require('path');
const Server = require('./lib/server.js');
const config = require('./config.json')['server'];

function preServerProcess(config, context) {
  // proxy为koa-proxy
  const proxy = context.proxy;
  // 获取当前的执行环境 【 local/build 】
  const serverEnv = process.env.SERVER_ENV;
  const cwd = process.cwd();

  // 设置一些配置项
  context.setConfig({
    port: config.port,
    // 默认打开的页面
    opnPath: config.opnPath[serverEnv] || '',
    // 自动刷新
    livereload: {
      watch: path.join(cwd, config.livereload.watch),
      init: config.livereload.init || {}
    }
  });

  // 处理代理配置
  config.proxy.forEach((p) => {
    // proxy(p.options) 返回一个middleware
    context.registerRouter(p.method, p.route, proxy(p.options));
  });
}


const arg = {
  cwd: '',
  port: 12345,
}
// 实例化koa服务器
const serve = new Server(arg);
// 进行一些预先的处理
preServerProcess(config, serve);
// 启动服务器
serve.start();

五、拓展

这个小的服务器因为功能是独立的,只负责资源的请求和监听,所以可以作为插件级别去处理

github.com/xuyede/serv…