http-proxy-middleware 0.17.3 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -1
- package/README.md +284 -240
- package/index.js +3 -3
- package/lib/config-factory.js +74 -71
- package/lib/context-matcher.js +46 -46
- package/lib/errors.js +12 -0
- package/lib/handlers.js +59 -51
- package/lib/index.js +167 -132
- package/lib/logger.js +130 -116
- package/lib/path-rewriter.js +57 -50
- package/lib/router.js +37 -39
- package/package.json +36 -15
package/index.js
CHANGED
package/lib/config-factory.js
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
var _
|
|
2
|
-
var url
|
|
3
|
-
var
|
|
1
|
+
var _ = require('lodash')
|
|
2
|
+
var url = require('url')
|
|
3
|
+
var ERRORS = require('./errors')
|
|
4
|
+
var logger = require('./logger').getInstance()
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
|
-
|
|
7
|
-
}
|
|
7
|
+
createConfig: createConfig
|
|
8
|
+
}
|
|
8
9
|
|
|
9
10
|
function createConfig(context, opts) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// structure of config object to be returned
|
|
12
|
+
var config = {
|
|
13
|
+
context: undefined,
|
|
14
|
+
options: {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// app.use('/api', proxy({target:'http://localhost:9000'}));
|
|
18
|
+
if (isContextless(context, opts)) {
|
|
19
|
+
config.context = '/'
|
|
20
|
+
config.options = _.assign(config.options, context)
|
|
15
21
|
|
|
16
|
-
// app.use('/api', proxy({target:'http://localhost:9000'}));
|
|
17
|
-
if (isContextless(context, opts)) {
|
|
18
|
-
config.context = '/';
|
|
19
|
-
config.options = _.assign(config.options, context);
|
|
20
|
-
}
|
|
21
22
|
// app.use('/api', proxy('http://localhost:9000'));
|
|
22
23
|
// app.use(proxy('http://localhost:9000/api'));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
} else if (isStringShortHand(context)) {
|
|
25
|
+
var oUrl = url.parse(context)
|
|
26
|
+
var target = [oUrl.protocol, '//', oUrl.host].join('')
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
config.context = oUrl.pathname || '/'
|
|
29
|
+
config.options = _.assign(config.options, { target: target }, opts)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
// app.use('/api', proxy({target:'http://localhost:9000'}));
|
|
34
|
-
} else {
|
|
35
|
-
config.context = context;
|
|
36
|
-
config.options = _.assign(config.options, opts);
|
|
31
|
+
if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') {
|
|
32
|
+
config.options.ws = true
|
|
37
33
|
}
|
|
34
|
+
// app.use('/api', proxy({target:'http://localhost:9000'}));
|
|
35
|
+
} else {
|
|
36
|
+
config.context = context
|
|
37
|
+
config.options = _.assign(config.options, opts)
|
|
38
|
+
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
configureLogger(config.options)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (!config.options.target) {
|
|
43
|
+
throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING)
|
|
44
|
+
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
// Legacy option.proxyHost
|
|
47
|
+
config.options = mapLegacyProxyHostOption(config.options)
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// Legacy option.proxyTable > option.router
|
|
50
|
+
config.options = mapLegacyProxyTableOption(config.options)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
return config
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -63,9 +64,9 @@ function createConfig(context, opts) {
|
|
|
63
64
|
* @return {Boolean} [description]
|
|
64
65
|
*/
|
|
65
66
|
function isStringShortHand(context) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
if (_.isString(context)) {
|
|
68
|
+
return !!url.parse(context).host
|
|
69
|
+
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/**
|
|
@@ -80,47 +81,49 @@ function isStringShortHand(context) {
|
|
|
80
81
|
* @return {Boolean} [description]
|
|
81
82
|
*/
|
|
82
83
|
function isContextless(context, opts) {
|
|
83
|
-
|
|
84
|
+
return _.isPlainObject(context) && _.isEmpty(opts)
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
function mapLegacyProxyHostOption(options) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
// set options.headers.host when option.proxyHost is provided
|
|
89
|
+
if (options.proxyHost) {
|
|
90
|
+
logger.warn('*************************************')
|
|
91
|
+
logger.warn('[HPM] Deprecated "option.proxyHost"')
|
|
92
|
+
logger.warn(
|
|
93
|
+
' Use "option.changeOrigin" or "option.headers.host" instead'
|
|
94
|
+
)
|
|
95
|
+
logger.warn(' "option.proxyHost" will be removed in future release.')
|
|
96
|
+
logger.warn('*************************************')
|
|
97
|
+
|
|
98
|
+
options.headers = options.headers || {}
|
|
99
|
+
options.headers.host = options.proxyHost
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return options
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
// Warn deprecated proxyTable api usage
|
|
103
106
|
function mapLegacyProxyTableOption(options) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
if (options.proxyTable) {
|
|
108
|
+
logger.warn('*************************************')
|
|
109
|
+
logger.warn('[HPM] Deprecated "option.proxyTable"')
|
|
110
|
+
logger.warn(' Use "option.router" instead')
|
|
111
|
+
logger.warn(' "option.proxyTable" will be removed in future release.')
|
|
112
|
+
logger.warn('*************************************')
|
|
113
|
+
|
|
114
|
+
options.router = _.clone(options.proxyTable)
|
|
115
|
+
_.omit(options, 'proxyTable')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return options
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
function configureLogger(options) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
if (options.logLevel) {
|
|
123
|
+
logger.setLevel(options.logLevel)
|
|
124
|
+
}
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
if (options.logProvider) {
|
|
127
|
+
logger.setProvider(options.logProvider)
|
|
128
|
+
}
|
|
126
129
|
}
|
package/lib/context-matcher.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
var _ = require('lodash')
|
|
2
|
-
var url = require('url')
|
|
3
|
-
var isGlob = require('is-glob')
|
|
4
|
-
var micromatch = require('micromatch')
|
|
1
|
+
var _ = require('lodash')
|
|
2
|
+
var url = require('url')
|
|
3
|
+
var isGlob = require('is-glob')
|
|
4
|
+
var micromatch = require('micromatch')
|
|
5
|
+
var ERRORS = require('./errors')
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
}
|
|
8
|
+
match: matchContext
|
|
9
|
+
}
|
|
9
10
|
|
|
10
11
|
function matchContext(context, uri, req) {
|
|
12
|
+
// single path
|
|
13
|
+
if (isStringPath(context)) {
|
|
14
|
+
return matchSingleStringPath(context, uri)
|
|
15
|
+
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// single glob path
|
|
18
|
+
if (isGlobPath(context)) {
|
|
19
|
+
return matchSingleGlobPath(context, uri)
|
|
20
|
+
}
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
// multi path
|
|
23
|
+
if (Array.isArray(context)) {
|
|
24
|
+
if (context.every(isStringPath)) {
|
|
25
|
+
return matchMultiPath(context, uri)
|
|
20
26
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(context)) {
|
|
24
|
-
if (context.every(isStringPath)) {
|
|
25
|
-
return matchMultiPath(context, uri);
|
|
26
|
-
}
|
|
27
|
-
if (context.every(isGlobPath)) {
|
|
28
|
-
return matchMultiGlobPath(context, uri);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
throw new Error('[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]');
|
|
27
|
+
if (context.every(isGlobPath)) {
|
|
28
|
+
return matchMultiGlobPath(context, uri)
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
throw new Error(ERRORS.ERR_CONTEXT_MATCHER_INVALID_ARRAY)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// custom matching
|
|
35
|
+
if (_.isFunction(context)) {
|
|
36
|
+
var pathname = getUrlPathName(uri)
|
|
37
|
+
return context(pathname, req)
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
throw new Error(ERRORS.ERR_CONTEXT_MATCHER_GENERIC)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -46,33 +46,33 @@ function matchContext(context, uri, req) {
|
|
|
46
46
|
* @return {Boolean}
|
|
47
47
|
*/
|
|
48
48
|
function matchSingleStringPath(context, uri) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
var pathname = getUrlPathName(uri)
|
|
50
|
+
return pathname.indexOf(context) === 0
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function matchSingleGlobPath(pattern, uri) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
var pathname = getUrlPathName(uri)
|
|
55
|
+
var matches = micromatch(pathname, pattern)
|
|
56
|
+
return matches && matches.length > 0
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function matchMultiGlobPath(patternList, uri) {
|
|
60
|
-
|
|
60
|
+
return matchSingleGlobPath(patternList, uri)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* @param {String}
|
|
64
|
+
* @param {String} contextList ['/api', '/ajax']
|
|
65
65
|
* @param {String} uri 'http://example.org/api/b/c/d.html'
|
|
66
66
|
* @return {Boolean}
|
|
67
67
|
*/
|
|
68
68
|
function matchMultiPath(contextList, uri) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
69
|
+
for (var i = 0; i < contextList.length; i++) {
|
|
70
|
+
var context = contextList[i]
|
|
71
|
+
if (matchSingleStringPath(context, uri)) {
|
|
72
|
+
return true
|
|
74
73
|
}
|
|
75
|
-
|
|
74
|
+
}
|
|
75
|
+
return false
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
@@ -82,13 +82,13 @@ function matchMultiPath(contextList, uri) {
|
|
|
82
82
|
* @return {String} RFC 3986 path
|
|
83
83
|
*/
|
|
84
84
|
function getUrlPathName(uri) {
|
|
85
|
-
|
|
85
|
+
return uri && url.parse(uri).pathname
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function isStringPath(context) {
|
|
89
|
-
|
|
89
|
+
return _.isString(context) && !isGlob(context)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function isGlobPath(context) {
|
|
93
|
-
|
|
93
|
+
return isGlob(context)
|
|
94
94
|
}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
ERR_CONFIG_FACTORY_TARGET_MISSING:
|
|
5
|
+
'[HPM] Missing "target" option. Example: {target: "http://www.example.org"}',
|
|
6
|
+
ERR_CONTEXT_MATCHER_GENERIC:
|
|
7
|
+
'[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]',
|
|
8
|
+
ERR_CONTEXT_MATCHER_INVALID_ARRAY:
|
|
9
|
+
'[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]',
|
|
10
|
+
ERR_PATH_REWRITER_CONFIG:
|
|
11
|
+
'[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function'
|
|
12
|
+
}
|
package/lib/handlers.js
CHANGED
|
@@ -1,74 +1,82 @@
|
|
|
1
|
-
var _
|
|
2
|
-
var logger = require('./logger').getInstance()
|
|
1
|
+
var _ = require('lodash')
|
|
2
|
+
var logger = require('./logger').getInstance()
|
|
3
3
|
|
|
4
4
|
module.exports = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
init: init,
|
|
6
|
+
getHandlers: getProxyEventHandlers
|
|
7
|
+
}
|
|
8
8
|
|
|
9
9
|
function init(proxy, opts) {
|
|
10
|
-
|
|
10
|
+
var handlers = getProxyEventHandlers(opts)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
_.forIn(handlers, function(handler, eventName) {
|
|
13
|
+
proxy.on(eventName, handlers[eventName])
|
|
14
|
+
})
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
logger.debug('[HPM] Subscribed to http-proxy events: ', _.keys(handlers))
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function getProxyEventHandlers(opts) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
// https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events
|
|
21
|
+
var proxyEvents = [
|
|
22
|
+
'error',
|
|
23
|
+
'proxyReq',
|
|
24
|
+
'proxyReqWs',
|
|
25
|
+
'proxyRes',
|
|
26
|
+
'open',
|
|
27
|
+
'close'
|
|
28
|
+
]
|
|
29
|
+
var handlers = {}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
_.forEach(proxyEvents, function(event) {
|
|
32
|
+
// all handlers for the http-proxy events are prefixed with 'on'.
|
|
33
|
+
// loop through options and try to find these handlers
|
|
34
|
+
// and add them to the handlers object for subscription in init().
|
|
35
|
+
var eventName = _.camelCase('on ' + event)
|
|
36
|
+
var fnHandler = _.get(opts, eventName)
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
handlers.error = defaultErrorHandler;
|
|
38
|
+
if (_.isFunction(fnHandler)) {
|
|
39
|
+
handlers[event] = fnHandler
|
|
39
40
|
}
|
|
41
|
+
})
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// add default error handler in absence of error handler
|
|
44
|
+
if (!_.isFunction(handlers.error)) {
|
|
45
|
+
handlers.error = defaultErrorHandler
|
|
46
|
+
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
// add default close handler in absence of close handler
|
|
49
|
+
if (!_.isFunction(handlers.close)) {
|
|
50
|
+
handlers.close = logClose
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return handlers
|
|
54
|
+
}
|
|
48
55
|
|
|
49
56
|
function defaultErrorHandler(err, req, res) {
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
var host = req.headers && req.headers.host
|
|
58
|
+
var code = err.code
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
if (res.writeHead && !res.headersSent) {
|
|
61
|
+
if (/HPE_INVALID/.test(code)) {
|
|
62
|
+
res.writeHead(502)
|
|
63
|
+
} else {
|
|
64
|
+
switch (code) {
|
|
65
|
+
case 'ECONNRESET':
|
|
66
|
+
case 'ENOTFOUND':
|
|
67
|
+
case 'ECONNREFUSED':
|
|
68
|
+
res.writeHead(504)
|
|
69
|
+
break
|
|
70
|
+
default:
|
|
71
|
+
res.writeHead(500)
|
|
72
|
+
}
|
|
66
73
|
}
|
|
74
|
+
}
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
res.end('Error occured while trying to proxy to: ' + host + req.url)
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
function logClose(req, socket, head) {
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
// view disconnected websocket connections
|
|
81
|
+
logger.info('[HPM] Client disconnected')
|
|
74
82
|
}
|