Bug Report Checklist
Description
I believe I have found a bug in the C# generic host generator's RateLimitProvider implementation.
I have an API that requires 2 headers:
Following the instructions in the generated README.md file, I configure my API as such:
builder.Host.ConfigureApi(
(context, services, options) =>
{
var config = context.Configuration.GetSection("[REDACTED]");
var baseAddress = config.GetValue<string>("BaseAddress", string.Empty);
var apiKey = config.GetValue<string>("ApiKey", string.Empty);
var clientId = config.GetValue<string>("ClientId", string.Empty);
options.AddTokens([
// "prefix: string.Empty" removes the "Bearer " prefix (The API doesn't support it).
new ApiKeyToken(clientId, ClientUtils.ApiKeyHeader.ClientId, prefix: string.Empty),
new ApiKeyToken(apiKey, ClientUtils.ApiKeyHeader.Authorization, prefix: string.Empty),
]);
options.UseProvider<RateLimitProvider<ApiKeyToken>, ApiKeyToken>();
options.AddApiHttpClients(
httpClient =>
{
httpClient.BaseAddress = new Uri(baseAddress);
},
httpClientBuilder =>
httpClientBuilder
.AddRetryPolicy(2)
.AddTimeoutPolicy(TimeSpan.FromSeconds(5))
.AddCircuitBreakerPolicy(10, TimeSpan.FromSeconds(30))
);
}
);
When making calls to the API in a for loop, I've run into an issue where the API will either:
- Return a
401, stating that my Authorization header was missing.
- Throw an exception, stating that the
Authorization header only supports 1 value.
The above is inconsistent, which led me suspect a threading or timing issue (the default token provider is the RateLimitProvider, after all).
I eventually found this code block inside the RateLimitProvider class:
foreach(global::System.Threading.Channels.Channel<TTokenBase> tokens in AvailableTokens.Values)
for (int i = 0; i < _tokens.Length; i++)
_tokens[i].TokenBecameAvailable += ((sender) => tokens.Writer.TryWrite((TTokenBase) sender));
A quick breakdown of the problem:
_tokens is an array of ApiKeyToken (handled by the base TokenProvider class). In my case: ClientId and Authorization.
AvailableTokens is a Dictionary<string, Channel<ApiKeyToken>>, which becomes: {"ClientId": Channel(ApiKeyToken), "Authorization": Channel(ApiKeyToken)}
- However, the
foreach loop goes over every value in AvailableTokens and then the for loop goes over every value in _tokens, so you end up overwriting all the tokens with whichever token happens to be the last one.
- Sometimes (it's timing-based), it will fail by trying to add
Authorization twice. Other times, it will fail by creating an invalid ClientId, which will have the same value twice.
openapi-generator version
openapi-generator-cli 7.14.0-SNAPSHOT
commit : 65c3126
built : -999999999-01-01T00:00:00+18:00
source : https://github.com/openapitools/openapi-generator
docs : https://openapi-generator.tech/
Generation Details
Generator:
Additional properties:
packageName=[REDACTED]
targetFramework=net9.0
nullableReferenceTypes=true
useDateTimeOffset=true
useSourceGeneration=true
netCoreProjectFile=true
apiName=[REDACTED]
equatable=false
Steps to reproduce
- Generated a client with the openapi-generator-cli tool (using the above generation arguments)
- Provide multiple tokens when configuring (following the generated
README.md file):
options.AddTokens([
new ApiKeyToken(clientId, ClientUtils.ApiKeyHeader.ClientId),
new ApiKeyToken(apiKey, ClientUtils.ApiKeyHeader.Authorization),
]);
- Make multiple calls to the API inside a
for loop.
Suggest a fix
When adding the TokenBecameAvailable handler, make sure the token that is being written to matches the one doing the writing (consider modifying the for loop instead of iterating over all the values in _tokens).
Bug Report Checklist
Description
I believe I have found a bug in the C# generic host generator's
RateLimitProviderimplementation.I have an API that requires 2 headers:
ClientIdAuthorizationFollowing the instructions in the generated
README.mdfile, I configure my API as such:When making calls to the API in a for loop, I've run into an issue where the API will either:
401, stating that myAuthorizationheader was missing.Authorizationheader only supports 1 value.The above is inconsistent, which led me suspect a threading or timing issue (the default token provider is the
RateLimitProvider, after all).I eventually found this code block inside the
RateLimitProviderclass:A quick breakdown of the problem:
_tokensis an array ofApiKeyToken(handled by the baseTokenProviderclass). In my case:ClientIdandAuthorization.AvailableTokensis aDictionary<string, Channel<ApiKeyToken>>, which becomes:{"ClientId": Channel(ApiKeyToken), "Authorization": Channel(ApiKeyToken)}foreachloop goes over every value inAvailableTokensand then theforloop goes over every value in_tokens, so you end up overwriting all the tokens with whichever token happens to be the last one.Authorizationtwice. Other times, it will fail by creating an invalidClientId, which will have the same value twice.openapi-generator version
openapi-generator-cli 7.14.0-SNAPSHOT
commit : 65c3126
built : -999999999-01-01T00:00:00+18:00
source : https://github.com/openapitools/openapi-generator
docs : https://openapi-generator.tech/
Generation Details
Generator:
csharpAdditional properties:
packageName=[REDACTED]targetFramework=net9.0nullableReferenceTypes=trueuseDateTimeOffset=trueuseSourceGeneration=truenetCoreProjectFile=trueapiName=[REDACTED]equatable=falseSteps to reproduce
README.mdfile):forloop.Suggest a fix
When adding the
TokenBecameAvailablehandler, make sure the token that is being written to matches the one doing the writing (consider modifying theforloop instead of iterating over all the values in_tokens).