hc-proxy

honeycomb api proxy express middleware.

Usage no npm install needed!

<script type="module">
  import hcProxy from 'https://cdn.skypack.dev/hc-proxy';
</script>

README

hc-proxy

api代理模块,node server端代理请求后端service用。

以honeycomb项目为例进行配置

  1. 确定honeycomb项目的启动端口和prefix,比如: 项目的启动端口为8001,prefix为 'example'
  2. 整理远程调用的服务,给每个服务起个英文名称,比如: 上面的两个服务 视频服务(video) 音乐服务(music) 聊天服务(chat)
  3. 配置在router.js中,进行如下配置
  4. 请注意,为了确保安全,所有api只支持白名单
// router.js
const app = require('./app');
const Proxy = require('hc-proxy');
const proxyInstance = new Proxy({
  service: {
    video: {
      endpoint: 'http://localhost:7001/',
      client: 'http',
      api: [
        '/api/aaa',
        '/api/c/d'
      ]
    },
    music: {
      endpoint: 'http://192.168.1.1:7001/',
      client: 'http',
      api: [
        '/api/bbb'
      ]
    }
  },
  headers: [
    'x-csrf-token',
    'X-Operator'
  ]
});

// 代理的接口前缀  /api/proxy, 可以自定义
proxyInstance.setProxyPrefix('/api/proxy');

module.exports = function (router) {
  proxyInstance.mount(router, app);
};

配置完成后访问地址

访问地址为:

${服务地址:服务端口/prefix} + ${proxyPrefix} + ${被代理服务名} + ${具体api}

上面的配置案例里:

1. 视频服务:   curl http://localhost:8001/example/api/proxy/video/api/aaa => http://localhost:7001/api/aaa
2. 视频服务:   curl http://localhost:8001/example/api/proxy/video/api/c/somewhat?xxx=123 => http://localhost:7001/api/c/somewhat?xxx=123
3. 音乐服务:   curl http://localhost:8001/example/api/proxy/music/api/bbb => http://192.168.1.1:7001/api/bbb

代理websocket服务

// router.js
const app = require('./app');
const Proxy = require('hc-proxy');
const proxyInstance = new Proxy({
  service: {
    music: {
      endpoint: 'http://192.168.1.1:7001/',
      client: 'http',
      api: [
        '/api/bbb'
      ]
    },
    chat: {
      endpoint: 'http://localhost:7001/',
      client: 'websocket',
      api: [
        '/ws/a'
      ]
    }
  },
  headers: [
    'x-csrf-token',
    'X-Operator'
  ]
});

proxyInstance.setProxyPrefix('/api/proxy');

module.exports = function (router) {
  proxyInstance.mount(router, app);
};

websocket配置完成后访问地址

访问地址为 ${honeycomb服务地址:honeycomb服务端口/honeycomb的prefix} + ${proxyPrefix} + ${被代理服务名} + ${具体api}

1. 音乐服务:   curl http://localhost:8001/example/api/proxy/music/api/bbb => http://192.168.1.1:7001/api/bbb
2. 聊天服务:   curl http://localhost:8001/example/api/proxy/chat/ws/somewhat => http://localhost:7001/ws/somewhat      // 支持websocket

代理文件上传

// router.js
const app = require('./app');
const Proxy = require('hc-proxy');
const proxyInstance = new Proxy({
  service: {
    music: {
      endpoint: 'http://192.168.1.1:7001/',
      client: 'http',
      api: [
        {
          path: '/api/404',
          return: 404
        },
        {
          path: '/api/upload',
          file: true
        },
        {
          path: '/api/upload_limited',
          file: {
            maxFileSize: 100        // 100B
          },
          beforeRequest: (req, options, config) => {},
          beforeResponse: (req, res, data) => {}
        }
      ]
    }
  },
  headers: [
    'x-csrf-token',
    'X-Operator'
  ]
});

