-
Notifications
You must be signed in to change notification settings - Fork 19
/
router.js
164 lines (145 loc) · 5.66 KB
/
router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
const fs = require("fs");
const path = require("path");
const URL = require("url");
const c = require("print-colors");
const _ = require("lodash");
const { flow, includes, get } = require("lodash/fp");
const finalhandler = require("finalhandler");
const serveStatic = require("serve-static");
const resolveHome = require("./resolveHome");
const ProxyAgent = require("proxy-agent");
const priv = {};
priv.tryPlugin = (plugin, req, res, target, cb) => {
if (typeof plugin === "function") {
plugin(req, res, target).then((t) => {
// Plugin may have sent back a new target
// if they did use it
t = t || target;
cb(t);
}); // TODO what to do if the plugin Promise fails ??
} else {
// Run with the default target
cb(target);
}
};
priv.doProxy = (proxy, req, res, target, confProxy = null) => {
if (target) {
const options = {
target,
ignorePath: true,
};
if (confProxy) {
const regex = RegExp(confProxy.pattern);
// if the target URL passes the regex test based on the
// pattern provided in the proxy.pattern property,
// add a new HttpsProxyAgent
if (regex.test(target)) {
options.agent = new ProxyAgent(confProxy.host);
}
}
proxy.web(req, res, options, (e) => {
console.error(e);
res.writeHead(502, { "Content-Type": "text/plain" });
res.write(
`HTTP 502 Bad gateway\n\nRequest to ${req.url} was proxied to ${target} which did not respond.`
);
res.end();
});
} else {
res.writeHead(404);
res.end();
}
};
module.exports = (conf, proxy) => {
// for each local file path in the conf, create a serveStatic object for
// serving that dir
const serveLocal = _(conf.routes)
.omitBy(_.isObject)
.mapValues((dir) =>
serveStatic(path.resolve(conf.configDir, resolveHome(dir)), {
redirect: true,
})
)
.value();
return (req, res, next) => {
// figure out which target to proxy to based on the requested resource path
const sortedRoutes = _(conf.routes)
.toPairs()
.filter((v) => _.startsWith(req.url, v[0]))
.sortBy((v) => -v[0].length)
.value();
const env = req.headers["x-spandx-env"];
for (let routeCandidate of sortedRoutes) {
const routeKey = routeCandidate[0];
const route = conf.routes[routeKey];
const acceptHTML = flow(
get("headers.accept"),
includes("text/html")
)(req);
const hasExtension = URL.parse(req.url).path.includes(".");
const isDoc = acceptHTML && !hasExtension;
const useSingle = route.single && isDoc;
const routePath = route.path || routeKey;
const targetPath = useSingle
? routePath
: req.url.replace(new RegExp(`^${routeKey}`), routePath);
const targetHost = route.host && route.host[env];
let fileExists;
let needsSlash = false;
const localFile = !targetHost;
let target = targetHost + targetPath;
// determine if the URL path maps to a local directory
// if it maps to a local directory, and if the file exists, serve it
// up. if the URL path maps to an HTTP server, OR if it maps to a file
// but the file doesn't exist, in either case proxy to a remote server.
if (localFile) {
const url = URL.parse(req.url);
const relativeFilePath = url.pathname.replace(
new RegExp(`^${routeKey}/?`),
"/"
); // remove route path (will be replaced with disk path)
const absoluteFilePath = path.resolve(
conf.configDir,
resolveHome(route),
relativeFilePath.replace(/^\//, "")
);
fileExists = fs.existsSync(absoluteFilePath);
if (fileExists) {
if (conf.verbose) {
console.log(
`GET ${c.fg.l.green}${req.url}${c.end} from ${c.fg.l.cyan}${absoluteFilePath}${c.end} ${env}`
);
}
req.url = relativeFilePath; // update the request's url to be relative to the on-disk dir
serveLocal[routeKey](req, res, finalhandler(req, res));
return; // stop here, don't continue to HTTP proxy section
}
}
if (localFile && !fileExists && routeKey.length > 1) {
continue;
}
if (localFile && (!fileExists || needsSlash)) {
target = conf.routes["/"].host
? conf.routes["/"].host[env]
: undefined;
}
if (conf.verbose) {
console.log(
`GET ${c.fg.l.green}${req.url}${c.end} from ${
c.fg.l.blue
}${target.replace(new RegExp(`${req.url}$`), "")}${c.end}${
c.fg.l.green
}${req.url}${c.end}`
);
}
priv.tryPlugin(conf.routerPlugin, req, res, target, (t) => {
priv.doProxy(proxy, req, res, t, conf.proxy);
});
return;
}
};
};
if (process.env.NODE_ENV === "test") {
// only leak in test
module.exports.priv = priv;
}