@@ -21,6 +21,7 @@ type Match = {
2121 has ?: {
2222 type : 'header'
2323 key : string
24+ value ?: string
2425 } [ ]
2526}
2627
@@ -45,6 +46,8 @@ export type RoutingRuleRewrite = RoutingRuleBase & {
4546 statusCode ?: 200 | 404 | 500
4647 /** Phases to re-run after matching this rewrite */
4748 rerunRoutingPhases ?: RoutingPhase [ ]
49+ /** Headers to include in the response */
50+ headers ?: Record < string , string >
4851 }
4952}
5053
@@ -80,22 +83,33 @@ function selectRoutingPhasesRules(routingRules: RoutingRule[], phases: RoutingPh
8083
8184let requestCounter = 0
8285
86+ const NOT_A_FETCH_RESPONSE = Symbol ( 'Not a Fetch Response' )
87+ type MaybeResponse = {
88+ response ?: Response | undefined
89+ status ?: number | undefined
90+ headers ?: HeadersInit | undefined
91+ [ NOT_A_FETCH_RESPONSE ] : true
92+ }
93+
8394// eslint-disable-next-line max-params
8495async function match (
8596 request : Request ,
8697 context : Context ,
8798 routingRules : RoutingRule [ ] ,
8899 outputs : NetlifyAdapterContext [ 'preparedOutputs' ] ,
89- prefix : string ,
90- ) {
100+ prefix : string | undefined ,
101+ initialResponse : MaybeResponse ,
102+ ) : Promise < MaybeResponse > {
91103 let currentRequest = request
92- let maybeResponse : Response | undefined
104+ let maybeResponse : MaybeResponse = initialResponse
93105
94106 const currentURL = new URL ( currentRequest . url )
95107 let { pathname } = currentURL
96108
97109 for ( const rule of routingRules ) {
98- console . log ( prefix , 'Evaluating rule:' , rule . description ?? JSON . stringify ( rule ) )
110+ if ( prefix ) {
111+ console . log ( prefix , 'Evaluating rule:' , rule . description ?? JSON . stringify ( rule ) )
112+ }
99113 if ( 'match' in rule ) {
100114 if ( 'type' in rule . match ) {
101115 if ( rule . match . type === 'static-asset-or-function' ) {
@@ -117,16 +131,26 @@ async function match(
117131 }
118132
119133 if ( matchedType ) {
120- console . log (
121- prefix ,
122- `Matched static asset or function (${ matchedType } ): ${ pathname } -> ${ currentRequest . url } ` ,
123- )
124- maybeResponse = await context . next ( currentRequest )
134+ if ( prefix ) {
135+ console . log (
136+ prefix ,
137+ `Matched static asset or function (${ matchedType } ): ${ pathname } -> ${ currentRequest . url } ` ,
138+ )
139+ }
140+ maybeResponse = {
141+ ...maybeResponse ,
142+ response : await context . next ( currentRequest ) ,
143+ }
125144 }
126145 } else if ( rule . match . type === 'image-cdn' && pathname . startsWith ( '/.netlify/image/' ) ) {
127- console . log ( prefix , 'Matched image cdn:' , pathname )
146+ if ( prefix ) {
147+ console . log ( prefix , 'Matched image cdn:' , pathname )
148+ }
128149
129- maybeResponse = await context . next ( currentRequest )
150+ maybeResponse = {
151+ ...maybeResponse ,
152+ response : await context . next ( currentRequest ) ,
153+ }
130154 }
131155 } else if ( 'apply' in rule ) {
132156 const sourceRegexp = new RegExp ( rule . match . path )
@@ -135,9 +159,16 @@ async function match(
135159 if ( rule . match . has ) {
136160 let hasAllMatch = true
137161 for ( const condition of rule . match . has ) {
138- if ( condition . type === 'header' && ! currentRequest . headers . has ( condition . key ) ) {
139- hasAllMatch = false
140- break
162+ if ( condition . type === 'header' ) {
163+ if ( typeof condition . value === 'undefined' ) {
164+ if ( ! currentRequest . headers . has ( condition . key ) ) {
165+ hasAllMatch = false
166+ break
167+ }
168+ } else if ( currentRequest . headers . get ( condition . key ) !== condition . value ) {
169+ hasAllMatch = false
170+ break
171+ }
141172 }
142173 }
143174
@@ -146,38 +177,69 @@ async function match(
146177 }
147178 }
148179
180+ if ( prefix ) {
181+ console . log ( prefix , 'Matched rule' , pathname , rule )
182+ }
183+
149184 const replaced = pathname . replace ( sourceRegexp , rule . apply . destination )
150185
151186 if ( rule . apply . type === 'rewrite' ) {
152187 const destURL = new URL ( replaced , currentURL )
153188 currentRequest = new Request ( destURL , currentRequest )
154189
190+ if ( rule . apply . headers ) {
191+ maybeResponse = {
192+ ...maybeResponse ,
193+ headers : {
194+ ...maybeResponse . headers ,
195+ ...rule . apply . headers ,
196+ } ,
197+ }
198+ }
199+
200+ if ( rule . apply . statusCode ) {
201+ maybeResponse = {
202+ ...maybeResponse ,
203+ status : rule . apply . statusCode ,
204+ }
205+ }
206+
155207 if ( rule . apply . rerunRoutingPhases ) {
156208 maybeResponse = await match (
157209 currentRequest ,
158210 context ,
159211 selectRoutingPhasesRules ( routingRules , rule . apply . rerunRoutingPhases ) ,
160212 outputs ,
161213 prefix ,
214+ maybeResponse ,
162215 )
163216 }
164217 } else {
165- console . log ( prefix , `Redirecting ${ pathname } to ${ replaced } ` )
166- maybeResponse = new Response ( null , {
167- status : rule . apply . statusCode ?? 307 ,
168- headers : {
169- Location : replaced ,
170- } ,
171- } )
218+ if ( prefix ) {
219+ console . log ( prefix , `Redirecting ${ pathname } to ${ replaced } ` )
220+ }
221+ const status = rule . apply . statusCode ?? 307
222+ maybeResponse = {
223+ ...maybeResponse ,
224+ status,
225+ response : new Response ( null , {
226+ status,
227+ headers : {
228+ Location : replaced ,
229+ } ,
230+ } ) ,
231+ }
172232 }
173233 }
174234 }
175235 }
176236
177- if ( maybeResponse ) {
237+ if ( maybeResponse ?. response ) {
238+ // once hit a response short circuit
178239 return maybeResponse
179240 }
180241 }
242+ return maybeResponse
181243}
182244
183245export async function runNextRouting (
@@ -191,28 +253,45 @@ export async function runNextRouting(
191253 return
192254 }
193255
194- const prefix = `[${
195- request . headers . get ( 'x-nf-request-id' ) ??
196- // for ntl serve, we use a combination of timestamp and pid to have a unique id per request as we don't have x-nf-request-id header then
197- // eslint-disable-next-line no-plusplus
198- `${ Date . now ( ) } - #${ process . pid } :${ ++ requestCounter } `
199- } ]`
200-
201- console . log ( prefix , 'Incoming request for routing:' , request . url )
256+ const prefix = request . url . includes ( '_next/static' )
257+ ? undefined
258+ : `[${
259+ request . headers . get ( 'x-nf-request-id' ) ??
260+ // for ntl serve, we use a combination of timestamp and pid to have a unique id per request as we don't have x-nf-request-id header then
261+ // eslint-disable-next-line no-plusplus
262+ `${ Date . now ( ) } - #${ process . pid } :${ ++ requestCounter } `
263+ } ]`
264+
265+ if ( prefix ) {
266+ console . log ( prefix , 'Incoming request for routing:' , request . url )
267+ }
202268
203269 const currentRequest = new Request ( request )
204270 currentRequest . headers . set ( 'x-ntl-routing' , '1' )
205271
206- let maybeResponse = await match ( currentRequest , context , routingRules , outputs , prefix )
207-
208- if ( ! maybeResponse ) {
209- console . log ( prefix , 'No route matched - 404ing' )
210- maybeResponse = new Response ( 'Not Found' , { status : 404 } )
211- }
272+ const maybeResponse = await match ( currentRequest , context , routingRules , outputs , prefix , {
273+ [ NOT_A_FETCH_RESPONSE ] : true ,
274+ } )
275+
276+ const response = maybeResponse . response
277+ ? new Response ( maybeResponse . response . body , {
278+ ...maybeResponse . response ,
279+ headers : {
280+ ...maybeResponse . response . headers ,
281+ ...maybeResponse . headers ,
282+ } ,
283+ status : maybeResponse . status ?? maybeResponse . response . status ?? 200 ,
284+ } )
285+ : new Response ( 'Not Found' , {
286+ status : maybeResponse ?. status ?? 404 ,
287+ headers : maybeResponse ?. headers ,
288+ } )
212289
213290 // for debugging add log prefixes to response headers to make it easy to find logs for a given request
214- maybeResponse . headers . set ( 'x-ntl-log-prefix' , prefix )
215- console . log ( prefix , 'Serving response' , maybeResponse . status )
291+ if ( prefix ) {
292+ response . headers . set ( 'x-ntl-log-prefix' , prefix )
293+ console . log ( prefix , 'Serving response' , response . status )
294+ }
216295
217- return maybeResponse
296+ return response
218297}
0 commit comments