[BUG][C-Sharp] HTTP subpath is ignored in the configured `BaseAddress` of the `HttpClient`
Bug Report Checklist
- [x] Have you provided a full/minimal spec to reproduce the issue?
- [x] Have you validated the input using an OpenAPI validator (example)?
- [ ] Have you tested with the latest master to confirm the issue still exists?
- [x] Have you searched for related issues/PRs?
- [x] What's the actual output vs expected output?
- [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
Let's assume we've configured the BaseAddress of the HttpClient used in the generated C# client from the OpenAPI description like so:
client.BaseAddress = new Uri("http://localhost/subpath/");
Then, using the generated client, the /subpath/ part of the BaseAddress is completely ignored when sending HTTP requests.
openapi-generator version
7.7.0
OpenAPI declaration file content or url
https://github.com/ory/kratos/blob/master/.schema/openapi.json
Generation Details
openapi-generator-cli generate --config <...>
https://github.com/leancodepl/dotnet-kratos-client/blob/master/generate.sh
Steps to reproduce
- Generate the C# OpenAPI client
- Configure
HttpClientprovidingBaseAddresswith a subpath - Attempt to send a request using generated client
Related issues/PRs
Somehow related issue, where generated client ignored provided HTTP scheme: https://github.com/OpenAPITools/openapi-generator/issues/14682
Suggest a fix
Instead of building the base address ourselves, maybe we should use the one already configured in the HttpClient?
Is your subpath not in ClientUtils.CONTEXT_PATH?
@devhl-labs It is not, since I have generated OpenAPI client with no particular base address to server in mind, having expected that users of that generated client will provide the base address to server by configuring HttpClient.BaseAddress. So in my case, the ClientUtils.CONTEXT_PATH is just const "", and the actual subpath of the configured HttpClient.BaseAddress is ignored.
This is still an issue on openapi-generator version 7.12.0 using the generichost output
I am also not able to rely on servers in the OAS and I ran into this where the API I am consuming has multiple environments, but some of those environments have no subpath, so the solution with CONTEXT_PATH would not work and break the others: e.g:
-
https://some-api-environment.com: Works now -
https://environment.proxy.com/some-api: Doesn't work. If I change OpenAPI spec to make use of CONTEXT_PATH, it will break the other one.
I ended up solving this with a DelegatingHandler to rewrite the Uri, since I was already using one.
DI
builder.ConfigureApi((context, services, options) =>
{
// ...
options.AddApiHttpClients(client =>
{
var config = context.Configuration.GetSection(AccountsConfiguration.SectionName).Get<AccountsConfiguration>()!;
client.BaseAddress = new Uri(config.Domain); // Domain here is the full string "https://example.com/subpath"
},
builder =>
{
builder
.AddHttpMessageHandler<AccountsDelegatingHandler>()
});
});
And then the handler itself
public class AccountsDelegatingHandler(IOptions<AccountsConfiguration> options) : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri?.AbsoluteUri.StartsWith(options.Value.Domain) == false) // Domain here is the full string "https://example.com/subpath"
{
request.RequestUri = new Uri(options.Value.Domain + request.RequestUri!.PathAndQuery);
}
return await base.SendAsync(request, cancellationToken);
}
}
It seems like an easy change to be done here: generichost/api.mustache that I would be glad to contribute to, but I do not understand the reason why BaseAddress is disassembled into parts and then reassembled again.
I do not understand the reason why BaseAddress is disassembled into parts and then reassembled again.
I will investigate but i vaguely remember an issue about the port not being set. If i remeber wrong and this can be fixed by simpler code that would be great.
@Dragemil @BartoszUrban Please see if this works for you https://github.com/OpenAPITools/openapi-generator/pull/20896
@devhl-labs, yes, thanks, works great!
Looks good, thanks!
Hello @Dragemil and @BartoszUrban ,
I would like to reopen this issue. The new solution causes a bug when you don't set a subpath. For example when we use the address http://localhost:8080/ with the client and have a path like /getFoo then string.Concat(HttpClient.BaseAddress.AbsolutePath, "{{{path}}}"); generates the path //getFoo.
The behaviour can be reproduced by executing the generated tests.
I am experiencing the same issue.
All my microservices have their api without a subpath.
I worked around that by patching every use with find src -name "*.cs" -type f -exec sed -i 's/HttpClient\.BaseAddress\.AbsolutePath/""/g' {} \; in my build script
I will send a pr thanks for the reports
Please check https://github.com/OpenAPITools/openapi-generator/pull/21081 @cavus700 @Ekwav @BartoszUrban
@devhl-labs Thank you for the PR. For me it is working again.
There is one case, which still could cause some trouble:
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost/foo/");
var path = client.BaseAddress.AbsolutePath == "/"
? "/bar"
: string.Concat(client.BaseAddress.AbsolutePath, "/bar");
The path in this example looks like this /foo//bar. It could be avoided by omitting the trailing slash in the base path but as a User this not obvious.
I would suggest a slightly different solution:
[Theory]
[InlineData("http://localhost", "/bar")]
[InlineData("http://localhost/", "/bar")]
[InlineData("http://localhost/api", "/api/bar")]
[InlineData("http://localhost/api/", "/api/bar")]
public void Test(string baseAddress, string expected)
{
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
var actualPath = string.Concat(client.BaseAddress.AbsolutePath.TrimEnd('/'), "/bar") ;
Assert.Equal(expected, actualPath);
}