Skip to content

Commit

Permalink
Feat/add redis example (#1)
Browse files Browse the repository at this point in the history
* Add WIP Redis example

* Fix Redis example

* Add Nearform as a known word

* Increase the default cache timeout to 5s

* Stop storing a manual TTL

* Rename 'webfrontend' to 'web'

* Disable local cache

* Greatly increase the cache timeout to allow for debugging
This is a sample, after all

* Disable local cache (missed this in f7c6242)

* Cleanup

* Address static analysis concerns

* Address ReSharper issues in Microsoft's template :)

* Feat/use di properly (#2)

* Switch to properly using DI 🤦‍♂️

* [WIP] Partially fix tests. No compilation errors, but most tests fail

* [WIP] Fix some DI

* Allow the caller to send in a cancellation token

* Fix removal of items in CacheHelperTests

* Remove an impossible test
DI now handles the cache, we can't set it anymore

* [WIP] ?

* Fix SlowDownMiddlewareExtensionsTests/SlowDownOptionsTests

* Make many of the middleware integration tests work again

* [WIP] Attempting to force a 404

* Fix more tests

* Remove `AspNetTestServerFixture`, not used

* Make /err work!

* Address some static analysis finds

* Get back to 100% code coverage on AspNetCoreHelper

* Static analysis fix

* Allow an IP to be passed to CreateXForwardedForHttpRequest()

* Suppress an unused warning

* Add tests for `CacheHelper`'s `Remove()` and `RemoveAll()`

* Remove dead code

* Remove the `TimeDelay` check in `CalculateDelay()`
A window of 0 is invalid for HybridCache and causes an exception.

* Get the middleware back to 100% test coverage.

* Static analysis fix

* Be consistent

* Set the URLs to the Nearform repo

* s/Nearform/Nearform Ltd.

* Add the README to the generated NuGet package

* Remove dead code

* Fix some issues in the readme

* Add Authors/Company/Copyright to the rest of the project files
  • Loading branch information
rnelson authored Dec 6, 2024
1 parent b05ee96 commit 3f32430
Show file tree
Hide file tree
Showing 57 changed files with 1,832 additions and 406 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@ The response will have some additional headers:

| Name | Type | Default Value | Description |
|--------------------------|----------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Delay` | number | `1000` | Base unit of time delay applied to requests. It is expressed in milliseconds. Set to `0` to disable delaying. |
| `DelayAfter` | number | `5` | Number of requests received during `TimeWindow` before starting to delay responses. Set to `0` to disable delaying. |
| `MaxDelay` | number | `int.MaxValue` | The maximum value of delay that a request has after many consecutive attempts. It is an important option for the server when it is running behind a load balancer or reverse proxy, and has a request timeout. Set to `0` to disable delaying. |
| `TimeWindow` | number | `30000` | The duration of the time window during which request counts are kept in memory. It is expressed in milliseconds. Set to `0` to disable delaying. |
| `AddHeaders` | boolean | `true` | Flag to add custom headers `x-slow-down-limit`, `x-slow-down-remaining`, `x-slow-down-delay` for all server responses. |
| `SlowDownEnabled` | bool | `true` | Flag to enable or disable the middleware. |
| `Delay` | int | `1000` | Base unit of time delay applied to requests. It is expressed in milliseconds. Set to `0` to disable delaying. |
| `DelayAfter` | int | `5` | Number of requests received during `TimeWindow` before starting to delay responses. Set to `0` to disable delaying. |
| `MaxDelay` | int | `int.MaxValue` | The maximum value of delay that a request has after many consecutive attempts. It is an important option for the server when it is running behind a load balancer or reverse proxy, and has a request timeout. Set to `0` to disable delaying. |
| `TimeWindow` | int | `30000` | The duration of the time window during which request counts are kept in memory. It is expressed in milliseconds. Set to `0` to disable delaying. |
| `AddHeaders` | bool | `true` | Flag to add custom headers `x-slow-down-limit`, `x-slow-down-remaining`, `x-slow-down-delay` for all server responses. |
| `KeyGenerator` | delegate | (req) => req.ip | Function used to generate keys to uniquely identify requests coming from the same user |
| `OnLimitReached` | delegate | `null` | Function that gets called the first time the limit is reached within `TimeWindow`. |
| `SkipFailedRequests` | boolean | `false` | When `true`, failed requests (status >= 400) won't be counted. |
| `SkipSuccessfulRequests` | boolean | `false` | When `true`, successful requests (status < 400) won't be counted. |
| `SkipFailedRequests` | bool | `false` | When `true`, failed requests (status >= 400) won't be counted. |
| `SkipSuccessfulRequests` | bool | `false` | When `true`, successful requests (status < 400) won't be counted. |
| `Skip` | delegate | `null` | Function used to skip requests. Returning `true` from the function will skip limiting for that request. |
| `CacheTimeout` | int | `5000` | Timeout, in ms, for cache lookups. If exceeded, the request is not delayed. |

## Configuration examples

Expand Down Expand Up @@ -136,4 +138,4 @@ Delay remains the same because the value of `MaxDelay` option is `100000`.

## License

Nearform.AspNetCore.SlowDown is released under the MIT License.
`Nearform.AspNetCore.SlowDown` is released under the MIT License.
1 change: 1 addition & 0 deletions SlowDown.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=BasicSlowDownExample_003B_002A_003B_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=extexp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mundo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=nearform/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
3 changes: 3 additions & 0 deletions samples/BasicSlowDownExample/BasicSlowDownExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>Ross Nelson</Authors>
<Company>Nearform Ltd.</Company>
<Copyright>Copyright (C) 2024 Nearform Ltd.</Copyright>
</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 0 additions & 5 deletions samples/BasicSlowDownExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

var builder = WebApplication.CreateBuilder(args);

#pragma warning disable EXTEXP0018
// Add HybridCache.
builder.Services.AddHybridCache();
#pragma warning restore EXTEXP0018

// Add services to the container.
builder.Services.AddRazorPages();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Authors>Ross Nelson</Authors>
<Company>Nearform Ltd.</Company>
<Copyright>Copyright (C) 2024 Nearform Ltd.</Copyright>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\SlowDown\Nearform.AspNetCore.SlowDown.csproj" />
<ProjectReference Include="..\DistributedCacheSlowDownExample.ServiceDefaults\DistributedCacheSlowDownExample.ServiceDefaults.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Diagnostics.CodeAnalysis;
using Nearform.AspNetCore.SlowDown;
// ReSharper disable HeapView.ObjectAllocation
// ReSharper disable HeapView.ObjectAllocation.Evident
// ReSharper disable HeapView.DelegateAllocation
// ReSharper disable HeapView.ClosureAllocation

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire components.
builder.AddServiceDefaults();

// Use the Aspire Redis connection
builder.AddRedisClient(connectionName: "cache");
builder.AddRedisDistributedCache(connectionName: "cache");

// Add services to the container.
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseSlowDown();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select( index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
});

app.MapDefaultEndpoints();

app.Run();

[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5556",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:7345;http://localhost:5556",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"SlowDown": {
"Delay": 5000,
"DelayAfter": 50,
"MaxDelay": 30000,
"TimeWindow": 600000,
"CacheTimeout": 2147483646
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>Ross Nelson</Authors>
<Company>Nearform Ltd.</Company>
<Copyright>Copyright (C) 2024 Nearform Ltd.</Copyright>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\SlowDown\Nearform.AspNetCore.SlowDown.csproj" />
<ProjectReference Include="..\DistributedCacheSlowDownExample.ServiceDefaults\DistributedCacheSlowDownExample.ServiceDefaults.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Diagnostics.CodeAnalysis;
using Nearform.AspNetCore.SlowDown;
// ReSharper disable HeapView.ObjectAllocation
// ReSharper disable HeapView.ObjectAllocation.Evident
// ReSharper disable HeapView.DelegateAllocation
// ReSharper disable HeapView.ClosureAllocation

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire components.
builder.AddServiceDefaults();

// Use the Aspire Redis connection
builder.AddRedisClient(connectionName: "cache");
builder.AddRedisDistributedCache(connectionName: "cache");

// Add services to the container.
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseSlowDown();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
});

app.MapDefaultEndpoints();

app.Run();

[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5249",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"SlowDown": {
"Delay": 5000,
"DelayAfter": 50,
"MaxDelay": 30000,
"TimeWindow": 600000,
"CacheTimeout": 2147483646
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Authors>Ross Nelson</Authors>
<Company>Nearform Ltd.</Company>
<Copyright>Copyright (C) 2024 Nearform Ltd.</Copyright>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>5aa6c716-9fce-49bb-a0d4-fa4aacc36b22</UserSecretsId>
</PropertyGroup>

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<ItemGroup>
<ProjectReference Include="..\DistributedCacheSlowDownExample.ApiService2\DistributedCacheSlowDownExample.ApiService2.csproj" />
<ProjectReference Include="..\DistributedCacheSlowDownExample.ApiService\DistributedCacheSlowDownExample.ApiService.csproj" />
<ProjectReference Include="..\DistributedCacheSlowDownExample.Web\DistributedCacheSlowDownExample.Web.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ReSharper disable InconsistentNaming

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache")
.WithRedisInsight()
.WithRedisCommander();

var api1_5556 = builder
.AddProject<Projects.DistributedCacheSlowDownExample_ApiService>("api1-5556")
.WithReference(cache);
var api2_5249 = builder
.AddProject<Projects.DistributedCacheSlowDownExample_ApiService2>("api2-5249")
.WithReference(cache);

builder.AddProject<Projects.DistributedCacheSlowDownExample_Web>("web")
.WithExternalHttpEndpoints()
.WithReference(cache)
.WithReference(api1_5556)
.WithReference(api2_5249);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17124;http://localhost:15260",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21006",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22279"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15260",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19059",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20228"
}
}
}
}
Loading

0 comments on commit 3f32430

Please sign in to comment.