@@ -4,10 +4,12 @@ import { readFile } from 'fs/promises'
44import http , { ServerResponse } from 'http'
55import https from 'https'
66import { isIPv6 } from 'net'
7+ import { Socket } from 'node:net'
78import { Readable } from 'node:stream'
89import path from 'path'
910import process from 'process'
1011import { Duplex } from 'stream'
12+ import url from 'url'
1113import util from 'util'
1214import zlib from 'zlib'
1315
@@ -20,9 +22,9 @@ import httpProxy from 'http-proxy'
2022import { createProxyMiddleware } from 'http-proxy-middleware'
2123import { jwtDecode } from 'jwt-decode'
2224import { locatePath } from 'locate-path'
25+ import throttle from 'lodash/throttle.js'
2326import { Match } from 'netlify-redirector'
2427import pFilter from 'p-filter'
25- import throttle from 'lodash/throttle.js'
2628
2729import { BaseCommand } from '../commands/index.js'
2830import { $TSFixMe , NetlifyOptions } from '../commands/types.js'
@@ -515,18 +517,41 @@ const initializeProxy = async function ({
515517 }
516518 } )
517519
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+ }
523547
524548 const message = isEdgeFunctionsRequest ( req )
525549 ? 'There was an error with an Edge Function. Please check the terminal for more details.'
526550 : 'Could not proxy request.'
527551
528552 res . end ( message )
529553 } )
554+
530555 proxy . on ( 'proxyReq' , ( proxyReq , req ) => {
531556 const requestID = generateRequestID ( )
532557
@@ -548,6 +573,7 @@ const initializeProxy = async function ({
548573 proxyReq . write ( req . originalBody )
549574 }
550575 } )
576+
551577 proxy . on ( 'proxyRes' , ( proxyRes , req , res ) => {
552578 res . setHeader ( 'server' , 'Netlify' )
553579
@@ -557,6 +583,23 @@ const initializeProxy = async function ({
557583 res . setHeader ( NFRequestID , requestID )
558584 }
559585
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+
560603 if ( proxyRes . statusCode === 404 || proxyRes . statusCode === 403 ) {
561604 // If a request for `/path` has failed, we'll a few variations like
562605 // `/path/index.html` to mimic the CDN behavior.
@@ -571,8 +614,7 @@ const initializeProxy = async function ({
571614 // The request has failed but we might still have a matching redirect
572615 // rule (without `force`) that should kick in. This is how we mimic the
573616 // 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 ) {
576618 return serveRedirect ( {
577619 // We don't want to match functions at this point because any redirects
578620 // to functions will have already been processed, so we don't supply a
@@ -582,18 +624,15 @@ const initializeProxy = async function ({
582624 res,
583625 proxy : handlers ,
584626 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,
589629 siteInfo,
590630 env,
591631 } )
592632 }
593633 }
594634
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 ) {
597636 req . url = proxyRes . headers . location
598637 return serveRedirect ( {
599638 // We don't want to match functions at this point because any redirects
@@ -605,8 +644,7 @@ const initializeProxy = async function ({
605644 proxy : handlers ,
606645 imageProxy,
607646 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,
610648 siteInfo,
611649 env,
612650 } )
@@ -634,8 +672,7 @@ const initializeProxy = async function ({
634672 // @ts -expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
635673 res . setHeader ( key , val )
636674 } )
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 )
639676
640677 proxyRes . on ( 'data' , function onData ( data ) {
641678 res . write ( data )
@@ -656,8 +693,7 @@ const initializeProxy = async function ({
656693 // @ts -expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message
657694 let responseBody = Buffer . concat ( responseData )
658695
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
661697
662698 // `req[shouldGenerateETag]` may contain a function that determines
663699 // whether the response should have an ETag header.
@@ -805,11 +841,16 @@ const onRequest = async (
805841 target : `http://${
806842 settings . frameworkHost && isIPv6 ( settings . frameworkHost ) ? `[${ settings . frameworkHost } ]` : settings . frameworkHost
807843 } :${ settings . frameworkPort } `,
844+ detectTarget : settings . detectFrameworkHost ,
845+ targetHostname : settings . frameworkHost ,
808846 publicFolder : settings . dist ,
809847 functionsServer,
810848 functionsPort : settings . functionsPort ,
811849 jwtRolePath : settings . jwtRolePath ,
812850 framework : settings . framework ,
851+ changeSettings ( newSettings : Partial < ServerSettings > ) {
852+ Object . assign ( settings , newSettings )
853+ } ,
813854 }
814855
815856 if ( match ) {
0 commit comments