@@ -198,6 +198,196 @@ describe('request utils', () => {
198198 data : { xx : 'a' , yy : 'z' } ,
199199 } ) ;
200200 } ) ;
201+
202+ describe ( 'x-forwarded headers support' , ( ) => {
203+ it ( 'should prioritize x-forwarded-proto header over explicit protocol parameter' , ( ) => {
204+ const actual = httpRequestToRequestData ( {
205+ url : '/test' ,
206+ headers : {
207+ host : 'example.com' ,
208+ 'x-forwarded-proto' : 'https' ,
209+ } ,
210+ protocol : 'http' ,
211+ } ) ;
212+
213+ expect ( actual ) . toEqual ( {
214+ url : 'https://example.com/test' ,
215+ headers : {
216+ host : 'example.com' ,
217+ 'x-forwarded-proto' : 'https' ,
218+ } ,
219+ } ) ;
220+ } ) ;
221+
222+ it ( 'should prioritize x-forwarded-proto header over socket encryption detection' , ( ) => {
223+ const actual = httpRequestToRequestData ( {
224+ url : '/test' ,
225+ headers : {
226+ host : 'example.com' ,
227+ 'x-forwarded-proto' : 'https' ,
228+ } ,
229+ socket : {
230+ encrypted : false ,
231+ } ,
232+ } ) ;
233+
234+ expect ( actual ) . toEqual ( {
235+ url : 'https://example.com/test' ,
236+ headers : {
237+ host : 'example.com' ,
238+ 'x-forwarded-proto' : 'https' ,
239+ } ,
240+ } ) ;
241+ } ) ;
242+
243+ it ( 'should prioritize x-forwarded-host header over standard host header' , ( ) => {
244+ const actual = httpRequestToRequestData ( {
245+ url : '/test' ,
246+ headers : {
247+ host : 'localhost:3000' ,
248+ 'x-forwarded-host' : 'example.com' ,
249+ 'x-forwarded-proto' : 'https' ,
250+ } ,
251+ } ) ;
252+
253+ expect ( actual ) . toEqual ( {
254+ url : 'https://example.com/test' ,
255+ headers : {
256+ host : 'localhost:3000' ,
257+ 'x-forwarded-host' : 'example.com' ,
258+ 'x-forwarded-proto' : 'https' ,
259+ } ,
260+ } ) ;
261+ } ) ;
262+
263+ it ( 'should construct URL correctly when both x-forwarded-proto and x-forwarded-host are present' , ( ) => {
264+ const actual = httpRequestToRequestData ( {
265+ method : 'POST' ,
266+ url : '/api/test?param=value' ,
267+ headers : {
268+ host : 'localhost:3000' ,
269+ 'x-forwarded-host' : 'api.example.com' ,
270+ 'x-forwarded-proto' : 'https' ,
271+ 'content-type' : 'application/json' ,
272+ } ,
273+ protocol : 'http' ,
274+ } ) ;
275+
276+ expect ( actual ) . toEqual ( {
277+ method : 'POST' ,
278+ url : 'https://api.example.com/api/test?param=value' ,
279+ query_string : 'param=value' ,
280+ headers : {
281+ host : 'localhost:3000' ,
282+ 'x-forwarded-host' : 'api.example.com' ,
283+ 'x-forwarded-proto' : 'https' ,
284+ 'content-type' : 'application/json' ,
285+ } ,
286+ } ) ;
287+ } ) ;
288+
289+ it ( 'should fall back to standard headers when x-forwarded headers are not present' , ( ) => {
290+ const actual = httpRequestToRequestData ( {
291+ url : '/test' ,
292+ headers : {
293+ host : 'example.com' ,
294+ } ,
295+ protocol : 'https' ,
296+ } ) ;
297+
298+ expect ( actual ) . toEqual ( {
299+ url : 'https://example.com/test' ,
300+ headers : {
301+ host : 'example.com' ,
302+ } ,
303+ } ) ;
304+ } ) ;
305+
306+ it ( 'should ignore x-forwarded headers when they contain non-string values' , ( ) => {
307+ const actual = httpRequestToRequestData ( {
308+ url : '/test' ,
309+ headers : {
310+ host : 'example.com' ,
311+ 'x-forwarded-host' : [ 'forwarded.example.com' ] as any ,
312+ 'x-forwarded-proto' : [ 'https' ] as any ,
313+ } ,
314+ protocol : 'http' ,
315+ } ) ;
316+
317+ expect ( actual ) . toEqual ( {
318+ url : 'http://example.com/test' ,
319+ headers : {
320+ host : 'example.com' ,
321+ } ,
322+ } ) ;
323+ } ) ;
324+
325+ it ( 'should correctly transform localhost request to public URL using x-forwarded headers' , ( ) => {
326+ const actual = httpRequestToRequestData ( {
327+ method : 'GET' ,
328+ url : '/' ,
329+ headers : {
330+ host : 'localhost:3000' ,
331+ 'x-forwarded-proto' : 'https' ,
332+ 'x-forwarded-host' : 'example.com' ,
333+ } ,
334+ } ) ;
335+
336+ expect ( actual ) . toEqual ( {
337+ method : 'GET' ,
338+ url : 'https://example.com/' ,
339+ headers : {
340+ host : 'localhost:3000' ,
341+ 'x-forwarded-proto' : 'https' ,
342+ 'x-forwarded-host' : 'example.com' ,
343+ } ,
344+ } ) ;
345+ } ) ;
346+
347+ it ( 'should respect x-forwarded-proto even when it downgrades from encrypted socket' , ( ) => {
348+ const actual = httpRequestToRequestData ( {
349+ url : '/test' ,
350+ headers : {
351+ host : 'example.com' ,
352+ 'x-forwarded-proto' : 'http' ,
353+ } ,
354+ socket : {
355+ encrypted : true ,
356+ } ,
357+ } ) ;
358+
359+ expect ( actual ) . toEqual ( {
360+ url : 'http://example.com/test' ,
361+ headers : {
362+ host : 'example.com' ,
363+ 'x-forwarded-proto' : 'http' ,
364+ } ,
365+ } ) ;
366+ } ) ;
367+
368+ it ( 'should preserve query parameters when constructing URL with x-forwarded headers' , ( ) => {
369+ const actual = httpRequestToRequestData ( {
370+ method : 'GET' ,
371+ url : '/search?q=test&category=api' ,
372+ headers : {
373+ host : 'localhost:8080' ,
374+ 'x-forwarded-host' : 'search.example.com' ,
375+ 'x-forwarded-proto' : 'https' ,
376+ } ,
377+ } ) ;
378+
379+ expect ( actual ) . toEqual ( {
380+ method : 'GET' ,
381+ url : 'https://search.example.com/search?q=test&category=api' ,
382+ query_string : 'q=test&category=api' ,
383+ headers : {
384+ host : 'localhost:8080' ,
385+ 'x-forwarded-host' : 'search.example.com' ,
386+ 'x-forwarded-proto' : 'https' ,
387+ } ,
388+ } ) ;
389+ } ) ;
390+ } ) ;
201391 } ) ;
202392
203393 describe ( 'extractQueryParamsFromUrl' , ( ) => {
0 commit comments