Url rewrite match pattern not working as expected

Hello!

I have an api definition with url_rewrites as follows:
“url_rewrites”: [
{
“path”: “/v4/cards/{id}”,
“method”: “GET”,
“match_pattern”: “^/v4/cards/(.)?",
“rewrite_to”: “http://card-lifecycle:3000/v4/migration/cards$1
},
{
“path”: “/v4/cards”,
“method”: “POST”,
“match_pattern”: "^/v4/cards(.
)?”,
“rewrite_to”: “http://card-lifecycle:3000/v4/migration/cards$1
},
{
“path”: “/v4/cards”,
“method”: “GET”,
“match_pattern”: “^/v4/cards(.*)?”,
“rewrite_to”: “http://api:3000/v4/cards$1
}
]
The proxy part is as follows
“proxy”: {
“listen_path”: “/v4/cards”,
“strip_listen_path”: false,
“target_url”: “http://card-lifecycle:3000
},

My tests with incoming request GET /v4/cards is not working - from the logs (I activated debug logs), I don’t see that rewriter is fired.

I have Tyk version 5.3.4

I just wanted to share the solution and things I learned

{
  "name": "V4 Cards",
  "api_id": "67cbc305-87c8-4e44-b835-db237b7f6203",
  "org_id": "default",
  "disable_rate_limit": true,
  "use_keyless": false,
  "enable_coprocess_auth": true,
  "enable_context_vars": true,
  "definition": {
    "location": "header",
    "key": "version"
  },
...
    "allowed_headers": [
      "*"
    ],
    "exposed_headers": [],
    "allow_credentials": true,
    "max_age": 24,
    "options_passthrough": false,
    "debug": false
  },
  "version_data": {
    "not_versioned": true,
    "versions": {
      "Default": {
        "name": "Default",
        "global_headers": {
          "X-Firstparty-Request-Id": "$tyk_context.headers_X_Request_Id"
        },
        "use_extended_paths": true,
        "extended_paths": {
          "url_rewrites": [
            {
              "path": "^/renew",
              "method": "POST",
              "match_pattern": "^/v4/cards/renew(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/renew$1"
            },
            {
              "path": "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/limits",
              "method": "PATCH",
              "match_pattern": "^/v4/cards/(.*)/limits(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/$1/limits$2"
            },
            {
              "path": "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/limits",
              "method": "PUT",
              "match_pattern": "^/v4/cards/(.*)/limits(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/$1/limits$2"
            },
            {
              "path": "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/numbers$",
              "method": "GET",
              "match_pattern": "^/v4/cards/(.*)/numbers(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/$1/numbers$2"
            },
            {
              "path": "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$",
              "method": "DELETE",
              "match_pattern": "^/v4/cards/(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/$1"
            },
            {
              "path": "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$",
              "method": "GET",
              "match_pattern": "^/v4/cards/(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards/$1"
            },
            {
              "method": "POST",
              "match_pattern": "^/v4/cards(.*)?",
              "rewrite_to": "http://card-lifecycle:3000/v4/migration/cards$1"
            },
            {
              "method": "GET",
              "match_pattern": "^/v4/cards(.*)?",
              "rewrite_to": "http://api:3000/v4/cards$1"
            }
          ]
        }
      }
    }
  },
...
    "driver": "grpc"
  },
  "proxy": {
    "listen_path": "/v4/cards",
    "strip_listen_path": false,
    "target_url": "http://card-lifecycle:3000"
  },
  "detailed_tracing": true
}

Things I learned:

  • the path in the url_rewrites entry, must not include the listen_path, which is stripped when it’s matched with the inbound path (regardless of how the property strip_listen_path is set)
  • the match_pattern instead is using the full listen_path (i.e. non-stripped) for the match
  • the path in the url_rewrites entries can contain a regex
  • (this is written in the doc, but should be highlighted better) if you use placeholders in the path (e.g., /books/{id}), the placeholder is replaced with (.*) which might screw up your rewrites
  • if your listen_path is /hello and you want to match GET /hello and POST /hello with different rewrites, don’t use path in the url_rewrites entry
  • the most specific path in the url_rewrites must come first in the array

Is it possible to create a merge request to improve docs?

Do you think that the above findings are correct and we could open a MR to help the community?