Skip to content

PathPattern encoding issue on .NET 6.0 RC2 #1307

@bogdandynamic

Description

@bogdandynamic

Hello. I've encountered a problem after upgrading a aspnet core project from net5.0 (where all worked fine) to net6.0-rc2. All proxied paths have %2F instead of /.

Here is my appsettings.json ReverseProxy config:

  "ReverseProxy": {
    "Clusters": {
      "cluster1": {
        "Destinations": {
          "cluster1/rest": {
            "Address": "http://container1:49602/"
          }
        }
      },
      "cluster1Grpc": {
        "Destinations": {
          "cluster1/grpc": {
            "Address": "http://container1:49603/"
          }
        }
      },
      "cluster2": {
        "Destinations": {
          "cluster2/rest": {
            "Address": "http://container2:49604/"
          }
        }
      },
      "cluster3": {
        "Destinations": {
          "cluster3/rest": {
            "Address": "http://container3:49606/"
          }
        }
      },
      "cluster4": {
        "Destinations": {
          "cluster4/rest": {
            "Address": "http://container4:49608/"
          }
        }
      }
    }
    "Routes": {
      "CommonRestRoute": {
        "ClusterId": "cluster1",
        "CorsPolicy": "CorsPolicy",
        "Match": {
          "Path": "/service1/rest/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      },
      "CommonSignalRRoute": {
        "ClusterId": "cluster1",
        "CorsPolicy": "CorsPolicy",
        "Match": {
          "Path": "/server-notifications/common/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/server-notifications/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      },
      "ProjectsRestRoute": {
        "ClusterId": "cluster2",
        "Match": {
          "Path": "/service2/rest/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      },
      "StorageRestRoute": {
        "ClusterId": "cluster3",
        "Match": {
          "Path": "/storage/rest/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      },
      "StaticStorageRoute": {
        "ClusterId": "cluster3",
        "CorsPolicy": "CorsPolicy",
        "Match": {
          "Path": "/static-storage/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/static-storage/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      },
      "ReceptionsRestRoute": {
        "ClusterId": "cluster4",
        "Match": {
          "Path": "/service4/rest/{*remainder}"
        },
        "Transforms": [
          { "PathPattern": "/{remainder}" },
          { "RequestHeadersCopy": "true" }
        ]
      }

In the container logs I receive the following output:

[09:51:41 INF] Proxying to http://container2:49604/v1.0%2Fprojects%2F?simpleDto=true
[09:51:41 INF] Proxying to http://container2:49604/v1.0%2Fprojects%2F?simpleDto=true
[09:51:41 INF] Proxying to http://container2:49604/v1.0%2Fdashboard%2Faq4Zv475

The whole path component, v1.0%2Fprojects%2F, is URL encoded on every transform path defined in appsettings.json.

I downloaded the source code of YARP and traced the problem to PathRouteValuesTransform.cs:

public override ValueTask ApplyAsync(RequestTransformContext context)
{
    if (context is null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    // TemplateBinder.BindValues will modify the RouteValueDictionary
    // We make a copy so that the original request is not modified by the transform
    var routeValues = new RouteValueDictionary(context.HttpContext.Request.RouteValues);

    // Route values that are not considered defaults will be appended as query parameters. Make them all defaults.
    var binder = _binderFactory.Create(Template, defaults: routeValues);
    context.Path = binder.BindValues(acceptedValues: routeValues); // THIS IS WHERE context.Path gets values with %2F
    return default;
}

Development stack and packages:
All 4 services run under docker (container 1 through 4). Docker files use
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base and FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build.
Yarp.ReverseProxy 1.0.0-preview.12.21451.3
All other Microsoft nugets are upgraded to NET 6.0 RC2 or latest available stable version.

Metadata

Metadata

Assignees

Labels

Type: BugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions