netlify-cli 17.3.2 → 17.4.0
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/README.md +2 -138
- package/npm-shrinkwrap.json +76 -76
- package/package.json +16 -15
- package/src/commands/addons/addons-auth.mjs +27 -30
- package/src/commands/addons/addons-config.mjs +145 -154
- package/src/commands/addons/addons-create.mjs +94 -108
- package/src/commands/addons/addons-delete.mjs +36 -41
- package/src/commands/addons/addons-list.mjs +38 -42
- package/src/commands/addons/addons.mjs +26 -28
- package/src/commands/addons/index.mjs +1 -1
- package/src/commands/api/api.mjs +45 -53
- package/src/commands/api/index.mjs +1 -1
- package/src/commands/base-command.mjs +597 -684
- package/src/commands/blobs/blobs-delete.mjs +35 -0
- package/src/commands/blobs/blobs-get.mjs +44 -0
- package/src/commands/blobs/blobs-list.mjs +48 -0
- package/src/commands/blobs/blobs-set.mjs +54 -0
- package/src/commands/blobs/blobs.mjs +32 -0
- package/src/commands/blobs/index.mjs +1 -0
- package/src/commands/build/build.mjs +55 -67
- package/src/commands/build/index.mjs +1 -1
- package/src/commands/completion/completion.mjs +41 -46
- package/src/commands/completion/index.mjs +1 -1
- package/src/commands/deploy/deploy.mjs +675 -710
- package/src/commands/deploy/index.mjs +1 -1
- package/src/commands/dev/dev-exec.mjs +20 -32
- package/src/commands/dev/dev.mjs +217 -302
- package/src/commands/dev/index.mjs +1 -1
- package/src/commands/dev/types.d.ts +30 -0
- package/src/commands/env/env-clone.mjs +157 -184
- package/src/commands/env/env-get.mjs +49 -68
- package/src/commands/env/env-import.mjs +100 -119
- package/src/commands/env/env-list.mjs +104 -129
- package/src/commands/env/env-set.mjs +160 -185
- package/src/commands/env/env-unset.mjs +104 -122
- package/src/commands/env/env.mjs +28 -30
- package/src/commands/env/index.mjs +1 -1
- package/src/commands/functions/functions-build.mjs +29 -41
- package/src/commands/functions/functions-create.mjs +533 -601
- package/src/commands/functions/functions-invoke.mjs +193 -216
- package/src/commands/functions/functions-list.mjs +45 -55
- package/src/commands/functions/functions-serve.mjs +51 -61
- package/src/commands/functions/functions.mjs +26 -32
- package/src/commands/functions/index.mjs +1 -1
- package/src/commands/index.mjs +2 -2
- package/src/commands/init/index.mjs +1 -1
- package/src/commands/init/init.mjs +138 -167
- package/src/commands/integration/deploy.mjs +337 -399
- package/src/commands/integration/index.mjs +12 -13
- package/src/commands/link/index.mjs +1 -1
- package/src/commands/link/link.mjs +298 -317
- package/src/commands/lm/index.mjs +1 -1
- package/src/commands/lm/lm-info.mjs +23 -31
- package/src/commands/lm/lm-install.mjs +13 -17
- package/src/commands/lm/lm-setup.mjs +80 -84
- package/src/commands/lm/lm-uninstall.mjs +7 -12
- package/src/commands/lm/lm.mjs +18 -22
- package/src/commands/login/index.mjs +1 -1
- package/src/commands/login/login.mjs +35 -41
- package/src/commands/logout/index.mjs +1 -1
- package/src/commands/logout/logout.mjs +25 -31
- package/src/commands/main.mjs +166 -201
- package/src/commands/open/index.mjs +1 -1
- package/src/commands/open/open-admin.mjs +15 -18
- package/src/commands/open/open-site.mjs +16 -19
- package/src/commands/open/open.mjs +24 -27
- package/src/commands/recipes/common.mjs +23 -34
- package/src/commands/recipes/index.mjs +1 -1
- package/src/commands/recipes/recipes-list.mjs +13 -20
- package/src/commands/recipes/recipes.mjs +59 -72
- package/src/commands/serve/index.mjs +1 -1
- package/src/commands/serve/serve.mjs +142 -189
- package/src/commands/sites/index.mjs +2 -2
- package/src/commands/sites/sites-create-template.mjs +214 -236
- package/src/commands/sites/sites-create.mjs +145 -157
- package/src/commands/sites/sites-delete.mjs +75 -81
- package/src/commands/sites/sites-list.mjs +63 -66
- package/src/commands/sites/sites.mjs +18 -20
- package/src/commands/status/index.mjs +1 -1
- package/src/commands/status/status-hooks.mjs +32 -34
- package/src/commands/status/status.mjs +99 -106
- package/src/commands/switch/index.mjs +1 -1
- package/src/commands/switch/switch.mjs +32 -37
- package/src/commands/types.d.ts +31 -0
- package/src/commands/unlink/index.mjs +1 -1
- package/src/commands/unlink/unlink.mjs +23 -29
- package/src/commands/watch/index.mjs +1 -1
- package/src/commands/watch/watch.mjs +91 -105
- package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
- package/src/lib/account.mjs +4 -5
- package/src/lib/api.mjs +22 -20
- package/src/lib/blobs/blobs.mjs +36 -45
- package/src/lib/build.mjs +82 -85
- package/src/lib/completion/constants.mjs +2 -4
- package/src/lib/completion/generate-autocompletion.mjs +33 -36
- package/src/lib/completion/get-autocompletion.mjs +31 -35
- package/src/lib/completion/index.mjs +1 -1
- package/src/lib/completion/script.mjs +12 -19
- package/src/lib/edge-functions/bootstrap.mjs +3 -5
- package/src/lib/edge-functions/consts.mjs +9 -10
- package/src/lib/edge-functions/deploy.mjs +28 -34
- package/src/lib/edge-functions/editor-helper.mjs +29 -42
- package/src/lib/edge-functions/headers.mjs +24 -26
- package/src/lib/edge-functions/internal.mjs +38 -44
- package/src/lib/edge-functions/proxy.mjs +229 -228
- package/src/lib/edge-functions/registry.mjs +473 -574
- package/src/lib/exec-fetcher.mjs +115 -122
- package/src/lib/fs.mjs +28 -27
- package/src/lib/functions/background.mjs +16 -20
- package/src/lib/functions/config.mjs +12 -9
- package/src/lib/functions/form-submissions-handler.mjs +143 -149
- package/src/lib/functions/local-proxy.mjs +40 -44
- package/src/lib/functions/memoized-build.mjs +19 -21
- package/src/lib/functions/netlify-function.mjs +269 -249
- package/src/lib/functions/registry.mjs +509 -568
- package/src/lib/functions/runtimes/go/index.mjs +62 -71
- package/src/lib/functions/runtimes/index.mjs +8 -15
- package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
- package/src/lib/functions/runtimes/js/constants.mjs +1 -1
- package/src/lib/functions/runtimes/js/index.mjs +92 -109
- package/src/lib/functions/runtimes/js/worker.mjs +43 -45
- package/src/lib/functions/runtimes/rust/index.mjs +64 -73
- package/src/lib/functions/scheduled.mjs +70 -88
- package/src/lib/functions/server.mjs +269 -327
- package/src/lib/functions/synchronous.mjs +118 -147
- package/src/lib/functions/utils.mjs +38 -46
- package/src/lib/geo-location.mjs +69 -81
- package/src/lib/http-agent.mjs +87 -90
- package/src/lib/images/proxy.mjs +97 -99
- package/src/lib/log.mjs +6 -9
- package/src/lib/path.mjs +2 -1
- package/src/lib/render-error-template.mjs +19 -20
- package/src/lib/settings.mjs +17 -19
- package/src/lib/spinner.mjs +21 -23
- package/src/lib/string.mjs +4 -2
- package/src/recipes/vscode/index.mjs +69 -85
- package/src/recipes/vscode/settings.mjs +53 -58
- package/src/utils/addons/compare.mjs +31 -32
- package/src/utils/addons/diffs/index.mjs +16 -17
- package/src/utils/addons/diffs/options.mjs +99 -101
- package/src/utils/addons/prepare.mjs +100 -97
- package/src/utils/addons/prompts.mjs +73 -76
- package/src/utils/addons/render.mjs +33 -36
- package/src/utils/addons/validation.mjs +19 -15
- package/src/utils/banner.mjs +11 -16
- package/src/utils/build-info.mjs +65 -66
- package/src/utils/command-helpers.mjs +185 -199
- package/src/utils/create-deferred.mjs +9 -12
- package/src/utils/create-stream-promise.mjs +54 -47
- package/src/utils/deploy/constants.mjs +9 -11
- package/src/utils/deploy/deploy-site.mjs +162 -182
- package/src/utils/deploy/hash-config.mjs +21 -21
- package/src/utils/deploy/hash-files.mjs +34 -38
- package/src/utils/deploy/hash-fns.mjs +149 -154
- package/src/utils/deploy/hasher-segments.mjs +58 -52
- package/src/utils/deploy/upload-files.mjs +99 -113
- package/src/utils/deploy/util.mjs +85 -91
- package/src/utils/detect-server-settings.mjs +236 -268
- package/src/utils/dev.mjs +163 -178
- package/src/utils/dot-env.mjs +37 -42
- package/src/utils/env/index.mjs +148 -148
- package/src/utils/execa.mjs +9 -13
- package/src/utils/feature-flags.mjs +6 -5
- package/src/utils/framework-server.mjs +43 -52
- package/src/utils/functions/constants.mjs +1 -1
- package/src/utils/functions/functions.mjs +30 -40
- package/src/utils/functions/get-functions.mjs +28 -29
- package/src/utils/functions/index.mjs +3 -3
- package/src/utils/get-global-config.mjs +33 -36
- package/src/utils/get-package-json.mjs +14 -15
- package/src/utils/get-repo-data.mjs +54 -64
- package/src/utils/get-site.mjs +14 -14
- package/src/utils/gh-auth.mjs +79 -100
- package/src/utils/gitignore.mjs +37 -40
- package/src/utils/headers.mjs +33 -35
- package/src/utils/hooks/requires-site-info.mjs +26 -22
- package/src/utils/init/config-github.mjs +207 -219
- package/src/utils/init/config-manual.mjs +83 -100
- package/src/utils/init/config.mjs +25 -26
- package/src/utils/init/node-version.mjs +23 -30
- package/src/utils/init/plugins.mjs +12 -8
- package/src/utils/init/utils.mjs +152 -172
- package/src/utils/live-tunnel.mjs +118 -141
- package/src/utils/lm/install.mjs +220 -259
- package/src/utils/lm/requirements.mjs +54 -63
- package/src/utils/lm/steps.mjs +31 -31
- package/src/utils/lm/ui.mjs +13 -20
- package/src/utils/open-browser.mjs +31 -32
- package/src/utils/parse-raw-flags.mjs +39 -35
- package/src/utils/proxy-server.mjs +84 -71
- package/src/utils/proxy.mjs +696 -750
- package/src/utils/read-repo-url.mjs +48 -47
- package/src/utils/redirects.mjs +49 -49
- package/src/utils/request-id.mjs +2 -4
- package/src/utils/rules-proxy.mjs +96 -100
- package/src/utils/run-build.mjs +109 -132
- package/src/utils/shell.mjs +99 -106
- package/src/utils/sign-redirect.mjs +14 -14
- package/src/utils/sites/utils.mjs +48 -55
- package/src/utils/state-config.mjs +101 -101
- package/src/utils/static-server.mjs +28 -34
- package/src/utils/telemetry/index.mjs +2 -2
- package/src/utils/telemetry/report-error.mjs +45 -49
- package/src/utils/telemetry/request.mjs +36 -43
- package/src/utils/telemetry/telemetry.mjs +90 -105
- package/src/utils/telemetry/utils.mjs +5 -6
- package/src/utils/telemetry/validation.mjs +55 -53
- package/src/utils/types.d.ts +46 -0
- package/src/utils/validation.mjs +10 -13
package/src/utils/proxy.mjs
CHANGED
|
@@ -1,812 +1,758 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import cookie from 'cookie'
|
|
14
|
-
import { getProperty } from 'dot-prop'
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import {
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
import
|
|
33
|
-
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
import { signRedirect } from './sign-redirect.mjs'
|
|
40
|
-
|
|
41
|
-
const gunzip = util.promisify(zlib.gunzip)
|
|
42
|
-
const brotliDecompress = util.promisify(zlib.brotliDecompress)
|
|
43
|
-
const deflate = util.promisify(zlib.deflate)
|
|
44
|
-
const shouldGenerateETag = Symbol('Internal: response should generate ETag')
|
|
45
|
-
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { once } from 'events';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import http from 'http';
|
|
5
|
+
import https from 'https';
|
|
6
|
+
import { isIPv6 } from 'net';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import util from 'util';
|
|
9
|
+
import zlib from 'zlib';
|
|
10
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'cont... Remove this comment to see the full error message
|
|
11
|
+
import contentType from 'content-type';
|
|
12
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'cook... Remove this comment to see the full error message
|
|
13
|
+
import cookie from 'cookie';
|
|
14
|
+
import { getProperty } from 'dot-prop';
|
|
15
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'etag... Remove this comment to see the full error message
|
|
16
|
+
import generateETag from 'etag';
|
|
17
|
+
import getAvailablePort from 'get-port';
|
|
18
|
+
import httpProxy from 'http-proxy';
|
|
19
|
+
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
20
|
+
import jwtDecode from 'jwt-decode';
|
|
21
|
+
import { locatePath } from 'locate-path';
|
|
22
|
+
import pFilter from 'p-filter';
|
|
23
|
+
import toReadableStream from 'to-readable-stream';
|
|
24
|
+
import { handleProxyRequest, initializeProxy as initializeEdgeFunctionsProxy, isEdgeFunctionsRequest, } from '../lib/edge-functions/proxy.mjs';
|
|
25
|
+
import { fileExistsAsync, isFileAsync } from '../lib/fs.mjs';
|
|
26
|
+
import { DEFAULT_FUNCTION_URL_EXPRESSION } from '../lib/functions/registry.mjs';
|
|
27
|
+
import { initializeProxy as initializeImageProxy, isImageRequest } from '../lib/images/proxy.mjs';
|
|
28
|
+
import renderErrorTemplate from '../lib/render-error-template.mjs';
|
|
29
|
+
import { NETLIFYDEVLOG, NETLIFYDEVWARN, log, chalk } from './command-helpers.mjs';
|
|
30
|
+
import createStreamPromise from './create-stream-promise.mjs';
|
|
31
|
+
import { headersForPath, parseHeaders, NFFunctionName, NFRequestID, NFFunctionRoute } from './headers.mjs';
|
|
32
|
+
import { generateRequestID } from './request-id.mjs';
|
|
33
|
+
import { createRewriter, onChanges } from './rules-proxy.mjs';
|
|
34
|
+
import { signRedirect } from './sign-redirect.mjs';
|
|
35
|
+
const gunzip = util.promisify(zlib.gunzip);
|
|
36
|
+
const brotliDecompress = util.promisify(zlib.brotliDecompress);
|
|
37
|
+
const deflate = util.promisify(zlib.deflate);
|
|
38
|
+
const shouldGenerateETag = Symbol('Internal: response should generate ETag');
|
|
46
39
|
/**
|
|
47
40
|
* @param {Buffer} body
|
|
48
41
|
* @param {string | undefined} contentEncoding
|
|
49
42
|
* @returns {Promise<Buffer>}
|
|
50
43
|
*/
|
|
44
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'body' implicitly has an 'any' type.
|
|
51
45
|
const decompressResponseBody = async function (body, contentEncoding = '') {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
46
|
+
switch (contentEncoding) {
|
|
47
|
+
case 'gzip':
|
|
48
|
+
return await gunzip(body);
|
|
49
|
+
case 'br':
|
|
50
|
+
return await brotliDecompress(body);
|
|
51
|
+
case 'deflate':
|
|
52
|
+
return await deflate(body);
|
|
53
|
+
default:
|
|
54
|
+
return body;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'errorBuffer' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
64
58
|
const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
errorMessage: message,
|
|
76
|
-
trace: stack.split('\\n'),
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
59
|
+
const { error: { message, name, stack }, } = JSON.parse(errorBuffer.toString());
|
|
60
|
+
if (!acceptsHtml) {
|
|
61
|
+
return `${name}: ${message}\n ${stack}`;
|
|
62
|
+
}
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
errorType: name,
|
|
65
|
+
errorMessage: message,
|
|
66
|
+
trace: stack.split('\\n'),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
80
69
|
/**
|
|
81
70
|
* @param {string} url
|
|
82
71
|
*/
|
|
72
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'url' implicitly has an 'any' type.
|
|
83
73
|
function isInternal(url) {
|
|
84
|
-
|
|
74
|
+
return url.startsWith('/.netlify/');
|
|
85
75
|
}
|
|
86
|
-
|
|
87
76
|
/**
|
|
88
77
|
* @param {boolean|number|undefined} functionsPort
|
|
89
78
|
* @param {string} url
|
|
90
79
|
*/
|
|
80
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'functionsPort' implicitly has an 'any' ... Remove this comment to see the full error message
|
|
91
81
|
function isFunction(functionsPort, url) {
|
|
92
|
-
|
|
82
|
+
return functionsPort && url.match(DEFAULT_FUNCTION_URL_EXPRESSION);
|
|
93
83
|
}
|
|
94
|
-
|
|
95
84
|
/**
|
|
96
85
|
* @param {Record<string, string>} addonsUrls
|
|
97
86
|
* @param {http.IncomingMessage} req
|
|
98
87
|
*/
|
|
88
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'addonsUrls' implicitly has an 'any' typ... Remove this comment to see the full error message
|
|
99
89
|
function getAddonUrl(addonsUrls, req) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
const matches = req.url?.match(/^\/.netlify\/([^/]+)(\/.*)/);
|
|
91
|
+
const addonUrl = matches && addonsUrls[matches[1]];
|
|
92
|
+
return addonUrl ? `${addonUrl}${matches[2]}` : null;
|
|
103
93
|
}
|
|
104
|
-
|
|
105
94
|
/**
|
|
106
95
|
* @param {string} pathname
|
|
107
96
|
* @param {string} publicFolder
|
|
108
97
|
*/
|
|
98
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'pathname' implicitly has an 'any' type.
|
|
109
99
|
const getStatic = async function (pathname, publicFolder) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return `/${path.relative(publicFolder, file)}`
|
|
120
|
-
}
|
|
121
|
-
|
|
100
|
+
const alternatives = [pathname, ...alternativePathsFor(pathname)].map((filePath) => path.resolve(publicFolder, filePath.slice(1)));
|
|
101
|
+
const file = await locatePath(alternatives);
|
|
102
|
+
if (file === undefined) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return `/${path.relative(publicFolder, file)}`;
|
|
106
|
+
};
|
|
107
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'match' implicitly has an 'any' type.
|
|
122
108
|
const isExternal = function (match) {
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
109
|
+
return match.to && match.to.match(/^https?:\/\//);
|
|
110
|
+
};
|
|
111
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'hash' implicitly has an 'any' typ... Remove this comment to see the full error message
|
|
126
112
|
const stripOrigin = function ({ hash, pathname, search }) {
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
113
|
+
return `${pathname}${search}${hash}`;
|
|
114
|
+
};
|
|
115
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'dest' implicitly has an 'any' typ... Remove this comment to see the full error message
|
|
130
116
|
const proxyToExternalUrl = function ({ dest, destURL, req, res }) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
117
|
+
console.log(`${NETLIFYDEVLOG} Proxying to ${dest}`);
|
|
118
|
+
const handler = createProxyMiddleware({
|
|
119
|
+
target: dest.origin,
|
|
120
|
+
changeOrigin: true,
|
|
121
|
+
pathRewrite: () => destURL,
|
|
122
|
+
...(Buffer.isBuffer(req.originalBody) && { buffer: toReadableStream(req.originalBody) }),
|
|
123
|
+
});
|
|
124
|
+
return handler(req, res, () => { });
|
|
125
|
+
};
|
|
126
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonUrl' implicitly has an 'any'... Remove this comment to see the full error message
|
|
141
127
|
const handleAddonUrl = function ({ addonUrl, req, res }) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
128
|
+
const dest = new URL(addonUrl);
|
|
129
|
+
const destURL = stripOrigin(dest);
|
|
130
|
+
return proxyToExternalUrl({ req, res, dest, destURL });
|
|
131
|
+
};
|
|
132
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'match' implicitly has an 'any' type.
|
|
148
133
|
const isRedirect = function (match) {
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
134
|
+
return match.status && match.status >= 300 && match.status <= 400;
|
|
135
|
+
};
|
|
136
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'publicFolder' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
152
137
|
const render404 = async function (publicFolder) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
138
|
+
const maybe404Page = path.resolve(publicFolder, '404.html');
|
|
139
|
+
try {
|
|
140
|
+
const isFile = await isFileAsync(maybe404Page);
|
|
141
|
+
if (isFile)
|
|
142
|
+
return await readFile(maybe404Page, 'utf-8');
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
|
|
146
|
+
console.warn(NETLIFYDEVWARN, 'Error while serving 404.html file', error.message);
|
|
147
|
+
}
|
|
148
|
+
return 'Not Found';
|
|
149
|
+
};
|
|
164
150
|
// Used as an optimization to avoid dual lookups for missing assets
|
|
165
|
-
const assetExtensionRegExp = /\.(html?|png|jpg|js|css|svg|gif|ico|woff|woff2)
|
|
166
|
-
|
|
151
|
+
const assetExtensionRegExp = /\.(html?|png|jpg|js|css|svg|gif|ico|woff|woff2)$/;
|
|
152
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'url' implicitly has an 'any' type.
|
|
167
153
|
const alternativePathsFor = function (url) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const paths = []
|
|
173
|
-
if (url[url.length - 1] === '/') {
|
|
174
|
-
const end = url.length - 1
|
|
175
|
-
if (url !== '/') {
|
|
176
|
-
paths.push(`${url.slice(0, end)}.html`, `${url.slice(0, end)}.htm`)
|
|
177
|
-
}
|
|
178
|
-
paths.push(`${url}index.html`, `${url}index.htm`)
|
|
179
|
-
} else if (!assetExtensionRegExp.test(url)) {
|
|
180
|
-
paths.push(`${url}.html`, `${url}.htm`, `${url}/index.html`, `${url}/index.htm`)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return paths
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const serveRedirect = async function ({
|
|
187
|
-
env,
|
|
188
|
-
functionsRegistry,
|
|
189
|
-
imageProxy,
|
|
190
|
-
match,
|
|
191
|
-
options,
|
|
192
|
-
proxy,
|
|
193
|
-
req,
|
|
194
|
-
res,
|
|
195
|
-
siteInfo,
|
|
196
|
-
}) {
|
|
197
|
-
if (!match) return proxy.web(req, res, options)
|
|
198
|
-
|
|
199
|
-
options = options || req.proxyOptions || {}
|
|
200
|
-
options.match = null
|
|
201
|
-
|
|
202
|
-
if (match.proxyHeaders && Object.keys(match.proxyHeaders).length >= 0) {
|
|
203
|
-
Object.entries(match.proxyHeaders).forEach(([key, value]) => {
|
|
204
|
-
req.headers[key] = value
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (match.signingSecret) {
|
|
209
|
-
const signingSecretVar = env[match.signingSecret]
|
|
210
|
-
|
|
211
|
-
if (signingSecretVar) {
|
|
212
|
-
req.headers['x-nf-sign'] = signRedirect({
|
|
213
|
-
deployContext: 'dev',
|
|
214
|
-
secret: signingSecretVar.value,
|
|
215
|
-
siteID: siteInfo.id,
|
|
216
|
-
siteURL: siteInfo.url,
|
|
217
|
-
})
|
|
218
|
-
} else {
|
|
219
|
-
log(
|
|
220
|
-
NETLIFYDEVWARN,
|
|
221
|
-
`Could not sign redirect because environment variable ${chalk.yellow(match.signingSecret)} is not set`,
|
|
222
|
-
)
|
|
154
|
+
if (isFunction(true, url)) {
|
|
155
|
+
return [];
|
|
223
156
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const urlForAddons = getAddonUrl(options.addonsUrls, req)
|
|
231
|
-
if (urlForAddons) {
|
|
232
|
-
return handleAddonUrl({ req, res, addonUrl: urlForAddons })
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const originalURL = req.url
|
|
236
|
-
if (match.exceptions && match.exceptions.JWT) {
|
|
237
|
-
// Some values of JWT can start with :, so, make sure to normalize them
|
|
238
|
-
const expectedRoles = new Set(
|
|
239
|
-
match.exceptions.JWT.split(',').map((value) => (value.startsWith(':') ? value.slice(1) : value)),
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
const cookieValues = cookie.parse(req.headers.cookie || '')
|
|
243
|
-
const token = cookieValues.nf_jwt
|
|
244
|
-
|
|
245
|
-
// Serve not found by default
|
|
246
|
-
req.url = '/.netlify/non-existent-path'
|
|
247
|
-
|
|
248
|
-
if (token) {
|
|
249
|
-
let jwtValue = {}
|
|
250
|
-
try {
|
|
251
|
-
jwtValue = jwtDecode(token) || {}
|
|
252
|
-
} catch (error) {
|
|
253
|
-
console.warn(NETLIFYDEVWARN, 'Error while decoding JWT provided in request', error.message)
|
|
254
|
-
res.writeHead(400)
|
|
255
|
-
res.end('Invalid JWT provided. Please see logs for more info.')
|
|
256
|
-
return
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if ((jwtValue.exp || 0) < Math.round(Date.now() / MILLISEC_TO_SEC)) {
|
|
260
|
-
console.warn(NETLIFYDEVWARN, 'Expired JWT provided in request', req.url)
|
|
261
|
-
} else {
|
|
262
|
-
const presentedRoles = getProperty(jwtValue, options.jwtRolePath) || []
|
|
263
|
-
if (!Array.isArray(presentedRoles)) {
|
|
264
|
-
console.warn(NETLIFYDEVWARN, `Invalid roles value provided in JWT ${options.jwtRolePath}`, presentedRoles)
|
|
265
|
-
res.writeHead(400)
|
|
266
|
-
res.end('Invalid JWT provided. Please see logs for more info.')
|
|
267
|
-
return
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Restore the URL if everything is correct
|
|
271
|
-
if (presentedRoles.some((pr) => expectedRoles.has(pr))) {
|
|
272
|
-
req.url = originalURL
|
|
157
|
+
const paths = [];
|
|
158
|
+
if (url[url.length - 1] === '/') {
|
|
159
|
+
const end = url.length - 1;
|
|
160
|
+
if (url !== '/') {
|
|
161
|
+
paths.push(`${url.slice(0, end)}.html`, `${url.slice(0, end)}.htm`);
|
|
273
162
|
}
|
|
274
|
-
|
|
163
|
+
paths.push(`${url}index.html`, `${url}index.htm`);
|
|
275
164
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const reqUrl = reqToURL(req, req.url)
|
|
279
|
-
|
|
280
|
-
const staticFile = await getStatic(decodeURIComponent(reqUrl.pathname), options.publicFolder)
|
|
281
|
-
if (staticFile) {
|
|
282
|
-
req.url = encodeURI(staticFile) + reqUrl.search
|
|
283
|
-
// if there is an existing static file and it is not a forced redirect, return the file
|
|
284
|
-
if (!match.force) {
|
|
285
|
-
return proxy.web(req, res, { ...options, staticFile })
|
|
165
|
+
else if (!assetExtensionRegExp.test(url)) {
|
|
166
|
+
paths.push(`${url}.html`, `${url}.htm`, `${url}/index.html`, `${url}/index.htm`);
|
|
286
167
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
168
|
+
return paths;
|
|
169
|
+
};
|
|
170
|
+
const serveRedirect = async function ({
|
|
171
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
172
|
+
env,
|
|
173
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message
|
|
174
|
+
functionsRegistry,
|
|
175
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'an... Remove this comment to see the full error message
|
|
176
|
+
imageProxy,
|
|
177
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'match' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
178
|
+
match,
|
|
179
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'options' implicitly has an 'any' ... Remove this comment to see the full error message
|
|
180
|
+
options,
|
|
181
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'proxy' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
182
|
+
proxy,
|
|
183
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'req' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
184
|
+
req,
|
|
185
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'res' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
186
|
+
res,
|
|
187
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message
|
|
188
|
+
siteInfo, }) {
|
|
189
|
+
if (!match)
|
|
190
|
+
return proxy.web(req, res, options);
|
|
191
|
+
options = options || req.proxyOptions || {};
|
|
192
|
+
options.match = null;
|
|
193
|
+
if (match.proxyHeaders && Object.keys(match.proxyHeaders).length >= 0) {
|
|
194
|
+
Object.entries(match.proxyHeaders).forEach(([key, value]) => {
|
|
195
|
+
req.headers[key] = value;
|
|
196
|
+
});
|
|
309
197
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
198
|
+
if (match.signingSecret) {
|
|
199
|
+
const signingSecretVar = env[match.signingSecret];
|
|
200
|
+
if (signingSecretVar) {
|
|
201
|
+
req.headers['x-nf-sign'] = signRedirect({
|
|
202
|
+
deployContext: 'dev',
|
|
203
|
+
secret: signingSecretVar.value,
|
|
204
|
+
siteID: siteInfo.id,
|
|
205
|
+
siteURL: siteInfo.url,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
log(NETLIFYDEVWARN, `Could not sign redirect because environment variable ${chalk.yellow(match.signingSecret)} is not set`);
|
|
210
|
+
}
|
|
320
211
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.log(`${NETLIFYDEVLOG} Redirecting ${req.url} to ${destURL}`)
|
|
324
|
-
res.writeHead(match.status, {
|
|
325
|
-
Location: destURL,
|
|
326
|
-
'Cache-Control': 'no-cache',
|
|
327
|
-
})
|
|
328
|
-
res.end(`Redirecting to ${destURL}`)
|
|
329
|
-
|
|
330
|
-
return
|
|
212
|
+
if (isFunction(options.functionsPort, req.url)) {
|
|
213
|
+
return proxy.web(req, res, { target: options.functionsServer });
|
|
331
214
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
req.method === 'POST' &&
|
|
336
|
-
!isInternal(req.url) &&
|
|
337
|
-
!isInternal(destURL) &&
|
|
338
|
-
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
|
|
339
|
-
) {
|
|
340
|
-
return proxy.web(req, res, { target: options.functionsServer })
|
|
215
|
+
const urlForAddons = getAddonUrl(options.addonsUrls, req);
|
|
216
|
+
if (urlForAddons) {
|
|
217
|
+
return handleAddonUrl({ req, res, addonUrl: urlForAddons });
|
|
341
218
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
219
|
+
const originalURL = req.url;
|
|
220
|
+
if (match.exceptions && match.exceptions.JWT) {
|
|
221
|
+
// Some values of JWT can start with :, so, make sure to normalize them
|
|
222
|
+
const expectedRoles = new Set(
|
|
223
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'value' implicitly has an 'any' type.
|
|
224
|
+
match.exceptions.JWT.split(',').map((value) => (value.startsWith(':') ? value.slice(1) : value)));
|
|
225
|
+
const cookieValues = cookie.parse(req.headers.cookie || '');
|
|
226
|
+
const token = cookieValues.nf_jwt;
|
|
227
|
+
// Serve not found by default
|
|
228
|
+
req.url = '/.netlify/non-existent-path';
|
|
229
|
+
if (token) {
|
|
230
|
+
let jwtValue = {};
|
|
231
|
+
try {
|
|
232
|
+
// @ts-expect-error TS(2349) This expression is not callable
|
|
233
|
+
jwtValue = jwtDecode(token) || {};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
|
|
237
|
+
console.warn(NETLIFYDEVWARN, 'Error while decoding JWT provided in request', error.message);
|
|
238
|
+
res.writeHead(400);
|
|
239
|
+
res.end('Invalid JWT provided. Please see logs for more info.');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// @ts-expect-error TS(2339) FIXME: Property 'exp' does not exist on type '{}'.
|
|
243
|
+
if ((jwtValue.exp || 0) < Math.round(Date.now() / MILLISEC_TO_SEC)) {
|
|
244
|
+
console.warn(NETLIFYDEVWARN, 'Expired JWT provided in request', req.url);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const presentedRoles = getProperty(jwtValue, options.jwtRolePath) || [];
|
|
248
|
+
if (!Array.isArray(presentedRoles)) {
|
|
249
|
+
console.warn(NETLIFYDEVWARN, `Invalid roles value provided in JWT ${options.jwtRolePath}`, presentedRoles);
|
|
250
|
+
res.writeHead(400);
|
|
251
|
+
res.end('Invalid JWT provided. Please see logs for more info.');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Restore the URL if everything is correct
|
|
255
|
+
if (presentedRoles.some((pr) => expectedRoles.has(pr))) {
|
|
256
|
+
req.url = originalURL;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
354
260
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const url = reqToURL(req, originalURL)
|
|
364
|
-
req.headers['x-netlify-original-pathname'] = url.pathname
|
|
365
|
-
req.headers['x-netlify-original-search'] = url.search
|
|
366
|
-
|
|
367
|
-
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer })
|
|
261
|
+
const reqUrl = reqToURL(req, req.url);
|
|
262
|
+
const staticFile = await getStatic(decodeURIComponent(reqUrl.pathname), options.publicFolder);
|
|
263
|
+
if (staticFile) {
|
|
264
|
+
req.url = encodeURI(staticFile) + reqUrl.search;
|
|
265
|
+
// if there is an existing static file and it is not a forced redirect, return the file
|
|
266
|
+
if (!match.force) {
|
|
267
|
+
return proxy.web(req, res, { ...options, staticFile });
|
|
268
|
+
}
|
|
368
269
|
}
|
|
369
|
-
if (
|
|
370
|
-
|
|
270
|
+
if (match.force404) {
|
|
271
|
+
res.writeHead(404);
|
|
272
|
+
res.end(await render404(options.publicFolder));
|
|
273
|
+
return;
|
|
371
274
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
275
|
+
if (match.force || !staticFile || !options.framework || req.method === 'POST') {
|
|
276
|
+
// construct destination URL from redirect rule match
|
|
277
|
+
const dest = new URL(match.to, `${reqUrl.protocol}//${reqUrl.host}`);
|
|
278
|
+
// We pass through request params if the redirect rule
|
|
279
|
+
// doesn't have any query params
|
|
280
|
+
if ([...dest.searchParams].length === 0) {
|
|
281
|
+
dest.searchParams.forEach((_, key) => {
|
|
282
|
+
dest.searchParams.delete(key);
|
|
283
|
+
});
|
|
284
|
+
const requestParams = new URLSearchParams(reqUrl.searchParams);
|
|
285
|
+
requestParams.forEach((val, key) => {
|
|
286
|
+
dest.searchParams.append(key, val);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
let destURL = stripOrigin(dest);
|
|
290
|
+
if (isExternal(match)) {
|
|
291
|
+
if (isRedirect(match)) {
|
|
292
|
+
// This is a redirect, so we set the complete external URL as destination
|
|
293
|
+
destURL = `${dest}`;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
return proxyToExternalUrl({ req, res, dest, destURL });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (isRedirect(match)) {
|
|
300
|
+
console.log(`${NETLIFYDEVLOG} Redirecting ${req.url} to ${destURL}`);
|
|
301
|
+
res.writeHead(match.status, {
|
|
302
|
+
Location: destURL,
|
|
303
|
+
'Cache-Control': 'no-cache',
|
|
304
|
+
});
|
|
305
|
+
res.end(`Redirecting to ${destURL}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const ct = req.headers['content-type'] ? contentType.parse(req).type : '';
|
|
309
|
+
if (req.method === 'POST' &&
|
|
310
|
+
!isInternal(req.url) &&
|
|
311
|
+
!isInternal(destURL) &&
|
|
312
|
+
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')) {
|
|
313
|
+
return proxy.web(req, res, { target: options.functionsServer });
|
|
314
|
+
}
|
|
315
|
+
const matchingFunction = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method));
|
|
316
|
+
const destStaticFile = await getStatic(dest.pathname, options.publicFolder);
|
|
317
|
+
let statusValue;
|
|
318
|
+
if (match.force ||
|
|
319
|
+
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || matchingFunction))) {
|
|
320
|
+
req.url = destStaticFile ? destStaticFile + dest.search : destURL;
|
|
321
|
+
const { status } = match;
|
|
322
|
+
statusValue = status;
|
|
323
|
+
console.log(`${NETLIFYDEVLOG} Rewrote URL to`, req.url);
|
|
324
|
+
}
|
|
325
|
+
if (matchingFunction) {
|
|
326
|
+
const functionHeaders = matchingFunction.func
|
|
327
|
+
? {
|
|
328
|
+
[NFFunctionName]: matchingFunction.func?.name,
|
|
329
|
+
[NFFunctionRoute]: matchingFunction.route,
|
|
330
|
+
}
|
|
331
|
+
: {};
|
|
332
|
+
const url = reqToURL(req, originalURL);
|
|
333
|
+
req.headers['x-netlify-original-pathname'] = url.pathname;
|
|
334
|
+
req.headers['x-netlify-original-search'] = url.search;
|
|
335
|
+
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer });
|
|
336
|
+
}
|
|
337
|
+
if (isImageRequest(req)) {
|
|
338
|
+
return imageProxy(req, res);
|
|
339
|
+
}
|
|
340
|
+
const addonUrl = getAddonUrl(options.addonsUrls, req);
|
|
341
|
+
if (addonUrl) {
|
|
342
|
+
return handleAddonUrl({ req, res, addonUrl });
|
|
343
|
+
}
|
|
344
|
+
return proxy.web(req, res, { ...options, status: statusValue });
|
|
375
345
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return proxy.web(req, res, options)
|
|
381
|
-
}
|
|
382
|
-
|
|
346
|
+
return proxy.web(req, res, options);
|
|
347
|
+
};
|
|
348
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
383
349
|
const reqToURL = function (req, pathname) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}`,
|
|
389
|
-
)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const MILLISEC_TO_SEC = 1e3
|
|
393
|
-
|
|
350
|
+
return new URL(pathname, `${req.protocol || (req.headers.scheme && `${req.headers.scheme}:`) || 'http:'}//${req.headers.host || req.hostname}`);
|
|
351
|
+
};
|
|
352
|
+
const MILLISEC_TO_SEC = 1e3;
|
|
353
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'an... Remove this comment to see the full error message
|
|
394
354
|
const initializeProxy = async function ({ configPath, distDir, env, host, imageProxy, port, projectDir, siteInfo }) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
355
|
+
const proxy = httpProxy.createProxyServer({
|
|
356
|
+
selfHandleResponse: true,
|
|
357
|
+
target: {
|
|
358
|
+
host,
|
|
359
|
+
port,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
const headersFiles = [...new Set([path.resolve(projectDir, '_headers'), path.resolve(distDir, '_headers')])];
|
|
363
|
+
let headers = await parseHeaders({ headersFiles, configPath });
|
|
364
|
+
const watchedHeadersFiles = configPath === undefined ? headersFiles : [...headersFiles, configPath];
|
|
365
|
+
onChanges(watchedHeadersFiles, async () => {
|
|
366
|
+
const existingHeadersFiles = await pFilter(watchedHeadersFiles, fileExistsAsync);
|
|
367
|
+
console.log(`${NETLIFYDEVLOG} Reloading headers files from`, existingHeadersFiles.map((headerFile) => path.relative(projectDir, headerFile)));
|
|
368
|
+
headers = await parseHeaders({ headersFiles, configPath });
|
|
369
|
+
});
|
|
370
|
+
// @ts-expect-error TS(2339) FIXME: Property 'before' does not exist on type 'Server'.
|
|
371
|
+
proxy.before('web', 'stream', (req) => {
|
|
372
|
+
// See https://github.com/http-party/node-http-proxy/issues/1219#issuecomment-511110375
|
|
373
|
+
if (req.headers.expect) {
|
|
374
|
+
req.__expectHeader = req.headers.expect;
|
|
375
|
+
delete req.headers.expect;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
proxy.on('error', (_, req, res) => {
|
|
379
|
+
// @ts-expect-error TS(2339) FIXME: Property 'writeHead' does not exist on type 'Socke... Remove this comment to see the full error message
|
|
380
|
+
res.writeHead(500, {
|
|
381
|
+
'Content-Type': 'text/plain',
|
|
382
|
+
});
|
|
383
|
+
const message = isEdgeFunctionsRequest(req)
|
|
384
|
+
? 'There was an error with an Edge Function. Please check the terminal for more details.'
|
|
385
|
+
: 'Could not proxy request.';
|
|
386
|
+
res.end(message);
|
|
387
|
+
});
|
|
388
|
+
proxy.on('proxyReq', (proxyReq, req) => {
|
|
389
|
+
const requestID = generateRequestID();
|
|
390
|
+
proxyReq.setHeader(NFRequestID, requestID);
|
|
391
|
+
req.headers[NFRequestID] = requestID;
|
|
392
|
+
if (isEdgeFunctionsRequest(req)) {
|
|
393
|
+
handleProxyRequest(req, proxyReq);
|
|
394
|
+
}
|
|
395
|
+
// @ts-expect-error TS(2339) FIXME: Property '__expectHeader' does not exist on type '... Remove this comment to see the full error message
|
|
396
|
+
if (req.__expectHeader) {
|
|
397
|
+
// @ts-expect-error TS(2339) FIXME: Property '__expectHeader' does not exist on type '... Remove this comment to see the full error message
|
|
398
|
+
proxyReq.setHeader('Expect', req.__expectHeader);
|
|
399
|
+
}
|
|
400
|
+
// @ts-expect-error TS(2339) FIXME: Property 'originalBody' does not exist on type 'In... Remove this comment to see the full error message
|
|
401
|
+
if (req.originalBody) {
|
|
402
|
+
// @ts-expect-error TS(2339) FIXME: Property 'originalBody' does not exist on type 'In... Remove this comment to see the full error message
|
|
403
|
+
proxyReq.write(req.originalBody);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
proxy.on('proxyRes', (proxyRes, req, res) => {
|
|
407
|
+
res.setHeader('server', 'Netlify');
|
|
408
|
+
const requestID = req.headers[NFRequestID];
|
|
409
|
+
if (requestID) {
|
|
410
|
+
res.setHeader(NFRequestID, requestID);
|
|
411
|
+
}
|
|
412
|
+
if (proxyRes.statusCode === 404 || proxyRes.statusCode === 403) {
|
|
413
|
+
// If a request for `/path` has failed, we'll a few variations like
|
|
414
|
+
// `/path/index.html` to mimic the CDN behavior.
|
|
415
|
+
// @ts-expect-error TS(2339) FIXME: Property 'alternativePaths' does not exist on type... Remove this comment to see the full error message
|
|
416
|
+
if (req.alternativePaths && req.alternativePaths.length !== 0) {
|
|
417
|
+
// @ts-expect-error TS(2339) FIXME: Property 'alternativePaths' does not exist on type... Remove this comment to see the full error message
|
|
418
|
+
req.url = req.alternativePaths.shift();
|
|
419
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
420
|
+
return proxy.web(req, res, req.proxyOptions);
|
|
421
|
+
}
|
|
422
|
+
// The request has failed but we might still have a matching redirect
|
|
423
|
+
// rule (without `force`) that should kick in. This is how we mimic the
|
|
424
|
+
// file shadowing behavior from the CDN.
|
|
425
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
426
|
+
if (req.proxyOptions && req.proxyOptions.match) {
|
|
427
|
+
return serveRedirect({
|
|
428
|
+
// We don't want to match functions at this point because any redirects
|
|
429
|
+
// to functions will have already been processed, so we don't supply a
|
|
430
|
+
// functions registry to `serveRedirect`.
|
|
431
|
+
functionsRegistry: null,
|
|
432
|
+
req,
|
|
433
|
+
res,
|
|
434
|
+
proxy: handlers,
|
|
435
|
+
imageProxy,
|
|
436
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
437
|
+
match: req.proxyOptions.match,
|
|
438
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
439
|
+
options: req.proxyOptions,
|
|
440
|
+
siteInfo,
|
|
441
|
+
env,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
446
|
+
if (req.proxyOptions.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
|
|
447
|
+
req.url = proxyRes.headers.location;
|
|
448
|
+
return serveRedirect({
|
|
449
|
+
// We don't want to match functions at this point because any redirects
|
|
450
|
+
// to functions will have already been processed, so we don't supply a
|
|
451
|
+
// functions registry to `serveRedirect`.
|
|
452
|
+
functionsRegistry: null,
|
|
453
|
+
req,
|
|
454
|
+
res,
|
|
455
|
+
proxy: handlers,
|
|
456
|
+
imageProxy,
|
|
457
|
+
match: null,
|
|
458
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
459
|
+
options: req.proxyOptions,
|
|
460
|
+
siteInfo,
|
|
461
|
+
env,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
// @ts-expect-error TS(7034) FIXME: Variable 'responseData' implicitly has type 'any[]... Remove this comment to see the full error message
|
|
465
|
+
const responseData = [];
|
|
466
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type 'string | undefined' is not assig... Remove this comment to see the full error message
|
|
467
|
+
const requestURL = new URL(req.url, `http://${req.headers.host || '127.0.0.1'}`);
|
|
468
|
+
const headersRules = headersForPath(headers, requestURL.pathname);
|
|
469
|
+
// for streamed responses, we can't do etag generation nor error templates.
|
|
470
|
+
// we'll just stream them through!
|
|
471
|
+
const isStreamedResponse = proxyRes.headers['content-length'] === undefined;
|
|
472
|
+
if (isStreamedResponse) {
|
|
473
|
+
Object.entries(headersRules).forEach(([key, val]) => {
|
|
474
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
|
|
475
|
+
res.setHeader(key, val);
|
|
476
|
+
});
|
|
477
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
478
|
+
res.writeHead(req.proxyOptions.status || proxyRes.statusCode, proxyRes.headers);
|
|
479
|
+
proxyRes.on('data', function onData(data) {
|
|
480
|
+
res.write(data);
|
|
481
|
+
});
|
|
482
|
+
proxyRes.on('end', function onEnd() {
|
|
483
|
+
res.end();
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
proxyRes.on('data', function onData(data) {
|
|
488
|
+
responseData.push(data);
|
|
489
|
+
});
|
|
490
|
+
proxyRes.on('end', async function onEnd() {
|
|
491
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message
|
|
492
|
+
const responseBody = Buffer.concat(responseData);
|
|
493
|
+
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
|
|
494
|
+
let responseStatus = req.proxyOptions.status || proxyRes.statusCode;
|
|
495
|
+
// `req[shouldGenerateETag]` may contain a function that determines
|
|
496
|
+
// whether the response should have an ETag header.
|
|
497
|
+
if (
|
|
498
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
499
|
+
typeof req[shouldGenerateETag] === 'function' &&
|
|
500
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
501
|
+
req[shouldGenerateETag]({ statusCode: responseStatus }) === true) {
|
|
502
|
+
const etag = generateETag(responseBody, { weak: true });
|
|
503
|
+
if (req.headers['if-none-match'] === etag) {
|
|
504
|
+
responseStatus = 304;
|
|
505
|
+
}
|
|
506
|
+
res.setHeader('etag', etag);
|
|
507
|
+
}
|
|
508
|
+
Object.entries(headersRules).forEach(([key, val]) => {
|
|
509
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
|
|
510
|
+
res.setHeader(key, val);
|
|
511
|
+
});
|
|
512
|
+
const isUncaughtError = proxyRes.headers['x-nf-uncaught-error'] === '1';
|
|
513
|
+
if (isEdgeFunctionsRequest(req) && isUncaughtError) {
|
|
514
|
+
const acceptsHtml = req.headers && req.headers.accept && req.headers.accept.includes('text/html');
|
|
515
|
+
const decompressedBody = await decompressResponseBody(responseBody, proxyRes.headers['content-encoding']);
|
|
516
|
+
const formattedBody = formatEdgeFunctionError(decompressedBody, acceptsHtml);
|
|
517
|
+
const errorResponse = acceptsHtml
|
|
518
|
+
? await renderErrorTemplate(formattedBody, './templates/function-error.html', 'edge function')
|
|
519
|
+
: formattedBody;
|
|
520
|
+
const contentLength = Buffer.from(errorResponse, 'utf8').byteLength;
|
|
521
|
+
res.setHeader('content-length', contentLength);
|
|
522
|
+
res.statusCode = 500;
|
|
523
|
+
res.write(errorResponse);
|
|
524
|
+
return res.end();
|
|
525
|
+
}
|
|
526
|
+
res.writeHead(responseStatus, proxyRes.headers);
|
|
527
|
+
if (responseStatus !== 304) {
|
|
528
|
+
res.write(responseBody);
|
|
529
|
+
}
|
|
530
|
+
res.end();
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
const handlers = {
|
|
534
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
535
|
+
web: (req, res, options) => {
|
|
536
|
+
const requestURL = new URL(req.url, 'http://127.0.0.1');
|
|
537
|
+
req.proxyOptions = options;
|
|
538
|
+
req.alternativePaths = alternativePathsFor(requestURL.pathname).map((filePath) => filePath + requestURL.search);
|
|
539
|
+
// Ref: https://nodejs.org/api/net.html#net_socket_remoteaddress
|
|
540
|
+
req.headers['x-forwarded-for'] = req.connection.remoteAddress || '';
|
|
541
|
+
return proxy.web(req, res, options);
|
|
542
|
+
},
|
|
543
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
544
|
+
ws: (req, socket, head) => proxy.ws(req, socket, head),
|
|
545
|
+
};
|
|
546
|
+
return handlers;
|
|
547
|
+
};
|
|
548
|
+
const onRequest = async ({
|
|
549
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonsUrls' implicitly has an 'an... Remove this comment to see the full error message
|
|
550
|
+
addonsUrls,
|
|
551
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'edgeFunctionsProxy' implicitly ha... Remove this comment to see the full error message
|
|
552
|
+
edgeFunctionsProxy,
|
|
553
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
554
|
+
env,
|
|
555
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message
|
|
556
|
+
functionsRegistry,
|
|
557
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'functionsServer' implicitly has a... Remove this comment to see the full error message
|
|
558
|
+
functionsServer,
|
|
559
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'an... Remove this comment to see the full error message
|
|
560
|
+
imageProxy,
|
|
561
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'proxy' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
562
|
+
proxy,
|
|
563
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'rewriter' implicitly has an 'any'... Remove this comment to see the full error message
|
|
564
|
+
rewriter,
|
|
565
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message
|
|
566
|
+
settings,
|
|
567
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message
|
|
568
|
+
siteInfo, },
|
|
569
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
570
|
+
req,
|
|
571
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'res' implicitly has an 'any' type.
|
|
572
|
+
res) => {
|
|
573
|
+
req.originalBody = ['GET', 'OPTIONS', 'HEAD'].includes(req.method)
|
|
574
|
+
? null
|
|
575
|
+
: await createStreamPromise(req, BYTES_LIMIT);
|
|
576
|
+
const edgeFunctionsProxyURL = await edgeFunctionsProxy(req, res);
|
|
577
|
+
if (edgeFunctionsProxyURL !== undefined) {
|
|
578
|
+
return proxy.web(req, res, { target: edgeFunctionsProxyURL });
|
|
459
579
|
}
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// to functions will have already been processed, so we don't supply a
|
|
476
|
-
// functions registry to `serveRedirect`.
|
|
477
|
-
functionsRegistry: null,
|
|
478
|
-
req,
|
|
479
|
-
res,
|
|
480
|
-
proxy: handlers,
|
|
481
|
-
imageProxy,
|
|
482
|
-
match: req.proxyOptions.match,
|
|
483
|
-
options: req.proxyOptions,
|
|
484
|
-
siteInfo,
|
|
485
|
-
env,
|
|
486
|
-
})
|
|
487
|
-
}
|
|
580
|
+
const functionMatch = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(req.url, req.method));
|
|
581
|
+
if (functionMatch) {
|
|
582
|
+
// Setting an internal header with the function name so that we don't
|
|
583
|
+
// have to match the URL again in the functions server.
|
|
584
|
+
/** @type {Record<string, string>} */
|
|
585
|
+
const headers = {};
|
|
586
|
+
if (functionMatch.func) {
|
|
587
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
588
|
+
headers[NFFunctionName] = functionMatch.func.name;
|
|
589
|
+
}
|
|
590
|
+
if (functionMatch.route) {
|
|
591
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
592
|
+
headers[NFFunctionRoute] = functionMatch.route.pattern;
|
|
593
|
+
}
|
|
594
|
+
return proxy.web(req, res, { headers, target: functionsServer });
|
|
488
595
|
}
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
return serveRedirect({
|
|
493
|
-
// We don't want to match functions at this point because any redirects
|
|
494
|
-
// to functions will have already been processed, so we don't supply a
|
|
495
|
-
// functions registry to `serveRedirect`.
|
|
496
|
-
functionsRegistry: null,
|
|
497
|
-
req,
|
|
498
|
-
res,
|
|
499
|
-
proxy: handlers,
|
|
500
|
-
imageProxy,
|
|
501
|
-
match: null,
|
|
502
|
-
options: req.proxyOptions,
|
|
503
|
-
siteInfo,
|
|
504
|
-
env,
|
|
505
|
-
})
|
|
596
|
+
const addonUrl = getAddonUrl(addonsUrls, req);
|
|
597
|
+
if (addonUrl) {
|
|
598
|
+
return handleAddonUrl({ req, res, addonUrl });
|
|
506
599
|
}
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
res
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
proxyRes.on('end', function onEnd() {
|
|
526
|
-
res.end()
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
return
|
|
600
|
+
const match = await rewriter(req);
|
|
601
|
+
const options = {
|
|
602
|
+
match,
|
|
603
|
+
addonsUrls,
|
|
604
|
+
target: `http://${isIPv6(settings.frameworkHost) ? `[${settings.frameworkHost}]` : settings.frameworkHost}:${settings.frameworkPort}`,
|
|
605
|
+
publicFolder: settings.dist,
|
|
606
|
+
functionsServer,
|
|
607
|
+
functionsPort: settings.functionsPort,
|
|
608
|
+
jwtRolePath: settings.jwtRolePath,
|
|
609
|
+
framework: settings.framework,
|
|
610
|
+
};
|
|
611
|
+
if (match) {
|
|
612
|
+
// We don't want to generate an ETag for 3xx redirects.
|
|
613
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'statusCode' implicitly has an 'an... Remove this comment to see the full error message
|
|
614
|
+
req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400;
|
|
615
|
+
return serveRedirect({ req, res, proxy, imageProxy, match, options, siteInfo, env, functionsRegistry });
|
|
530
616
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
// whether the response should have an ETag header.
|
|
543
|
-
if (
|
|
544
|
-
typeof req[shouldGenerateETag] === 'function' &&
|
|
545
|
-
req[shouldGenerateETag]({ statusCode: responseStatus }) === true
|
|
546
|
-
) {
|
|
547
|
-
const etag = generateETag(responseBody, { weak: true })
|
|
548
|
-
|
|
549
|
-
if (req.headers['if-none-match'] === etag) {
|
|
550
|
-
responseStatus = 304
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
res.setHeader('etag', etag)
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
Object.entries(headersRules).forEach(([key, val]) => {
|
|
557
|
-
res.setHeader(key, val)
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
const isUncaughtError = proxyRes.headers['x-nf-uncaught-error'] === '1'
|
|
561
|
-
|
|
562
|
-
if (isEdgeFunctionsRequest(req) && isUncaughtError) {
|
|
563
|
-
const acceptsHtml = req.headers && req.headers.accept && req.headers.accept.includes('text/html')
|
|
564
|
-
const decompressedBody = await decompressResponseBody(responseBody, proxyRes.headers['content-encoding'])
|
|
565
|
-
const formattedBody = formatEdgeFunctionError(decompressedBody, acceptsHtml)
|
|
566
|
-
const errorResponse = acceptsHtml
|
|
567
|
-
? await renderErrorTemplate(formattedBody, './templates/function-error.html', 'edge function')
|
|
568
|
-
: formattedBody
|
|
569
|
-
const contentLength = Buffer.from(errorResponse, 'utf8').byteLength
|
|
570
|
-
|
|
571
|
-
res.setHeader('content-length', contentLength)
|
|
572
|
-
res.statusCode = 500
|
|
573
|
-
res.write(errorResponse)
|
|
574
|
-
return res.end()
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
res.writeHead(responseStatus, proxyRes.headers)
|
|
578
|
-
|
|
579
|
-
if (responseStatus !== 304) {
|
|
580
|
-
res.write(responseBody)
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
res.end()
|
|
584
|
-
})
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
const handlers = {
|
|
588
|
-
web: (req, res, options) => {
|
|
589
|
-
const requestURL = new URL(req.url, 'http://127.0.0.1')
|
|
590
|
-
req.proxyOptions = options
|
|
591
|
-
req.alternativePaths = alternativePathsFor(requestURL.pathname).map((filePath) => filePath + requestURL.search)
|
|
592
|
-
// Ref: https://nodejs.org/api/net.html#net_socket_remoteaddress
|
|
593
|
-
req.headers['x-forwarded-for'] = req.connection.remoteAddress || ''
|
|
594
|
-
return proxy.web(req, res, options)
|
|
595
|
-
},
|
|
596
|
-
ws: (req, socket, head) => proxy.ws(req, socket, head),
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return handlers
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const onRequest = async (
|
|
603
|
-
{
|
|
604
|
-
addonsUrls,
|
|
605
|
-
edgeFunctionsProxy,
|
|
606
|
-
env,
|
|
607
|
-
functionsRegistry,
|
|
608
|
-
functionsServer,
|
|
609
|
-
imageProxy,
|
|
610
|
-
proxy,
|
|
611
|
-
rewriter,
|
|
612
|
-
settings,
|
|
613
|
-
siteInfo,
|
|
614
|
-
},
|
|
615
|
-
req,
|
|
616
|
-
res,
|
|
617
|
-
) => {
|
|
618
|
-
req.originalBody = ['GET', 'OPTIONS', 'HEAD'].includes(req.method)
|
|
619
|
-
? null
|
|
620
|
-
: await createStreamPromise(req, BYTES_LIMIT)
|
|
621
|
-
|
|
622
|
-
const edgeFunctionsProxyURL = await edgeFunctionsProxy(req, res)
|
|
623
|
-
|
|
624
|
-
if (edgeFunctionsProxyURL !== undefined) {
|
|
625
|
-
return proxy.web(req, res, { target: edgeFunctionsProxyURL })
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const functionMatch = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(req.url, req.method))
|
|
629
|
-
|
|
630
|
-
if (functionMatch) {
|
|
631
|
-
// Setting an internal header with the function name so that we don't
|
|
632
|
-
// have to match the URL again in the functions server.
|
|
633
|
-
/** @type {Record<string, string>} */
|
|
634
|
-
const headers = {}
|
|
635
|
-
|
|
636
|
-
if (functionMatch.func) {
|
|
637
|
-
headers[NFFunctionName] = functionMatch.func.name
|
|
617
|
+
// The request will be served by the framework server, which means we want to
|
|
618
|
+
// generate an ETag unless we're rendering an error page. The only way for
|
|
619
|
+
// us to know that is by looking at the status code
|
|
620
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'statusCode' implicitly has an 'an... Remove this comment to see the full error message
|
|
621
|
+
req[shouldGenerateETag] = ({ statusCode }) => statusCode >= 200 && statusCode < 300;
|
|
622
|
+
const ct = req.headers['content-type'] ? contentType.parse(req).type : '';
|
|
623
|
+
if (functionsServer &&
|
|
624
|
+
req.method === 'POST' &&
|
|
625
|
+
!isInternal(req.url) &&
|
|
626
|
+
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')) {
|
|
627
|
+
return proxy.web(req, res, { target: functionsServer });
|
|
638
628
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
headers[NFFunctionRoute] = functionMatch.route.pattern
|
|
629
|
+
if (isImageRequest(req)) {
|
|
630
|
+
return imageProxy(req, res);
|
|
642
631
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const addonUrl = getAddonUrl(addonsUrls, req)
|
|
648
|
-
if (addonUrl) {
|
|
649
|
-
return handleAddonUrl({ req, res, addonUrl })
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const match = await rewriter(req)
|
|
653
|
-
const options = {
|
|
654
|
-
match,
|
|
655
|
-
addonsUrls,
|
|
656
|
-
target: `http://${isIPv6(settings.frameworkHost) ? `[${settings.frameworkHost}]` : settings.frameworkHost}:${
|
|
657
|
-
settings.frameworkPort
|
|
658
|
-
}`,
|
|
659
|
-
publicFolder: settings.dist,
|
|
660
|
-
functionsServer,
|
|
661
|
-
functionsPort: settings.functionsPort,
|
|
662
|
-
jwtRolePath: settings.jwtRolePath,
|
|
663
|
-
framework: settings.framework,
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (match) {
|
|
667
|
-
// We don't want to generate an ETag for 3xx redirects.
|
|
668
|
-
req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400
|
|
669
|
-
|
|
670
|
-
return serveRedirect({ req, res, proxy, imageProxy, match, options, siteInfo, env, functionsRegistry })
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// The request will be served by the framework server, which means we want to
|
|
674
|
-
// generate an ETag unless we're rendering an error page. The only way for
|
|
675
|
-
// us to know that is by looking at the status code
|
|
676
|
-
req[shouldGenerateETag] = ({ statusCode }) => statusCode >= 200 && statusCode < 300
|
|
677
|
-
|
|
678
|
-
const ct = req.headers['content-type'] ? contentType.parse(req).type : ''
|
|
679
|
-
if (
|
|
680
|
-
functionsServer &&
|
|
681
|
-
req.method === 'POST' &&
|
|
682
|
-
!isInternal(req.url) &&
|
|
683
|
-
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
|
|
684
|
-
) {
|
|
685
|
-
return proxy.web(req, res, { target: functionsServer })
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (isImageRequest(req)) {
|
|
689
|
-
return imageProxy(req, res)
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
proxy.web(req, res, options)
|
|
693
|
-
}
|
|
694
|
-
|
|
632
|
+
proxy.web(req, res, options);
|
|
633
|
+
};
|
|
695
634
|
/**
|
|
696
635
|
* @param {Pick<import('./types.js').ServerSettings, "https" | "port">} settings
|
|
697
636
|
* @returns
|
|
698
637
|
*/
|
|
638
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'settings' implicitly has an 'any' type.
|
|
699
639
|
export const getProxyUrl = function (settings) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
eventQueue
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
640
|
+
const scheme = settings.https ? 'https' : 'http';
|
|
641
|
+
return `${scheme}://localhost:${settings.port}`;
|
|
642
|
+
};
|
|
643
|
+
export const startProxy = async function ({
|
|
644
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message
|
|
645
|
+
accountId,
|
|
646
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonsUrls' implicitly has an 'an... Remove this comment to see the full error message
|
|
647
|
+
addonsUrls,
|
|
648
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'blobsContext' implicitly has an '... Remove this comment to see the full error message
|
|
649
|
+
blobsContext,
|
|
650
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
651
|
+
config,
|
|
652
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'an... Remove this comment to see the full error message
|
|
653
|
+
configPath,
|
|
654
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'debug' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
655
|
+
debug,
|
|
656
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
657
|
+
env,
|
|
658
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message
|
|
659
|
+
functionsRegistry,
|
|
660
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'geoCountry' implicitly has an 'an... Remove this comment to see the full error message
|
|
661
|
+
geoCountry,
|
|
662
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'geolocationMode' implicitly has a... Remove this comment to see the full error message
|
|
663
|
+
geolocationMode,
|
|
664
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'getUpdatedConfig' implicitly has ... Remove this comment to see the full error message
|
|
665
|
+
getUpdatedConfig,
|
|
666
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'inspectSettings' implicitly has a... Remove this comment to see the full error message
|
|
667
|
+
inspectSettings,
|
|
668
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'offline' implicitly has an 'any' ... Remove this comment to see the full error message
|
|
669
|
+
offline,
|
|
670
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message
|
|
671
|
+
projectDir,
|
|
672
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'repositoryRoot' implicitly has an... Remove this comment to see the full error message
|
|
673
|
+
repositoryRoot,
|
|
674
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message
|
|
675
|
+
settings,
|
|
676
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message
|
|
677
|
+
siteInfo,
|
|
678
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'state' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
679
|
+
state, }) {
|
|
680
|
+
const secondaryServerPort = settings.https ? await getAvailablePort() : null;
|
|
681
|
+
const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null;
|
|
682
|
+
const edgeFunctionsProxy = await initializeEdgeFunctionsProxy({
|
|
683
|
+
blobsContext,
|
|
684
|
+
config,
|
|
685
|
+
configPath,
|
|
686
|
+
debug,
|
|
687
|
+
env,
|
|
688
|
+
geolocationMode,
|
|
689
|
+
geoCountry,
|
|
690
|
+
getUpdatedConfig,
|
|
691
|
+
inspectSettings,
|
|
692
|
+
mainPort: settings.port,
|
|
693
|
+
offline,
|
|
694
|
+
passthroughPort: secondaryServerPort || settings.port,
|
|
695
|
+
settings,
|
|
696
|
+
projectDir,
|
|
697
|
+
repositoryRoot,
|
|
698
|
+
siteInfo,
|
|
699
|
+
accountId,
|
|
700
|
+
state,
|
|
701
|
+
});
|
|
702
|
+
const imageProxy = await initializeImageProxy({
|
|
703
|
+
config,
|
|
704
|
+
});
|
|
705
|
+
const proxy = await initializeProxy({
|
|
706
|
+
env,
|
|
707
|
+
host: settings.frameworkHost,
|
|
708
|
+
port: settings.frameworkPort,
|
|
709
|
+
distDir: settings.dist,
|
|
710
|
+
projectDir,
|
|
711
|
+
configPath,
|
|
712
|
+
siteInfo,
|
|
713
|
+
imageProxy,
|
|
714
|
+
});
|
|
715
|
+
const rewriter = await createRewriter({
|
|
716
|
+
distDir: settings.dist,
|
|
717
|
+
projectDir,
|
|
718
|
+
jwtSecret: settings.jwtSecret,
|
|
719
|
+
jwtRoleClaim: settings.jwtRolePath,
|
|
720
|
+
configPath,
|
|
721
|
+
geoCountry,
|
|
722
|
+
});
|
|
723
|
+
const onRequestWithOptions = onRequest.bind(undefined, {
|
|
724
|
+
proxy,
|
|
725
|
+
rewriter,
|
|
726
|
+
settings,
|
|
727
|
+
addonsUrls,
|
|
728
|
+
functionsRegistry,
|
|
729
|
+
functionsServer,
|
|
730
|
+
edgeFunctionsProxy,
|
|
731
|
+
imageProxy,
|
|
732
|
+
siteInfo,
|
|
733
|
+
env,
|
|
734
|
+
});
|
|
735
|
+
const primaryServer = settings.https
|
|
736
|
+
? https.createServer({ cert: settings.https.cert, key: settings.https.key }, onRequestWithOptions)
|
|
737
|
+
: http.createServer(onRequestWithOptions);
|
|
738
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
739
|
+
const onUpgrade = function onUpgrade(req, socket, head) {
|
|
740
|
+
proxy.ws(req, socket, head);
|
|
741
|
+
};
|
|
742
|
+
primaryServer.on('upgrade', onUpgrade);
|
|
743
|
+
primaryServer.listen({ port: settings.port });
|
|
744
|
+
const eventQueue = [once(primaryServer, 'listening')];
|
|
745
|
+
// If we're running the main server on HTTPS, we need to start a secondary
|
|
746
|
+
// server on HTTP for receiving passthrough requests from edge functions.
|
|
747
|
+
// This lets us run the Deno server on HTTP and avoid the complications of
|
|
748
|
+
// Deno talking to Node on HTTPS with potentially untrusted certificates.
|
|
749
|
+
if (secondaryServerPort) {
|
|
750
|
+
const secondaryServer = http.createServer(onRequestWithOptions);
|
|
751
|
+
secondaryServer.on('upgrade', onUpgrade);
|
|
752
|
+
secondaryServer.listen({ port: secondaryServerPort });
|
|
753
|
+
eventQueue.push(once(secondaryServer, 'listening'));
|
|
754
|
+
}
|
|
755
|
+
await Promise.all(eventQueue);
|
|
756
|
+
return getProxyUrl(settings);
|
|
757
|
+
};
|
|
758
|
+
const BYTES_LIMIT = 30;
|