@@ -31,7 +31,7 @@ import renderErrorTemplate from '../lib/render-error-template.mjs'
31
31
32
32
import { NETLIFYDEVLOG , NETLIFYDEVWARN , log , chalk } from './command-helpers.mjs'
33
33
import createStreamPromise from './create-stream-promise.mjs'
34
- import { headersForPath , parseHeaders , NFRequestID } from './headers.mjs'
34
+ import { headersForPath , parseHeaders , NFFunctionName , NFRequestID } from './headers.mjs'
35
35
import { generateRequestID } from './request-id.mjs'
36
36
import { createRewriter , onChanges } from './rules-proxy.mjs'
37
37
import { signRedirect } from './sign-redirect.mjs'
@@ -181,7 +181,7 @@ const alternativePathsFor = function (url) {
181
181
return paths
182
182
}
183
183
184
- const serveRedirect = async function ( { env, match, options, proxy, req, res, siteInfo } ) {
184
+ const serveRedirect = async function ( { env, functionsRegistry , match, options, proxy, req, res, siteInfo } ) {
185
185
if ( ! match ) return proxy . web ( req , res , options )
186
186
187
187
options = options || req . proxyOptions || { }
@@ -214,6 +214,7 @@ const serveRedirect = async function ({ env, match, options, proxy, req, res, si
214
214
if ( isFunction ( options . functionsPort , req . url ) ) {
215
215
return proxy . web ( req , res , { target : options . functionsServer } )
216
216
}
217
+
217
218
const urlForAddons = getAddonUrl ( options . addonsUrls , req )
218
219
if ( urlForAddons ) {
219
220
return handleAddonUrl ( { req, res, addonUrl : urlForAddons } )
@@ -327,22 +328,28 @@ const serveRedirect = async function ({ env, match, options, proxy, req, res, si
327
328
return proxy . web ( req , res , { target : options . functionsServer } )
328
329
}
329
330
331
+ const functionWithCustomRoute = functionsRegistry && ( await functionsRegistry . getFunctionForURLPath ( destURL ) )
330
332
const destStaticFile = await getStatic ( dest . pathname , options . publicFolder )
331
333
let statusValue
332
- if ( match . force || ( ! staticFile && ( ( ! options . framework && destStaticFile ) || isInternal ( destURL ) ) ) ) {
334
+ if (
335
+ match . force ||
336
+ ( ! staticFile && ( ( ! options . framework && destStaticFile ) || isInternal ( destURL ) || functionWithCustomRoute ) )
337
+ ) {
333
338
req . url = destStaticFile ? destStaticFile + dest . search : destURL
334
339
const { status } = match
335
340
statusValue = status
336
341
console . log ( `${ NETLIFYDEVLOG } Rewrote URL to` , req . url )
337
342
}
338
343
339
- if ( isFunction ( options . functionsPort , req . url ) ) {
344
+ if ( isFunction ( options . functionsPort , req . url ) || functionWithCustomRoute ) {
345
+ const functionHeaders = functionWithCustomRoute ? { [ NFFunctionName ] : functionWithCustomRoute . name } : { }
340
346
const url = reqToURL ( req , originalURL )
341
347
req . headers [ 'x-netlify-original-pathname' ] = url . pathname
342
348
req . headers [ 'x-netlify-original-search' ] = url . search
343
349
344
- return proxy . web ( req , res , { target : options . functionsServer } )
350
+ return proxy . web ( req , res , { headers : functionHeaders , target : options . functionsServer } )
345
351
}
352
+
346
353
const addonUrl = getAddonUrl ( options . addonsUrls , req )
347
354
if ( addonUrl ) {
348
355
return handleAddonUrl ( { req, res, addonUrl } )
@@ -434,12 +441,22 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
434
441
}
435
442
436
443
if ( proxyRes . statusCode === 404 || proxyRes . statusCode === 403 ) {
444
+ // If a request for `/path` has failed, we'll a few variations like
445
+ // `/path/index.html` to mimic the CDN behavior.
437
446
if ( req . alternativePaths && req . alternativePaths . length !== 0 ) {
438
447
req . url = req . alternativePaths . shift ( )
439
448
return proxy . web ( req , res , req . proxyOptions )
440
449
}
450
+
451
+ // The request has failed but we might still have a matching redirect
452
+ // rule (without `force`) that should kick in. This is how we mimic the
453
+ // file shadowing behavior from the CDN.
441
454
if ( req . proxyOptions && req . proxyOptions . match ) {
442
455
return serveRedirect ( {
456
+ // We don't want to match functions at this point because any redirects
457
+ // to functions will have already been processed, so we don't supply a
458
+ // functions registry to `serveRedirect`.
459
+ functionsRegistry : null ,
443
460
req,
444
461
res,
445
462
proxy : handlers ,
@@ -453,7 +470,19 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
453
470
454
471
if ( req . proxyOptions . staticFile && isRedirect ( { status : proxyRes . statusCode } ) && proxyRes . headers . location ) {
455
472
req . url = proxyRes . headers . location
456
- return serveRedirect ( { req, res, proxy : handlers , match : null , options : req . proxyOptions , siteInfo, env } )
473
+ return serveRedirect ( {
474
+ // We don't want to match functions at this point because any redirects
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
+ match : null ,
482
+ options : req . proxyOptions ,
483
+ siteInfo,
484
+ env,
485
+ } )
457
486
}
458
487
459
488
const responseData = [ ]
@@ -551,7 +580,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
551
580
}
552
581
553
582
const onRequest = async (
554
- { addonsUrls, edgeFunctionsProxy, env, functionsServer, proxy, rewriter, settings, siteInfo } ,
583
+ { addonsUrls, edgeFunctionsProxy, env, functionsRegistry , functionsServer, proxy, rewriter, settings, siteInfo } ,
555
584
req ,
556
585
res ,
557
586
) => {
@@ -565,9 +594,22 @@ const onRequest = async (
565
594
return proxy . web ( req , res , { target : edgeFunctionsProxyURL } )
566
595
}
567
596
597
+ // Does the request match a function on the fixed URL path?
568
598
if ( isFunction ( settings . functionsPort , req . url ) ) {
569
599
return proxy . web ( req , res , { target : functionsServer } )
570
600
}
601
+
602
+ // Does the request match a function on a custom URL path?
603
+ const functionMatch = functionsRegistry ? await functionsRegistry . getFunctionForURLPath ( req . url ) : null
604
+
605
+ if ( functionMatch ) {
606
+ // Setting an internal header with the function name so that we don't
607
+ // have to match the URL again in the functions server.
608
+ const headers = { [ NFFunctionName ] : functionMatch . name }
609
+
610
+ return proxy . web ( req , res , { headers, target : functionsServer } )
611
+ }
612
+
571
613
const addonUrl = getAddonUrl ( addonsUrls , req )
572
614
if ( addonUrl ) {
573
615
return handleAddonUrl ( { req, res, addonUrl } )
@@ -591,7 +633,7 @@ const onRequest = async (
591
633
// We don't want to generate an ETag for 3xx redirects.
592
634
req [ shouldGenerateETag ] = ( { statusCode } ) => statusCode < 300 || statusCode >= 400
593
635
594
- return serveRedirect ( { req, res, proxy, match, options, siteInfo, env } )
636
+ return serveRedirect ( { req, res, proxy, match, options, siteInfo, env, functionsRegistry } )
595
637
}
596
638
597
639
// The request will be served by the framework server, which means we want to
@@ -628,6 +670,7 @@ export const startProxy = async function ({
628
670
configPath,
629
671
debug,
630
672
env,
673
+ functionsRegistry,
631
674
geoCountry,
632
675
geolocationMode,
633
676
getUpdatedConfig,
@@ -681,6 +724,7 @@ export const startProxy = async function ({
681
724
rewriter,
682
725
settings,
683
726
addonsUrls,
727
+ functionsRegistry,
684
728
functionsServer,
685
729
edgeFunctionsProxy,
686
730
siteInfo,
0 commit comments