Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add OutputCachePolicy support #2328

Merged
merged 12 commits into from
Nov 28, 2023
63 changes: 63 additions & 0 deletions docs/docfx/articles/output-caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Output Caching

## Introduction
The reverse proxy can be used to cache proxied responses and serve requests before they are proxied to the destination servers. This can reduce load on the destination servers, add a layer of protection, and ensure consistent policies are implemented across your applications.

> This feature is only available when using .NET 7.0 or later

## Defaults

No output caching is performed unless enabled in the route or application configuration.

## Configuration
Output Cache policies can be specified per route via [RouteConfig.OutputCachePolicy](xref:Yarp.ReverseProxy.Configuration.RouteConfig) and can be bound from the `Routes` sections of the config file. As with other route properties, this can be modified and reloaded without restarting the proxy. Policy names are case insensitive.

Example:
```JSON
{
"ReverseProxy": {
"Routes": {
"route1" : {
"ClusterId": "cluster1",
"OutputCachePolicy": "customPolicy",
"Match": {
"Hosts": [ "localhost" ]
}
}
},
"Clusters": {
"cluster1": {
"Destinations": {
"cluster1/destination1": {
"Address": "https://localhost:10001/"
}
}
}
}
}
}
```

[Output cache policies](https://learn.microsoft.com/aspnet/core/performance/caching/output) are an ASP.NET Core concept that the proxy utilizes. The proxy provides the above configuration to specify a policy per route and the rest is handled by existing ASP.NET Core output caching middleware.

Output cache policies can be configured in Program.cs as follows:
```c#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOutputCache(options =>
{
options.AddPolicy("customPolicy", builder => builder.Expire(TimeSpan.FromSeconds(20)));
});
```

Then add the output caching middleware:

```c#
var app = builder.Build();

app.UseOutputCache();

app.MapReverseProxy();
```

See the [Output Caching](https://learn.microsoft.com/aspnet/core/performance/caching/output) docs for setting up your preferred kind of output caching.
2 changes: 2 additions & 0 deletions docs/docfx/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
href: authn-authz.md
- name: Rate Limiting
href: rate-limiting.md
- name: Output Caching
href: output-caching.md
- name: Cross-Origin Requests (CORS)
href: cors.md
- name: Session Affinity
Expand Down
14 changes: 10 additions & 4 deletions samples/KubernetesIngress.Sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@ metadata:
annotations:
yarp.ingress.kubernetes.io/authorization-policy: authzpolicy
yarp.ingress.kubernetes.io/rate-limiter-policy: ratelimiterpolicy
yarp.ingress.kubernetes.io/output-cache-policy: outputcachepolicy
yarp.ingress.kubernetes.io/transforms: |
- PathRemovePrefix: "/apis"
yarp.ingress.kubernetes.io/route-headers: |
- Name: the-header-key
Values:
Values:
- the-header-value
Mode: Contains
IsCaseSensitive: false
- Name: another-header-key
Values:
Values:
- another-header-value
Mode: Contains
IsCaseSensitive: false
Expand All @@ -75,6 +76,7 @@ The table below lists the available annotations.
|---|---|
|yarp.ingress.kubernetes.io/authorization-policy|string|
|yarp.ingress.kubernetes.io/rate-limiter-policy|string|
|yarp.ingress.kubernetes.io/output-cache-policy|string|
|yarp.ingress.kubernetes.io/backend-protocol|string|
|yarp.ingress.kubernetes.io/cors-policy|string|
|yarp.ingress.kubernetes.io/health-check|[ActivateHealthCheckConfig](https://microsoft.github.io/reverse-proxy/api/Yarp.ReverseProxy.Configuration.ActiveHealthCheckConfig.html)|
Expand All @@ -98,6 +100,10 @@ See https://microsoft.github.io/reverse-proxy/articles/rate-limiting.html for a

`yarp.ingress.kubernetes.io/rate-limiter-policy: mypolicy`

#### Output Cache Policy

`yarp.ingress.kubernetes.io/output-cache-policy: mycachepolicy`

#### Backend Protocol

Specifies the protocol of the backend service. Defaults to http.
Expand Down Expand Up @@ -196,12 +202,12 @@ See https://microsoft.github.io/reverse-proxy/api/Yarp.ReverseProxy.Configuratio
```
yarp.ingress.kubernetes.io/route-headers: |
- Name: the-header-key
Values:
Values:
- the-header-value
Mode: Contains
IsCaseSensitive: false
- Name: another-header-key
Values:
Values:
- another-header-value
Mode: Contains
IsCaseSensitive: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal sealed class YarpIngressOptions
public string AuthorizationPolicy { get; set; }
#if NET7_0_OR_GREATER
public string RateLimiterPolicy { get; set; }
public string OutputCachePolicy { get; set; }
#endif
public SessionAffinityConfig SessionAffinity { get; set; }
public HttpClientConfig HttpClientConfig { get; set; }
Expand Down
15 changes: 15 additions & 0 deletions src/Kubernetes.Controller/Converters/YarpParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private static RouteConfig CreateRoute(YarpIngressContext ingressContext, V1HTTP
AuthorizationPolicy = ingressContext.Options.AuthorizationPolicy,
#if NET7_0_OR_GREATER
RateLimiterPolicy = ingressContext.Options.RateLimiterPolicy,
OutputCachePolicy = ingressContext.Options.OutputCachePolicy,
#endif
#if NET8_0_OR_GREATER
Timeout = ingressContext.Options.Timeout,
Expand Down Expand Up @@ -234,6 +235,20 @@ private static YarpIngressOptions HandleAnnotations(YarpIngressContext context,
{
options.RateLimiterPolicy = rateLimiterPolicy;
}
if (annotations.TryGetValue("yarp.ingress.kubernetes.io/output-cache-policy", out var outputCachePolicy))
Tratcher marked this conversation as resolved.
Show resolved Hide resolved
{
options.OutputCachePolicy = outputCachePolicy;
}
#endif
#if NET8_0_OR_GREATER
if (annotations.TryGetValue("yarp.ingress.kubernetes.io/timeout", out var timeout))
{
options.Timeout = TimeSpan.Parse(timeout, CultureInfo.InvariantCulture);
}
if (annotations.TryGetValue("yarp.ingress.kubernetes.io/timeout-policy", out var timeoutPolicy))
{
options.TimeoutPolicy = timeoutPolicy;
}
#endif
if (annotations.TryGetValue("yarp.ingress.kubernetes.io/cors-policy", out var corsPolicy))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ private static RouteConfig CreateRoute(IConfigurationSection section)
AuthorizationPolicy = section[nameof(RouteConfig.AuthorizationPolicy)],
#if NET7_0_OR_GREATER
RateLimiterPolicy = section[nameof(RouteConfig.RateLimiterPolicy)],
OutputCachePolicy = section[nameof(RouteConfig.OutputCachePolicy)],
#endif
#if NET8_0_OR_GREATER
TimeoutPolicy = section[nameof(RouteConfig.TimeoutPolicy)],
Expand Down
8 changes: 8 additions & 0 deletions src/ReverseProxy/Configuration/RouteConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public sealed record RouteConfig
/// Set to "Default" or leave empty to use the global rate limits, if any.
/// </summary>
public string? RateLimiterPolicy { get; init; }

/// <summary>
/// The name of the OutputCachePolicy to apply to this route.
/// If not set then only the BasePolicy will apply.
/// </summary>
public string? OutputCachePolicy { get; init; }
Tratcher marked this conversation as resolved.
Show resolved Hide resolved
#endif
#if NET8_0_OR_GREATER
/// <summary>
Expand Down Expand Up @@ -106,6 +112,7 @@ public bool Equals(RouteConfig? other)
&& string.Equals(AuthorizationPolicy, other.AuthorizationPolicy, StringComparison.OrdinalIgnoreCase)
#if NET7_0_OR_GREATER
&& string.Equals(RateLimiterPolicy, other.RateLimiterPolicy, StringComparison.OrdinalIgnoreCase)
&& string.Equals(OutputCachePolicy, other.OutputCachePolicy, StringComparison.OrdinalIgnoreCase)
#endif
#if NET8_0_OR_GREATER
&& string.Equals(TimeoutPolicy, other.TimeoutPolicy, StringComparison.OrdinalIgnoreCase)
Expand All @@ -127,6 +134,7 @@ public override int GetHashCode()
hash.Add(AuthorizationPolicy?.GetHashCode(StringComparison.OrdinalIgnoreCase));
#if NET7_0_OR_GREATER
hash.Add(RateLimiterPolicy?.GetHashCode(StringComparison.OrdinalIgnoreCase));
hash.Add(OutputCachePolicy?.GetHashCode(StringComparison.OrdinalIgnoreCase));
#endif
#if NET8_0_OR_GREATER
hash.Add(Timeout?.GetHashCode());
Expand Down
6 changes: 6 additions & 0 deletions src/ReverseProxy/Routing/ProxyEndpointFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#endif
#if NET7_0_OR_GREATER
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.OutputCaching;
#endif
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
Expand Down Expand Up @@ -137,6 +138,11 @@ public Endpoint CreateEndpoint(RouteModel route, IReadOnlyList<Action<EndpointBu
{
endpointBuilder.Metadata.Add(new EnableRateLimitingAttribute(config.RateLimiterPolicy));
}

if (!string.IsNullOrEmpty(config.OutputCachePolicy))
{
endpointBuilder.Metadata.Add(new OutputCacheAttribute { PolicyName = config.OutputCachePolicy });
}
#endif
#if NET8_0_OR_GREATER
if (string.Equals(TimeoutPolicyConstants.Disable, config.TimeoutPolicy, StringComparison.OrdinalIgnoreCase))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ public class ConfigurationConfigProviderTests
""ClusterId"": ""cluster1"",
""AuthorizationPolicy"": ""Default"",
""RateLimiterPolicy"": ""Default"",
""OutputCachePolicy"": ""Default"",
""CorsPolicy"": ""Default"",
""TimeoutPolicy"": ""Default"",
""Timeout"": ""00:00:01"",
Expand Down Expand Up @@ -393,6 +394,7 @@ public class ConfigurationConfigProviderTests
""ClusterId"": ""cluster2"",
""AuthorizationPolicy"": null,
""RateLimiterPolicy"": null,
""OutputCachePolicy"": null,
""CorsPolicy"": null,
""Metadata"": null,
""Transforms"": null
Expand Down