router.js 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import isPlainObject from 'is-plain-obj';
  2. import { Debug } from './debug.js';
  3. const debug = Debug.extend('router');
  4. export async function getTarget(req, res, config) {
  5. let newTarget;
  6. const router = config.router;
  7. if (isPlainObject(router)) {
  8. newTarget = getTargetFromProxyTable(req, router);
  9. }
  10. else if (typeof router === 'function') {
  11. newTarget = await router(req, res, config);
  12. }
  13. return newTarget;
  14. }
  15. function getTargetFromProxyTable(req, table) {
  16. let result;
  17. const host = req.headers.host ?? '';
  18. const path = req.url ?? '';
  19. for (const [key, value] of Object.entries(table)) {
  20. if (containsPath(key)) {
  21. if (isHostAndPathKey(key)) {
  22. const [keyHost, keyPath] = splitHostAndPathKey(key);
  23. // SECURITY: host+path keys must match exact host + path prefix.
  24. if (host === keyHost && path.startsWith(keyPath)) {
  25. // match 'localhost:3000/api'
  26. result = value;
  27. debug('match: "%s" -> "%s"', key, result);
  28. break;
  29. }
  30. }
  31. else {
  32. if (path.startsWith(key)) {
  33. // match '/api'
  34. result = value;
  35. debug('match: "%s" -> "%s"', key, result);
  36. break;
  37. }
  38. }
  39. }
  40. else {
  41. if (key === host) {
  42. // match 'localhost:3000'
  43. result = value;
  44. debug('match: "%s" -> "%s"', host, result);
  45. break;
  46. }
  47. }
  48. }
  49. return result;
  50. }
  51. function containsPath(v) {
  52. return v.indexOf('/') > -1;
  53. }
  54. function isHostAndPathKey(v) {
  55. return containsPath(v) && !v.startsWith('/');
  56. }
  57. function splitHostAndPathKey(v) {
  58. const firstSlash = v.indexOf('/');
  59. return [v.slice(0, firstSlash), v.slice(firstSlash)];
  60. }