http-proxy-middleware在egg项目中的应用

4,672 阅读3分钟

简介

例如:我们当前主机为http://localhost:3000/,现在我们有一个需求,如果我们请求/api,我们不希望由3000来处理这个请求,而希望由另一台服务器来处理这个请求怎么办?我们可以通过http-proxy-middleware将请求代理转发到其他服务器的中间件。

安装

npm install --save-dev http-proxy-middleware

核心概念

proxy([context,] config)

var proxy = require('http-proxy-middleware');

var apiProxy = proxy('/api', { target: 'http://www.example.org' });
//                   \____/   \_____________________________/
//                     |                    |
//                   context             options

// 'apiProxy' is now ready to be used as middleware in a server.
  • context: 确定应将哪些请求代理到目标主机
  • options.target: target 目标服务器

匹配规则

         foo://example.com:8042/over/there?name=ferret#nose
         \_/   \______________/\_________/ \_________/ \__/
          |           |            |            |        |
       scheme     authority       path        query   fragment
  • 路径匹配

    • proxy({...}) - 匹配任何路径,所有请求都会被代理
    • proxy('/', {...}) - 匹配任何路径,所有请求都会被代理
    • proxy('/api', {...}) - 匹配以/api开头的路径
  • 多路径匹配

    • proxy(['/api', '/ajax', '/someotherpath'], {...})
  • 通配符路径匹配

    • proxy('**', {...}) 匹配任何路径,所有请求都会被代理

    • proxy('**/*.html', {...}) 匹配任何以.html结尾的路径

    • proxy('/*.html', {...}) 直接在绝对路径下匹配路径

    • proxy('/api/**/*.html', {...}) 匹配/api路径中以.html结尾的请求

    • proxy(['/api/**', '/ajax/**'], {...}) 结合多种模式

    • proxy(['/api/**', '!**/bad.json'], {...}) 排除

    Note: 在多路径匹配中,不能同时使用字符串路径和通配符路径。

  • 自定义匹配

为了实现完全控制,您可以提供自定义功能来确定应否代理哪些请求。

/**
 * @return {Boolean}
 */
var filter = function(pathname, req) {
  return pathname.match('^/api') && req.method === 'GET';
};

var apiProxy = proxy(filter, { target: 'http://www.example.org' });

配置项

http-proxy-middleware options

  • option.pathRewrite: object/function, 重写目标网址路径。对象键将用作RegEx来匹配路径

    // rewrite path
    pathRewrite: {'^/old/api' : '/new/api'}
    
    // remove path
    pathRewrite: {'^/remove/api' : ''}
    
    // add base path
    pathRewrite: {'^/' : '/basepath/'}
    
    // custom rewriting
    pathRewrite: function (path, req) { return path.replace('/api', '/base/api') }
    
  • option.router: object/function,重新定位option.target以用于特定请求

    // 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
    router: function(req) {
        return 'http://localhost:8004';
    }
    
  • option.logLevel: string, ['debug', 'info', 'warn', 'error', 'silent']. Default: 'info'

  • option.logProvider: function, 修改或替换日志. Default: console.

    // simple replace
    function logProvider(provider) {
      // replace the default console log provider.
      return require('winston');
    }
    
    // verbose replacement
    function logProvider(provider) {
      var logger = new (require('winston')).Logger();
    
      var myCustomProvider = {
        log: logger.log,
        debug: logger.debug,
        info: logger.info,
        warn: logger.warn,
        error: logger.error
      };
      return myCustomProvider;
    }
    

http-proxy events

  • option.onError: function, 订阅http-proxy的错误事件以进行自定义错误处理

    function onError(err, req, res) {
      res.writeHead(500, {
        'Content-Type': 'text/plain'
      });
      res.end(
        'Something went wrong. And we are reporting a custom error message.'
      );
    }
    
  • option.onProxyRes: function, 订阅http-proxyproxyRes事件

    function onProxyRes(proxyRes, req, res) {
      proxyRes.headers['x-added'] = 'foobar'; // add new header to response
      delete proxyRes.headers['x-removed']; // remove header from response
    }
    
  • option.onProxyReq: function, 订阅http-proxyproxyReq事件

    function onProxyReq(proxyReq, req, res) {
      // add custom header to request
      proxyReq.setHeader('x-added', 'foobar');
      // or log the req
    }
    
  • option.onProxyReqWs: function, 订阅 http-proxyproxyReqWs 事件

    function onProxyReqWs(proxyReq, req, socket, options, head) {
      // add custom header
      proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
    }
    
  • option.onOpen: function, 订阅 http-proxyopen 事件

    function onOpen(proxySocket) {
      // listen for messages coming FROM the target here
      proxySocket.on('data', hybiParseAndLogMessage);
    }
    
  • option.onClose: function, 订阅 http-proxyclose 事件

    function onClose(res, socket, head) {
      // view disconnected websocket connections
      console.log('Client disconnected');
    }
    

http-proxy options

基础的http-proxy库提供以下选项。 http-proxy library.

在egg项目中应用

因为http-proxy-middleware不是标准的koa中间件,需要使用koa2-connect包装一下


// app/middleware/http_proxy.ts

import proxy from 'http-proxy-middleware';
import k2c from 'koa2-connect';
import pathToRegexp from 'path-to-regexp';

export default (options) => {
  // test1代理
  const test1Proxy = k2c(proxy({
    target: 'https://test1.com.cn',
    changeOrigin: true,
    pathRewrite: {
      '/proxy/test': ''
    },
    onProxyReq(proxyReq, req, res) {
      proxyReq.setHeader('cookie', req.headers.cookie);
    }
  }));
  
  const test2Proxy = .....

  return async function httpProxy(ctx, next) {
    if (pathToRegexp('/proxy/test/(.*)').exec(ctx.request.url)) {
      test1Proxy(ctx, next);
    }
    if(...){
       test2Proxy(ctx, next) 
    }
    await next();
  };
};

当然需要在 bodyparser 之前引入

// app.ts
import { Application } from 'egg';

export default (app: Application) => {
  app.config.coreMiddleware.unshift('httpProxy');
};