11import type { NuxtOptions } from '@nuxt/schema'
22import type { ParsedArgs } from 'citty'
3+ import type { ProxyTargetDetailed } from 'http-proxy-3/dist/lib/http-proxy'
34import type { HTTPSOptions , ListenOptions } from 'listhen'
45import type { ChildProcess } from 'node:child_process'
6+ import type { IncomingMessage , ServerResponse } from 'node:http'
7+ import type { TLSSocket } from 'node:tls'
58import type { NuxtDevContext , NuxtDevIPCMessage } from '../dev/utils'
69
710import { fork } from 'node:child_process'
811import process from 'node:process'
912
1013import { defineCommand } from 'citty'
1114import { isSocketSupported } from 'get-port-please'
15+ import { createProxyServer } from 'http-proxy-3'
1216import { listen } from 'listhen'
1317import { getArgs as getListhenArgs , parseArgs as parseListhenArgs } from 'listhen/cli'
1418import { resolve } from 'pathe'
@@ -17,10 +21,8 @@ import { isBun, isDeno, isTest } from 'std-env'
1721
1822import { initialize } from '../dev'
1923import { renderError } from '../dev/error'
20- import { createFetchHandler } from '../dev/fetch'
2124import { isSocketURL , parseSocketURL } from '../dev/socket'
2225import { resolveLoadingTemplate } from '../dev/utils'
23- import { connectToChildNetwork , connectToChildSocket } from '../dev/websocket'
2426import { showVersionsFromConfig } from '../utils/banner'
2527import { overrideEnv } from '../utils/env'
2628import { loadKit } from '../utils/kit'
@@ -131,14 +133,14 @@ const command = defineCommand({
131133 }
132134 }
133135
134- // Start listener
135- const devHandler = await createDevHandler ( cwd , nuxtOptions , listenOptions )
136+ // Start proxy Listener
137+ const devProxy = await createDevProxy ( cwd , nuxtOptions , listenOptions )
136138
137139 const nuxtSocketEnv = process . env . NUXT_SOCKET ? process . env . NUXT_SOCKET === '1' : undefined
138140
139141 const useSocket = nuxtSocketEnv ?? ( nuxtOptions . _majorVersion === 4 && await isSocketSupported ( ) )
140142
141- const urls = await devHandler . listener . getURLs ( )
143+ const urls = await devProxy . listener . getURLs ( )
142144 // run initially in in no-fork mode
143145 const { onRestart, onReady, close } = await initialize ( {
144146 cwd,
@@ -147,16 +149,16 @@ const command = defineCommand({
147149 public : listenOptions . public ,
148150 publicURLs : urls . map ( r => r . url ) ,
149151 proxy : {
150- url : devHandler . listener . url ,
152+ url : devProxy . listener . url ,
151153 urls,
152- https : devHandler . listener . https ,
153- addr : devHandler . listener . address ,
154+ https : devProxy . listener . https ,
155+ addr : devProxy . listener . address ,
154156 } ,
155157 // if running with nuxt v4 or `NUXT_SOCKET=1`, we use the socket listener
156158 // otherwise pass 'true' to listen on a random port instead
157159 } , { } , useSocket ? undefined : true )
158160
159- onReady ( address => devHandler . setAddress ( address ) )
161+ onReady ( address => devProxy . setAddress ( address ) )
160162
161163 // ... then fall back to pre-warmed fork if a hard restart is required
162164 const fork = startSubprocess ( cwd , ctx . args , ctx . rawArgs , listenOptions )
@@ -165,16 +167,16 @@ const command = defineCommand({
165167 fork ,
166168 devServer . close ( ) . catch ( ( ) => { } ) ,
167169 ] )
168- await subprocess . initialize ( devHandler , useSocket )
170+ await subprocess . initialize ( devProxy , useSocket )
169171 } )
170172
171173 return {
172- listener : devHandler . listener ,
174+ listener : devProxy . listener ,
173175 async close ( ) {
174176 await close ( )
175177 const subprocess = await fork
176178 subprocess . kill ( 0 )
177- await devHandler . listener . close ( )
179+ await devProxy . listener . close ( )
178180 } ,
179181 }
180182 } ,
@@ -189,53 +191,42 @@ type ArgsT = Exclude<
189191 undefined | ( ( ...args : unknown [ ] ) => unknown )
190192>
191193
192- type DevHandler = Awaited < ReturnType < typeof createDevHandler > >
194+ type DevProxy = Awaited < ReturnType < typeof createDevProxy > >
193195
194- async function createDevHandler ( cwd : string , nuxtOptions : NuxtOptions , listenOptions : Partial < ListenOptions > ) {
196+ async function createDevProxy ( cwd : string , nuxtOptions : NuxtOptions , listenOptions : Partial < ListenOptions > ) {
195197 let loadingMessage = 'Nuxt dev server is starting...'
196198 let error : Error | undefined
197199 let address : string | undefined
198200
199201 let loadingTemplate = nuxtOptions . devServer . loadingTemplate
200202
201- // Create fetch-based handler
202- const fetchHandler = createFetchHandler (
203- ( ) => {
204- if ( ! address ) {
205- return undefined
206- }
207-
208- // Convert address string to DevAddress format
209- if ( isSocketURL ( address ) ) {
210- const { socketPath } = parseSocketURL ( address )
211- return { socketPath }
212- }
203+ const proxy = createProxyServer ( { } )
213204
214- // Parse network address
215- try {
216- const url = new URL ( address )
217- return {
218- host : url . hostname ,
219- port : Number . parseInt ( url . port ) || 80 ,
220- }
205+ proxy . on ( 'proxyReq' , ( proxyReq , req ) => {
206+ if ( ! proxyReq . hasHeader ( 'x-forwarded-for' ) ) {
207+ const address = req . socket . remoteAddress
208+ if ( address ) {
209+ proxyReq . appendHeader ( 'x-forwarded-for' , address )
221210 }
222- catch {
223- return undefined
224- }
225- } ,
226- // Error handler
227- async ( req , res ) => {
228- renderError ( req , res , error )
229- } ,
230- // Loading handler
231- async ( req , res ) => {
232- if ( res . headersSent ) {
233- if ( ! res . writableEnded ) {
234- res . end ( )
235- }
236- return
211+ }
212+ if ( ! proxyReq . hasHeader ( 'x-forwarded-port' ) ) {
213+ const localPort = req ?. socket ?. localPort
214+ if ( localPort ) {
215+ proxyReq . setHeader ( 'x-forwarded-port' , localPort )
237216 }
217+ }
218+ if ( ! proxyReq . hasHeader ( 'x-forwarded-Proto' ) ) {
219+ const encrypted = ( req ?. connection as TLSSocket ) ?. encrypted
220+ proxyReq . setHeader ( 'x-forwarded-proto' , encrypted ? 'https' : 'http' )
221+ }
222+ } )
238223
224+ const listener = await listen ( ( req : IncomingMessage , res : ServerResponse ) => {
225+ if ( error ) {
226+ renderError ( req , res , error )
227+ return
228+ }
229+ if ( ! address ) {
239230 res . statusCode = 503
240231 res . setHeader ( 'Content-Type' , 'text/html' )
241232 res . setHeader ( 'Cache-Control' , 'no-store' )
@@ -250,10 +241,10 @@ async function createDevHandler(cwd: string, nuxtOptions: NuxtOptions, listenOpt
250241 res . end ( loadingTemplate ( { loading : loadingMessage } ) )
251242 }
252243 return resolveLoadingMessage ( )
253- } ,
254- )
255-
256- const listener = await listen ( fetchHandler , listenOptions )
244+ }
245+ const target = isSocketURL ( address ) ? parseSocketURL ( address ) as ProxyTargetDetailed : address
246+ proxy . web ( req , res , { target } )
247+ } , listenOptions )
257248
258249 listener . server . on ( 'upgrade' , ( req , socket , head ) => {
259250 if ( ! address ) {
@@ -262,23 +253,8 @@ async function createDevHandler(cwd: string, nuxtOptions: NuxtOptions, listenOpt
262253 }
263254 return
264255 }
265- if ( isSocketURL ( address ) ) {
266- const { socketPath } = parseSocketURL ( address )
267- connectToChildSocket ( socketPath , req , socket , head )
268- }
269- else {
270- try {
271- const url = new URL ( address )
272- const host = url . hostname
273- const port = Number . parseInt ( url . port ) || 80
274- connectToChildNetwork ( host , port , req , socket , head )
275- }
276- catch {
277- if ( ! socket . destroyed ) {
278- socket . end ( )
279- }
280- }
281- }
256+ const target = isSocketURL ( address ) ? parseSocketURL ( address ) as ProxyTargetDetailed : address
257+ return proxy . ws ( req , socket , head , { target, xfwd : true } )
282258 } )
283259
284260 return {
@@ -300,7 +276,7 @@ async function createDevHandler(cwd: string, nuxtOptions: NuxtOptions, listenOpt
300276
301277async function startSubprocess ( cwd : string , args : { logLevel : string , clear : boolean , dotenv : string , envName : string , extends ?: string } , rawArgs : string [ ] , listenOptions : Partial < ListenOptions > ) {
302278 let childProc : ChildProcess | undefined
303- let devHandler : DevHandler
279+ let devProxy : DevProxy
304280 let ready : Promise < void > | undefined
305281 const kill = ( signal : NodeJS . Signals | number ) => {
306282 if ( childProc ) {
@@ -309,9 +285,9 @@ async function startSubprocess(cwd: string, args: { logLevel: string, clear: boo
309285 }
310286 }
311287
312- async function initialize ( handler : DevHandler , socket : boolean ) {
313- devHandler = handler
314- const urls = await devHandler . listener . getURLs ( )
288+ async function initialize ( proxy : DevProxy , socket : boolean ) {
289+ devProxy = proxy
290+ const urls = await devProxy . listener . getURLs ( )
315291 await ready
316292 childProc ! . send ( {
317293 type : 'nuxt:internal:dev:context' ,
@@ -323,16 +299,16 @@ async function startSubprocess(cwd: string, args: { logLevel: string, clear: boo
323299 public : listenOptions . public ,
324300 publicURLs : urls . map ( r => r . url ) ,
325301 proxy : {
326- url : devHandler . listener . url ,
302+ url : devProxy . listener . url ,
327303 urls,
328- https : devHandler . listener . https ,
304+ https : devProxy . listener . https ,
329305 } ,
330306 } satisfies NuxtDevContext ,
331307 } )
332308 }
333309
334310 async function restart ( ) {
335- devHandler ?. clearError ( )
311+ devProxy ?. clearError ( )
336312 if ( ! globalThis . __nuxt_cli__ ) {
337313 return
338314 }
@@ -367,19 +343,19 @@ async function startSubprocess(cwd: string, args: { logLevel: string, clear: boo
367343 resolve ( )
368344 }
369345 else if ( message . type === 'nuxt:internal:dev:ready' ) {
370- devHandler . setAddress ( message . address )
346+ devProxy . setAddress ( message . address )
371347 if ( startTime ) {
372348 logger . debug ( `Dev server ready for connections in ${ Date . now ( ) - startTime } ms` )
373349 }
374350 }
375351 else if ( message . type === 'nuxt:internal:dev:loading' ) {
376- devHandler . setAddress ( undefined )
377- devHandler . setLoadingMessage ( message . message )
378- devHandler . clearError ( )
352+ devProxy . setAddress ( undefined )
353+ devProxy . setLoadingMessage ( message . message )
354+ devProxy . clearError ( )
379355 }
380356 else if ( message . type === 'nuxt:internal:dev:loading:error' ) {
381- devHandler . setAddress ( undefined )
382- devHandler . setError ( message . error )
357+ devProxy . setAddress ( undefined )
358+ devProxy . setError ( message . error )
383359 }
384360 else if ( message . type === 'nuxt:internal:dev:restart' ) {
385361 restart ( )
0 commit comments