proxyInstance.setProxyPrefix('/api/proxy');

module.exports = function (router) {
  proxyInstance.mount(router, app);
};

文件上传代理效果

1. 上传文件1:   curl http://localhost:8001/example/api/proxy/music/api/upload => http://192.168.1.1:7001/api/upload
2. 上传文件2:   curl http://localhost:8001/example/api/proxy/music/api/upload_limited => http://192.168.1.1:7001/api/upload_limited

API document

约定:

  • proxy挂在的http服务称为 "代理服务"
  • 被proxy代理的远端服务称为 "远端服务"

new Proxy(options)

options Object

options.service 详情

{
  ${serviceCode}: {
    /* 每个远端服务的服务地址,如: 'http://localhost:7001' */
    endpoint: ${endpoint},
    accessKeyId: ${accessKeyId},
    accessKeySecret: ${accessKeySecret},
    /* 同 hc-service-client 配置,见文档: https://www.npmjs.com/package/hc-service-client */
    headerExtension: ${headerExtension},
    /* 选填,透传的header列表,同 hc-service-client 配置,见文档: https://www.npmjs.com/package/hc-service-client */
    headers: {Array},
    /* 可选,发起请求的agent,目前只支持'appClient' / 'http' / 'websocket' / 'serviceWebsocket',默认为'appClient',其中 appClient 和 serviceWebsocket 带了honeycomb体系中的签名逻辑 */
    client: ${client},
    /* 接口超时时间,单位毫秒 */
    timeout: ${timeout},
    /* 可选,delete方法使用querystring代理, 默认为true */
    useQuerystringInDelete: ${useQuerystringInDelete}, // 只有 appClient / urllib 模式有效
    /* 可选,用户覆盖的urllibOption,覆盖系统默认值,优先级: service.api.urllibOption > service.urllibOption > hc-proxy默认设置 */
    urllibOption: {Object},                     // 只有 appClient / urllib 模式有效
    /* 覆盖转发时的5XX的errorCode */
    defaultErrorCode: {Number}
    /* 排除列表, 不代理的接口 */
    exclude: {Array}
    /* 路由前缀 */
    routePrefix: {String} 
    /* 是否开启路径支持正则匹配, 默认关闭,开启请确保安全 */
    enablePathWithMatch: {Boolean} false
    api: [
      /* 接口配置可以是简单的一个string */
      '${ApiPathString}',
      {
        /* api访问的path */
        path: {String}
        /* 如若定义,会覆盖proxyPrefix, 给特殊场景定义接口路径用 */
        route: {String}
        /* 接口方法 */
        method: 'GET|POST|PUT|DELETE|PATCH'
        /* 接口超时时间, 单位毫秒,覆盖上面配置的服务的通用超时,通常用来设置特殊接口的超时时长 */
        timeout: {Number},
        /* 是否透传, 开启透传之后,body不落地,直接pipe到远端; 开启pipe之后,body内容不参与签名(签名里的body='') */
        pipe: true,
        /* 请求的默认querystring信息, 用于配置默认的query参数(代理请求时自动加上) */
        defaultQuery: {Object|String},
        /**
         * 发起请求前的hook, beforeRequest(req, apiReq, config) 
         *    @param req {Request} 客户端请求对象request,
         *    @param options {Object} urlib的配置信息,
         *    @param config {Object} api的配置信息
         */
        beforeRequest: {Function(req, options, config)},
        /**
         * 请求从服务接口返回之后的hook,afterResponse(req, res, apiRes) 
         *  @param req {Request} 客户端请求的request对象,
         *  @param res {Response} proxy端请求的response对象,
         *  @param data {Response} 返回数据
         *  @return data
         */
        beforeResponse: {Function(req, res, data)}, 
        /* delete方法使用querystring代理, 默认为true */
        useQuerystringInDelete: {Boolean},
        /** 用户覆盖的urllibOption,覆盖系统默认值,优先级: service.api.urllibOption > service.urllibOption > hc-proxy默认设置 */
        urllibOption: {Object}
      }
    ]
  }
}

通用配置:

options.headers Array

options.headers 用于声明proxy需要转发的header。 默认情况下,proxy不转发客户端过来的header,只有在proxyHeaders中配置的header才会被转发。

proxy.setProxyPrefix(proxyPrefix)

  • proxyPrefix

setProxyPrefix方法用于指定 hc-proxy 挂载在代理服务上的总前缀

如: 默认 proxyPrefix = /api/proxy 则: 所有请求远端服务的请求,格式为 ${localHttpServer}/api/proxy/${serviceCode}/${remoteApi}

proxy.mount(router, app)

将proxy的配置挂在到代理服务。

  • router: Express Router instance
  • app: app.server 是一个 http.Server 的实例,honeycomb中,直接 require('./app') 能获得

更多例子

'use strict';

const app = require('./app');
const Proxy = require('hc-proxy');
const proxyInstance = new Proxy({
  service: {
    otm: {
      endpoint: 'http://dev.dtboost.biz.aliyun.test/otm_v2',                           // 自动截取最后的'/'
      client: 'appClient',                                                                // 默认appClient
      timeout: 10000,                                                                     // 默认60000
      api: [
        '/api/a',                                                                         // 支持 * 代理某个path下的所有api
        '/otm_v2/api/entities/list',                                                      // 代理这个 url 的 GET POST PUT DELETE 方法
        {path: '/otm_v2/api/entities/list', method: 'GET'},                                // 只代理 GET 方法
        {path: '/otm_v2/api/entities/list', client: 'appClient'},                          // 显式指定 method
        {path: '/otm_v2/api/schemas', method: ['GET', 'POST']},                            // 只支持 GET POST 方法
        {host: 'http://taobao.com/api', path: '/tag_factory_v2/api/:id', method: 'GET'},   // 不同域时,指定host
        {path: 'http://taobao.com/api', route: '/api/proxy/taobao_api'},                   // 指定route的代理,不指定route时,所有代理接口请求 /<-app_name->/api/proxy 获得代理
        '/otm_v2/api/tags/query_tags_entity_pagenum',                                     // 
        {path: '/otm_v2/api/schemas/:id', route: '/api/proxy/schemas/:id'}                 // 带参数的url,保证route和url中的param一致,也可以不填route
      ]
    },
    websocket: {
      endpoint: 'http://dev.dtboost.biz.aliyun.test/websocket',               // 远程的websocket地址
      client: 'websocket',                                                       // 选择websocket作为连接的client
      api: [
        '/ws'                                                                    // 配置连接路径,不在的路径返回404
      ]
    }
  },
  headers: [
    'x-csrf-token',
    'X-Operator'
  ]
});

proxyInstance.setProxyPrefix('/api/proxy');

module.exports = function (router) {
  proxyInstance.mount(router, app);
};

DEBUG [代理没生效?]

在命令行启动命令前加入DEBUG=hc-proxy

// 以启动命令为 honeycomb start 为例
DEBUG=hc-proxy honeycomb start

// ...
// hc-proxy [GET] /api/proxy/urllib_proxy/urllib -> http://localhost:58062/urllib +0ms
// hc-proxy [POST] /api/proxy/urllib_proxy/urllib -> http://localhost:58062/urllib +3ms
// hc-proxy [DELETE] /api/proxy/urllib_proxy/urllib -> http://localhost:58062/urllib +1ms
// hc-proxy [PUT] /api/proxy/urllib_proxy/urllib -> http://localhost:58062/urllib +0ms
// ...

作用

  • 本地开发时,可以使用这个代理访问远程其它服务(如otm)的问题;
  • 非本地开发环境时,可以不使用代理,而直接访问类似'/otm_v2/api/xxx',以减小内部调用开销,由前端自行控制;