Query params should not start with double question marks
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.
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.
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
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
{
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
{
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!