@@ -66,12 +66,27 @@ func translateFromFilter(rule gwtypes.HTTPRouteRule, filter gwtypes.HTTPRouteFil
6666 pData .config = configJSON
6767 pluginConfs = append (pluginConfs , pData )
6868 case gatewayv1 .HTTPRouteFilterRequestRedirect :
69- pData := kongPluginConfig {name : "redirect" }
69+ var pluginName string
70+ var config any
71+ var err error
72+ rr := filter .RequestRedirect
73+
74+ // Decide which plugin to use based on the path modifier type.
75+ // If it's a PrefixMatch, we need to use a custom Lua pre-function to handle the redirect properly
76+ // otherwise we can use the standard redirect plugin.
77+ if rr .Path != nil && rr .Path .Type == gatewayv1 .PrefixMatchHTTPPathModifier {
78+ config , err = translateRequestRedirectPreFunction (filter , rule )
79+ pluginName = "pre-function"
80+ } else {
81+ config , err = translateRequestRedirect (filter )
82+ pluginName = "redirect"
83+ }
7084
71- config , err := translateRequestRedirect ( filter )
85+ // From here on
7286 if err != nil {
7387 return nil , fmt .Errorf ("translating RequestRedirect filter: %w" , err )
7488 }
89+ pData := kongPluginConfig {name : pluginName }
7590 configJSON , err := json .Marshal (config )
7691 if err != nil {
7792 return nil , fmt .Errorf ("failed to marshal %q plugin config: %w" , pData .name , err )
@@ -199,6 +214,85 @@ func translateResponseModifier(filter gwtypes.HTTPRouteFilter) (transformerData,
199214 return plugin , err
200215}
201216
217+ type accessPreFunctionConfig struct {
218+ Access []string `json:"access"`
219+ }
220+
221+ func translateRequestRedirectPreFunction (filter gwtypes.HTTPRouteFilter , rule gwtypes.HTTPRouteRule ) (accessPreFunctionConfig , error ) {
222+ rr := filter .RequestRedirect
223+
224+ if rr == nil {
225+ return accessPreFunctionConfig {}, errors .New ("RequestRedirect filter config is missing" )
226+ }
227+
228+ // Kong does not have a direct equivalent for prefix match replacement in redirects.
229+ // We need to deal it with a custom pre-function so that we can access the captured groups.
230+ sourcePathPrefix := getPathPrefixMatchValue (rule )
231+ targetPathPrefix := lo .FromPtrOr (rr .Path .ReplacePrefixMatch , "" )
232+ targetHost := string (lo .FromPtrOr (rr .Hostname , "" ))
233+ customScheme := ""
234+ if rr .Scheme != nil && * rr .Scheme != "" {
235+ customScheme = * rr .Scheme
236+ }
237+ customCode := 302
238+ if rr .StatusCode != nil {
239+ customCode = * rr .StatusCode
240+ }
241+
242+ funcBody := translateRequestRedirectGenerateFunctionBody (
243+ sourcePathPrefix ,
244+ targetPathPrefix ,
245+ targetHost ,
246+ customScheme ,
247+ customCode )
248+ return accessPreFunctionConfig {Access : []string {funcBody }}, nil
249+ }
250+
251+ func translateRequestRedirectGenerateFunctionBody (
252+ sourcePathPrefix , targetPathPrefix , targetHost , customScheme string ,
253+ customCode int ) string {
254+ return fmt .Sprintf (`
255+ -- Inputs
256+ local match_prefix = [[%s]]
257+ local custom_prefix = [[%s]]
258+ local custom_host = [[%s]]
259+ local custom_scheme = [[%s]]
260+ local code = %d
261+
262+ -- Scheme: use custom_scheme if provided, else preserve original
263+ local scheme = custom_scheme
264+ if not scheme or scheme == "" then
265+ scheme = kong.request.get_scheme() or "http"
266+ end
267+
268+ -- Host: use custom_host if provided, else preserve original
269+ local host = custom_host
270+
271+ -- Prefer forwarded host if present (more accurate in proxied setups)
272+ if not host or host == "" then
273+ host = kong.request.get_forwarded_host() or kong.request.get_host()
274+ end
275+
276+ -- Get request path and raw query string
277+ local path = kong.request.get_path() or "/"
278+ local qs = kong.request.get_raw_query()
279+ if path:sub(1, #match_prefix) == match_prefix then
280+ local remainder = path:sub(#match_prefix + 1)
281+ if remainder == "" then
282+ remainder = "/"
283+ end
284+ -- Build redirect target
285+ local new_path = custom_prefix .. remainder
286+ local location = scheme .. "://" .. host .. new_path
287+ if qs and qs ~= "" then
288+ location = location .. "?" .. qs
289+ end
290+ -- Issue redirect
291+ return kong.response.exit(code, "", { ["Location"] = location })
292+ end
293+ ` , sourcePathPrefix , targetPathPrefix , targetHost , customScheme , customCode )
294+ }
295+
202296type requestRedirectConfig struct {
203297 KeepIncomingPath bool `json:"keep_incoming_path"`
204298 Location string `json:"location"`
@@ -250,39 +344,34 @@ func translateRequestRedirectHostname(rr *gatewayv1.HTTPRequestRedirectFilter) s
250344}
251345
252346func translateRequestRedirectPath (rr * gatewayv1.HTTPRequestRedirectFilter ) (string , error ) {
253- path := ""
347+ pluginPath := ""
254348 var err error
255349
256350 if rr .Path == nil {
257- return path , nil
351+ return pluginPath , nil
258352 }
259353
260354 pathModifier := rr .Path
261355 switch pathModifier .Type {
262356 case gatewayv1 .FullPathHTTPPathModifier :
263- path = translateRequestRedirectPathFullPath (pathModifier .ReplaceFullPath )
357+ pluginPath = translatePathReplaceFullPath (pathModifier .ReplaceFullPath )
358+
264359 case gatewayv1 .PrefixMatchHTTPPathModifier :
265- path = translateRequestRedirectPathPrefixMatch (pathModifier .ReplacePrefixMatch )
360+ // Nothing to do here, handled in translateFromFilter to allow proper ordering of plugins.
361+ pluginPath = ""
266362 default :
267363 err = errors .New ("unsupported RequestRedirect path modifier type: " + string (pathModifier .Type ))
268364 }
269- return path , err
365+ return pluginPath , err
270366}
271367
272- func translateRequestRedirectPathFullPath (replaceFullPath * string ) string {
368+ func translatePathReplaceFullPath (replaceFullPath * string ) string {
273369 if replaceFullPath == nil || * replaceFullPath == "" {
274370 return "/"
275371 }
276372 return * replaceFullPath
277373}
278374
279- func translateRequestRedirectPathPrefixMatch (prefixMatch * string ) string {
280- // Not implemented yet - Kong does not have a direct equivalent for prefix match replacement.
281- // KIC in Konnect just ignores PrefixMatch filters, let's do the same.
282- // Tracker: https://github.com/Kong/kong-operator/issues/2466
283- return "/"
284- }
285-
286375func translateURLRewrite (filter gwtypes.HTTPRouteFilter , path string ) (transformerData , error ) {
287376 ur := filter .URLRewrite
288377 pluginConf := transformerData {}
@@ -300,9 +389,9 @@ func translateURLRewrite(filter gwtypes.HTTPRouteFilter, path string) (transform
300389 if ur .Path != nil {
301390 switch ur .Path .Type {
302391 case gatewayv1 .FullPathHTTPPathModifier :
303- pluginConf .Replace .Uri = translateURLRewritePathFullPath (ur .Path .ReplaceFullPath )
392+ pluginConf .Replace .Uri = translatePathReplaceFullPath (ur .Path .ReplaceFullPath )
304393 case gatewayv1 .PrefixMatchHTTPPathModifier :
305- pluginConf .Replace .Uri = translateURLRewritePathPrefixMatch (
394+ pluginConf .Replace .Uri = translatePathReplacePrefixMatch (
306395 normalizePath (ur .Path .ReplacePrefixMatch ),
307396 normalizePath (& path ))
308397 default :
@@ -320,18 +409,11 @@ func normalizePath(path *string) string {
320409 return strings .TrimSuffix (* path , "/" )
321410}
322411
323- func translateURLRewritePathFullPath (replaceFullPath * string ) string {
324- if replaceFullPath == nil || * replaceFullPath == "" {
325- return "/"
326- }
327- return * replaceFullPath
328- }
329-
330- // translateURLRewritePathPrefixMatch generates the replacement URI for the request-transformer
412+ // translatePathReplacePrefixMatch generates the replacement URI for the request-transformer
331413// plugin for the URLRewrite filter with a PrefixMatchHTTPPathModifier.
332414// The logic here is copied from KIC's implementation to ensure consistent behavior, see:
333415// https://github.com/Kong/kubernetes-ingress-controller/blob/main/internal/dataplane/translator/subtranslator/httproute.go#L1434.
334- func translateURLRewritePathPrefixMatch (replacePrefixMatch string , path string ) string {
416+ func translatePathReplacePrefixMatch (replacePrefixMatch string , path string ) string {
335417 // Trim the trailing slash from the ReplacePrefixMatch to avoid double slashes in the final URI.
336418 replacePrefixMatch = strings .TrimSuffix (replacePrefixMatch , "/" )
337419 pathIsRoot := path == "/"
0 commit comments