Ocelot: Query params start with ??

Created on 14 Jan 2019  路  4Comments  路  Source: ThreeMammals/Ocelot

Expected Behavior / New Feature

Query params should not start with double question marks

Actual Behavior / Motivation for New Feature

The same bug (and a solution) is mentioned in this issue: https://github.com/ThreeMammals/Ocelot/issues/555#issuecomment-436063081

Now the downstream request contains an extra question mark, which causes unexpected behaviour on the backend server, because the first query parameter name contains a question mark.

https://github.com/ThreeMammals/Ocelot/blob/develop/src/Ocelot/Request/Middleware/DownstreamRequest.cs#L21

Here you can see that you should not assign a value to the UriBuilder.Query with a starting question mark: https://docs.microsoft.com/en-us/dotnet/api/system.uribuilder.query?view=netcore-2.1#examples

However the HttpRequestMessage.RequestUri is a Uri and getting it's Query property will include a starting question mark: https://docs.microsoft.com/en-us/dotnet/api/system.uri.query?view=netcore-2.1#examples

It can be hotfixed with a DelegatingHandler, but that is not the best solution.

Steps to Reproduce the Problem

I am using very simple config:

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/{action}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5000
        }
      ],
      "UpstreamPathTemplate": "/{action}",
      "UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE"],
      "UpstreamHeaderTransform": {
        "Custom-Header": "SomeValue"
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:4000"
  }
}

For example this request

http://localhost:4000/something?someparam=1

will look like this after the ocelot proxy

http://localhost:5000/api/something??someparam=1

Specifications

  • Version: 10.0.4, 12.0.1, 13.0.0
  • Platform: Windows 10, .NET Framework 4.6.1
bug needs feedback

All 4 comments

More info about the bug:

It seems to me that there is difference between the .NET Framework and .NET Standard/Core behavior.

If I host the application over .NET Framework, the UriBuilder will append an extra question mark, but over .NET Core this will not happen (if you remove the leading question mark from the query, it will also give the correct Uri, so in the .NET Core case the leading question mark maybe does not matter).

Maybe that's why the "should_have_question_mark_with_question_mark_prefixed" test not fails.

If you try this modification (or something similar) in DownstreamRequest.cs:

```c#
public HttpRequestMessage ToHttpRequestMessage()
{
var uriBuilder = new UriBuilder
{
Port = Port,
Host = Host,
Path = AbsolutePath,
Query = (!string.IsNullOrEmpty(Query) && Query[0].Equals('?'))
? Query.Substring(1)
: Query,
Scheme = Scheme
};

_request.RequestUri = uriBuilder.Uri;
return _request;

}

```c#
public string ToUri()
{
    var uriBuilder = new UriBuilder
    {
        Port = Port,
        Host = Host,
        Path = AbsolutePath,
        Query = (!string.IsNullOrEmpty(Query) && Query[0].Equals('?'))
            ? Query.Substring(1)
            : Query,
        Scheme = Scheme
    };

    return uriBuilder.Uri.AbsoluteUri;
}

tests will pass, and the proxy would work as expected over .NET Framework too.

hi guys, is there any work-around for this? Which would not include upgrading to a higher version?

@SindelarPetr we used this delegating handler:
```c#
[Obsolete("Ocelot update solves this")]
public class QueryStringHandler : DelegatingHandler
{
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Get && !string.IsNullOrEmpty(request.RequestUri.Query))
{
request.RequestUri = new UriBuilder()
{
Host = request.RequestUri.Host,
Port = request.RequestUri.Port,
Path = request.RequestUri.AbsolutePath,
Query = request.RequestUri.Query.Substring(1),
Scheme = request.RequestUri.Scheme,
}.Uri;
}

    return base.SendAsync(request, cancellationToken);
}

}


you can add this message handler with this code snippet:
```c#
services.AddOcelot().AddDelegatingHandler<QueryStringHandler>(true)

Maybe it would be cleaner (and safer) to use the same logic that is part of the newer Ocelot nuget:
https://github.com/ThreeMammals/Ocelot/pull/794/files

@nagybalint001 thanks a lot for your quick reply! Your solution led to fixing the problem. I just had to tweak it a bit, because this method removed only one '?', but then UriBuilder added it again, so I ended up getting 2 again. I am not sure if every time this method receives a URL with either one or two question marks, so the following semi-nasty code makes the trick.
```c#
[Obsolete("Ocelot update solves this")]
public class QueryStringHandler : DelegatingHandler
{
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(request.RequestUri.Query))
{
request.RequestUri = new UriBuilder()
{
Host = request.RequestUri.Host,
Port = request.RequestUri.Port,
Path = request.RequestUri.AbsolutePath,
Query = RemoveLeadingQuestionMarks(request.RequestUri.Query),
Scheme = request.RequestUri.Scheme,
}.Uri;
}
return base.SendAsync(request, cancellationToken);
}

    private string RemoveLeadingQuestionMarks(string query)
    {
        while (!string.IsNullOrEmpty(query) && query.StartsWith("?"))
        {
            query = query.Substring(1);
        }

        return query;
    }
}

```

Thanks a lot again for the tip!

Was this page helpful?
0 / 5 - 0 ratings