@nohost/router
Advanced tools
Comparing version 0.7.0 to 0.8.0
113
lib/index.js
const url = require('url'); | ||
const { parse: parseUrl } = require('url'); | ||
const { onClose, getRawHeaders } = require('@nohost/connect'); | ||
const { getServers, isFinished, getJSON } = require('./util'); | ||
const { getServers, isFinished, decode, getJSON } = require('./util'); | ||
const { connect, writeError, writeHead, writeBody } = require('./connect'); | ||
@@ -13,3 +13,50 @@ | ||
const ENV_HEAD = 'x-whistle-nohost-env'; | ||
const NOHOST_RULE = 'x-whistle-rule-value'; | ||
const NOHOST_VALUE = 'x-whistle-key-value'; | ||
const CLIENT_ID = 'x-whistle-client-id'; | ||
const CLIENT_ID_FILTER = 'x-whistle-filter-client-id'; | ||
const ROUTE_RE = /([?&])route=([^?&]+)($|&)/; | ||
const ROUTE_VALUE_RE = /^[\w.:/=+-]{1,100}$/; | ||
const isString = (str) => { | ||
return str && typeof str === 'string'; | ||
}; | ||
const getString = (obj) => { | ||
if (!obj || typeof obj === 'string') { | ||
return obj; | ||
} | ||
if (typeof obj === 'object') { | ||
try { | ||
return JSON.stringify(obj); | ||
} catch (e) {} | ||
} | ||
}; | ||
const addOptions = (req, options) => { | ||
if (!options) { | ||
return; | ||
} | ||
const { headers, isUIRequest } = req; | ||
delete headers['x-whistle-nohost-rule']; | ||
delete headers['x-whistle-nohost-value']; | ||
const addHeader = (name, value) => { | ||
if (isString(value)) { | ||
headers[name] = encodeURIComponent(value); | ||
} else { | ||
delete headers[name]; | ||
} | ||
}; | ||
if (isUIRequest) { | ||
addHeader(CLIENT_ID_FILTER, options.clientId); | ||
} else { | ||
addHeader(NOHOST_RULE, options.rules); | ||
addHeader(NOHOST_VALUE, getString(options.values)); | ||
addHeader(CLIENT_ID, options.clientId); | ||
} | ||
addHeader(SPACE_NAME, options.spaceName); | ||
addHeader(GROUP_NAME, options.groupName); | ||
addHeader(ENV_NAME, options.envName); | ||
}; | ||
class Router { | ||
@@ -51,3 +98,3 @@ constructor(servers) { | ||
const server = servers[i]; | ||
if (typeof callback === 'function') { | ||
if (callback) { | ||
callback(server); | ||
@@ -98,4 +145,16 @@ } | ||
async proxy(req, res, callback) { | ||
existsHost(host) { | ||
return this._result ? this._result.map[host] : false; | ||
} | ||
async proxy(req, res, options) { | ||
onClose(res); | ||
let callback; | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = null; | ||
} else if (typeof options.callback === 'function') { | ||
callback = options.callback; | ||
} | ||
addOptions(req, options); | ||
if (this._nohostAddress) { | ||
@@ -105,3 +164,3 @@ if (req.isUIRequest) { | ||
} | ||
if (typeof callback === 'function') { | ||
if (callback) { | ||
callback(this._nohostAddress); | ||
@@ -116,7 +175,3 @@ } | ||
if (!result || isFinished(req)) { | ||
if (!res) { | ||
throw new Error(result ? 'request is finished.' : 'not found nohost server.'); | ||
} | ||
req.destroy(); | ||
return; | ||
throw new Error(result ? 'request is finished.' : 'not found nohost server.'); | ||
} | ||
@@ -129,11 +184,21 @@ const { headers } = req; | ||
if (req.isUIRequest) { | ||
if (!res) { | ||
throw new Error('space & group is required.'); | ||
} | ||
req.destroy(); | ||
return; | ||
throw new Error('space & group is required.'); | ||
} | ||
return this._connectDefault(req, res, callback); | ||
} | ||
const status = await this._getStatus(space, group, name); | ||
let status; | ||
let needRoute; | ||
if (req.isUIRequest && ROUTE_RE.test(req.url)) { | ||
let host = decode(RegExp.$2); | ||
if (host === '!required!') { | ||
needRoute = true; | ||
} else if (ROUTE_VALUE_RE.test(host)) { | ||
host = Buffer.from(host, 'base64').toString(); | ||
if (this.existsHost(host)) { | ||
host = host.split(':'); | ||
status = { host: host[0], port: host[1] }; | ||
} | ||
} | ||
} | ||
status = status || await this._getStatus(space, group, name); | ||
if (!status || !status.host) { | ||
@@ -152,5 +217,9 @@ return this._connectDefault(req, res, callback); | ||
req.url = `/account/${env}${path}`; | ||
if (needRoute) { | ||
const route = Buffer.from(`${status.host}:${status.port}`).toString('base64'); | ||
req.url = req.url.replace(ROUTE_RE, `$1route=${route}$3`); | ||
} | ||
} | ||
headers[ENV_HEAD] = env; | ||
if (typeof callback === 'function') { | ||
if (callback) { | ||
callback(status); | ||
@@ -161,5 +230,5 @@ } | ||
proxyUI(req, res, callback) { | ||
proxyUI(req, res, options) { | ||
req.isUIRequest = true; | ||
return this.proxy(req, res, callback); | ||
return this.proxy(req, res, options); | ||
} | ||
@@ -171,6 +240,6 @@ } | ||
Router.ENV_NAME = ENV_NAME; | ||
Router.NOHOST_RULE = 'x-whistle-rule-value'; | ||
Router.NOHOST_VALUE = 'x-whistle-key-value'; | ||
Router.CLIENT_ID = 'x-whistle-client-id'; | ||
Router.CLIENT_ID_FILTER = 'x-whistle-filter-client-id'; | ||
Router.NOHOST_RULE = NOHOST_RULE; | ||
Router.NOHOST_VALUE = NOHOST_VALUE; | ||
Router.CLIENT_ID = CLIENT_ID; | ||
Router.CLIENT_ID_FILTER = CLIENT_ID_FILTER; | ||
Router.writeError = writeError; | ||
@@ -177,0 +246,0 @@ Router.writeHead = writeHead; |
@@ -94,6 +94,10 @@ const http = require('http'); | ||
servers = usableServers.filter(noop); | ||
const map = {}; | ||
const list = servers.map((server) => { | ||
return `${server.host}:${server.port}/${server.workerNum}`; | ||
const host = `${server.host}:${server.port}`; | ||
map[host] = server; | ||
return `${host}/${server.workerNum}`; | ||
}); | ||
return list.length && { | ||
map, | ||
servers, | ||
@@ -103,1 +107,8 @@ base64: Buffer.from(list.join()).toString('base64'), | ||
}; | ||
exports.decode = (str) => { | ||
try { | ||
return decodeURIComponent(str); | ||
} catch (e) {} | ||
return str; | ||
}; |
{ | ||
"name": "@nohost/router", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "Nohost cluster router", | ||
@@ -5,0 +5,0 @@ "main": "lib/", |
@@ -70,17 +70,28 @@ # router | ||
``` js | ||
const { headers } = req; | ||
// 设置规则,可以从数据库动态获取 | ||
headers[NOHOST_RULE] = encodeURIComponent('ke.qq.com file://{test.html}'); | ||
headers[NOHOST_VALUE] = encodeURIComponent(JSON.stringify({ 'test.html': 'hell world.' })); | ||
const getOptions = (req) => { | ||
const { headers } = req; | ||
const spaceName = 'imweb'; | ||
let gruopName; | ||
let envName; | ||
if (headers.host === 'km.oa2.com') { | ||
gruopName = 'avenwu'; | ||
envName = '测试'; // 可选 | ||
} else if (req.headers.host !== 'km.oa.com') { | ||
gruopName = 'avenwu2'; | ||
envName = '测试2'; // 可选 | ||
} | ||
// 设置环境 | ||
headers[SPACE_NAME] = encodeURIComponent('imweb'); | ||
headers[GROUP_NAME] = encodeURIComponent('avenwu'); | ||
headers[ENV_NAME] = encodeURIComponent('测试'); // 可选 | ||
return { | ||
rules: 'file://{test.html} km.oa2.com www.test2.com', | ||
values: { 'test.html': 'hell world.' }, | ||
spaceName, | ||
gruopName, | ||
envName, | ||
callback: console.log, // 可选 | ||
// clientId: 'test', // 如果从外网转发过来的带登录态请求,设置下 clientId 方便插件当前用户的请求抓包 | ||
}; | ||
}; | ||
// 如果从外网转发过来的带登录态请求,设置下 clientId 方便插件当前用户的请求抓包 | ||
// headers[CLIENT_ID] = uin; | ||
router.proxy(req, res, getOptions(req)); | ||
router.proxy(req, res); | ||
// 或自己处理响应 | ||
@@ -92,11 +103,3 @@ // const svrRes = await router.proxy(req); | ||
``` js | ||
// 设置环境 | ||
headers[SPACE_NAME] = encodeURIComponent('imweb'); | ||
headers[GROUP_NAME] = encodeURIComponent('avenwu'); | ||
headers[ENV_NAME] = encodeURIComponent('测试'); // 可选 | ||
// 只查看指定 clientId 的请求 | ||
// headers[CLIENT_ID_FILTER] = uin; | ||
router.proxyUI(req, res); | ||
router.proxyUI(req, res, getOptions(req)); | ||
``` | ||
@@ -103,0 +106,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15807
393
120