@@ -4,10 +4,12 @@ import { readFile } from 'fs/promises'
4
4
import http , { ServerResponse } from 'http'
5
5
import https from 'https'
6
6
import { isIPv6 } from 'net'
7
+ import { Socket } from 'node:net'
7
8
import { Readable } from 'node:stream'
8
9
import path from 'path'
9
10
import process from 'process'
10
11
import { Duplex } from 'stream'
12
+ import url from 'url'
11
13
import util from 'util'
12
14
import zlib from 'zlib'
13
15
@@ -20,9 +22,9 @@ import httpProxy from 'http-proxy'
20
22
import { createProxyMiddleware } from 'http-proxy-middleware'
21
23
import { jwtDecode } from 'jwt-decode'
22
24
import { locatePath } from 'locate-path'
25
+ import throttle from 'lodash/throttle.js'
23
26
import { Match } from 'netlify-redirector'
24
27
import pFilter from 'p-filter'
25
- import throttle from 'lodash/throttle.js'
26
28
27
29
import { BaseCommand } from '../commands/index.js'
28
30
import { $TSFixMe , NetlifyOptions } from '../commands/types.js'
@@ -515,18 +517,41 @@ const initializeProxy = async function ({
515
517
}
516
518
} )
517
519
518
- proxy . on ( 'error' , ( _ , req , res ) => {
519
- // @ts -expect-error TS(2339) FIXME: Property 'writeHead' does not exist on type 'Socke... Remove this comment to see the full error message
520
- res . writeHead ( 500 , {
521
- 'Content-Type' : 'text/plain' ,
522
- } )
520
+ proxy . on ( 'error' , ( err , req , res , proxyUrl ) => {
521
+ // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
522
+ const options = req . proxyOptions
523
+
524
+ const isConRefused = 'code' in err && err . code === 'ECONNREFUSED'
525
+ if ( options ?. detectTarget && ! ( res instanceof Socket ) && isConRefused && proxyUrl ) {
526
+ // got econnrefused while detectTarget set to true -> try to switch between current ipVer and other (4 to 6 and vice versa)
527
+
528
+ // proxyUrl is parsed in http-proxy using url, parsing the same here. Difference between it and
529
+ // URL that hostname not includes [] symbols when using url.parse
530
+ // eslint-disable-next-line n/no-deprecated-api
531
+ const targetUrl = typeof proxyUrl === 'string' ? url . parse ( proxyUrl ) : proxyUrl
532
+ const isCurrentHost = targetUrl . hostname === options . targetHostname
533
+ if ( targetUrl . hostname && isCurrentHost ) {
534
+ const newHost = isIPv6 ( targetUrl . hostname ) ? '127.0.0.1' : '::1'
535
+ options . target = `http://${ isIPv6 ( newHost ) ? `[${ newHost } ]` : newHost } :${ targetUrl . port } `
536
+ options . targetHostname = newHost
537
+ options . isChangingTarget = true
538
+ return proxy . web ( req , res , options )
539
+ }
540
+ }
541
+
542
+ if ( res instanceof http . ServerResponse ) {
543
+ res . writeHead ( 500 , {
544
+ 'Content-Type' : 'text/plain' ,
545
+ } )
546
+ }
523
547
524
548
const message = isEdgeFunctionsRequest ( req )
525
549
? 'There was an error with an Edge Function. Please check the terminal for more details.'
526
550
: 'Could not proxy request.'
527
551
528
552
res . end ( message )
529
553
} )
554
+
530
555
proxy . on ( 'proxyReq' , ( proxyReq , req ) => {
531
556
const requestID = generateRequestID ( )
532
557
@@ -548,6 +573,7 @@ const initializeProxy = async function ({
548
573
proxyReq . write ( req . originalBody )
549
574
}
550
575
} )
576
+
551
577
proxy . on ( 'proxyRes' , ( proxyRes , req , res ) => {
552
578
res . setHeader ( 'server' , 'Netlify' )
553
579
@@ -557,6 +583,23 @@ const initializeProxy = async function ({
557
583
res . setHeader ( NFRequestID , requestID )
558
584
}
559
585
586
+ // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
587
+ const options = req . proxyOptions
588
+
589
+ if ( options . isChangingTarget ) {
590
+ // got a response after switching the ipVer for host (and its not an error since we will be in on('error') handler) - let's remember this host now
591
+
592
+ // options are not exported in ts for the proxy:
593
+ // @ts -expect-error TS(2339) FIXME: Property 'options' does not exist on type 'In...
594
+ proxy . options . target . host = options . targetHostname
595
+
596
+ options . changeSettings ?.( {
597
+ frameworkHost : options . targetHostname ,
598
+ detectFrameworkHost : false ,
599
+ } )
600
+ console . log ( `${ NETLIFYDEVLOG } Switched host to ${ options . targetHostname } ` )
601
+ }
602
+
560
603
if ( proxyRes . statusCode === 404 || proxyRes . statusCode === 403 ) {
561
604
// If a request for `/path` has failed, we'll a few variations like
562
605
// `/path/index.html` to mimic the CDN behavior.
@@ -571,8 +614,7 @@ const initializeProxy = async function ({
571
614
// The request has failed but we might still have a matching redirect
572
615
// rule (without `force`) that should kick in. This is how we mimic the
573
616
// file shadowing behavior from the CDN.
574
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
575
- if ( req . proxyOptions && req . proxyOptions . match ) {
617
+ if ( options && options . match ) {
576
618
return serveRedirect ( {
577
619
// We don't want to match functions at this point because any redirects
578
620
// to functions will have already been processed, so we don't supply a
@@ -582,18 +624,15 @@ const initializeProxy = async function ({
582
624
res,
583
625
proxy : handlers ,
584
626
imageProxy,
585
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
586
- match : req . proxyOptions . match ,
587
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
588
- options : req . proxyOptions ,
627
+ match : options . match ,
628
+ options,
589
629
siteInfo,
590
630
env,
591
631
} )
592
632
}
593
633
}
594
634
595
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
596
- if ( req . proxyOptions . staticFile && isRedirect ( { status : proxyRes . statusCode } ) && proxyRes . headers . location ) {
635
+ if ( options . staticFile && isRedirect ( { status : proxyRes . statusCode } ) && proxyRes . headers . location ) {
597
636
req . url = proxyRes . headers . location
598
637
return serveRedirect ( {
599
638
// We don't want to match functions at this point because any redirects
@@ -605,8 +644,7 @@ const initializeProxy = async function ({
605
644
proxy : handlers ,
606
645
imageProxy,
607
646
match : null ,
608
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
609
- options : req . proxyOptions ,
647
+ options,
610
648
siteInfo,
611
649
env,
612
650
} )
@@ -634,8 +672,7 @@ const initializeProxy = async function ({
634
672
// @ts -expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
635
673
res . setHeader ( key , val )
636
674
} )
637
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
638
- res . writeHead ( req . proxyOptions . status || proxyRes . statusCode , proxyRes . headers )
675
+ res . writeHead ( options . status || proxyRes . statusCode , proxyRes . headers )
639
676
640
677
proxyRes . on ( 'data' , function onData ( data ) {
641
678
res . write ( data )
@@ -656,8 +693,7 @@ const initializeProxy = async function ({
656
693
// @ts -expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message
657
694
let responseBody = Buffer . concat ( responseData )
658
695
659
- // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
660
- let responseStatus = req . proxyOptions . status || proxyRes . statusCode
696
+ let responseStatus = options . status || proxyRes . statusCode
661
697
662
698
// `req[shouldGenerateETag]` may contain a function that determines
663
699
// whether the response should have an ETag header.
@@ -805,11 +841,16 @@ const onRequest = async (
805
841
target : `http://${
806
842
settings . frameworkHost && isIPv6 ( settings . frameworkHost ) ? `[${ settings . frameworkHost } ]` : settings . frameworkHost
807
843
} :${ settings . frameworkPort } `,
844
+ detectTarget : settings . detectFrameworkHost ,
845
+ targetHostname : settings . frameworkHost ,
808
846
publicFolder : settings . dist ,
809
847
functionsServer,
810
848
functionsPort : settings . functionsPort ,
811
849
jwtRolePath : settings . jwtRolePath ,
812
850
framework : settings . framework ,
851
+ changeSettings ( newSettings : Partial < ServerSettings > ) {
852
+ Object . assign ( settings , newSettings )
853
+ } ,
813
854
}
814
855
815
856
if ( match ) {
0 commit comments