Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "../../../src/Application/yarp-config.schema.json",
"StaticFiles": {
"Enabled": true
},
"NavigationFallback": {
"Path": "/index.html"
},
"Headers": [
{
"Match": { "Path": "/{**catch-all}" },
"Set": {
"X-Content-Type-Options": "nosniff"
}
},
{
"Match": { "Path": "/assets/{**catch-all}" },
"Set": {
"Cache-Control": "public,max-age=31536000,immutable",
"X-Site": "contoso-marketing"
}
}
]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
body {
color: #17324d;
font-family: system-ui, sans-serif;
margin: 4rem;
}

.eyebrow {
color: #476f95;
letter-spacing: .08em;
text-transform: uppercase;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Contoso Launch</title>
<link rel="stylesheet" href="/assets/site.css">
</head>
<body>
<main>
<p class="eyebrow">Contoso Cloud</p>
<h1>Launch campaigns without shipping a custom server.</h1>
<p>Static assets are served directly, campaign deep links fall back to this shell, and asset responses get cache headers from config.</p>
</main>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "../../../src/Application/yarp-config.schema.json",
"StaticFiles": {
"Enabled": true
},
"Redirects": [
{
"Match": { "Path": "/learn/{slug}" },
"Destination": "/docs/{slug}.html",
"StatusCode": 301
}
],
"Rewrites": [
{
"Regex": "^docs/current/(.*)$",
"Replacement": "docs/v2/$1"
}
],
"Headers": [
{
"Match": { "Path": "/docs/{**catch-all}" },
"Set": {
"X-Docs-Site": "contoso-docs"
}
}
]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Intro docs</h1>
<p>This is the canonical documentation URL after redirecting old /learn links.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Current intro docs</h1>
<p>The /docs/current path is rewritten to this versioned file.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Contoso Docs</h1>
<p>Use /learn/intro for a redirected legacy link, or /docs/current/intro.html for a rewritten versioned link.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "../../../src/Application/yarp-config.schema.json",
"StaticFiles": {
"Enabled": true
},
"NavigationFallback": {
"Path": "/index.html",
"Exclude": [
{ "Path": "/api/{**catch-all}" }
]
},
"Headers": [
{
"Match": { "Path": "/{**catch-all}" },
"Set": {
"X-Static-Host": "dashboard"
}
},
{
"Match": { "Path": "/assets/{**catch-all}" },
"Set": {
"Cache-Control": "public,max-age=3600"
}
}
],
"ReverseProxy": {
"Routes": {
"api": {
"ClusterId": "catalog-api",
"Match": { "Path": "/api/{**catch-all}" }
}
},
"Clusters": {
"catalog-api": {
"Destinations": {
"d1": { "Address": "http://catalog-api:8080/" }
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
body {
background: #101827;
color: #f7fafc;
font-family: system-ui, sans-serif;
margin: 3rem;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Operations Dashboard</title>
<link rel="stylesheet" href="/assets/site.css">
</head>
<body>
<h1>Operations Dashboard</h1>
<p>Client-side dashboard routes fall back here. Requests under /api are excluded from fallback and forwarded to the catalog API.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "../../../src/Application/yarp-config.schema.json",
"StaticFiles": {
"Enabled": true
},
"NavigationFallback": {
"Path": "/index.html",
"Exclude": [
{ "Path": "/checkout/{**catch-all}" }
]
},
"ErrorPages": {
"404": "/errors/not-found.html",
"503": "/errors/maintenance.html",
"5xx": "/errors/server-error.html"
},
"ReverseProxy": {
"Routes": {
"checkout": {
"ClusterId": "checkout",
"Match": { "Path": "/checkout/{**catch-all}" }
}
},
"Clusters": {
"checkout": {
"Destinations": {
"d1": { "Address": "http://checkout-api:8080/" }
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Checkout is temporarily unavailable</h1>
<p>This exact 503 page wins over the broader 5xx wildcard page.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>We could not find that product</h1>
<p>Try browsing featured categories or search again.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Something went wrong</h1>
<p>The store is still responding with the original 5xx status code.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Contoso Store</h1>
<p>The storefront is static, checkout is proxied, and failures render branded error pages while preserving the original status code.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"$schema": "../../../src/Application/yarp-config.schema.json",
"StaticFiles": {
"Enabled": true
},
"NavigationFallback": {
"Path": "/index.html",
"Exclude": [
{ "Path": "/api/{**catch-all}" },
{ "Path": "/orders/{**catch-all}" }
]
},
"Headers": [
{
"Match": { "Path": "/{**catch-all}" },
"Set": {
"X-Static-Host": "edge-frontend"
}
},
{
"Match": { "Path": "/assets/{**catch-all}" },
"Set": {
"Cache-Control": "public,max-age=31536000,immutable"
}
}
],
"Redirects": [
{
"Match": { "Path": "/old-campaign/{slug}" },
"Destination": "/campaigns/{slug}",
"StatusCode": 301
}
],
"Rewrites": [
{
"Regex": "^legacy-static$",
"Replacement": "assets/app.css"
},
{
"Regex": "^content/(.*)$",
"Replacement": "api/content/$1"
}
],
"ErrorPages": {
"404": "/errors/not-found.html",
"5xx": "/errors/server-error.html"
},
"ReverseProxy": {
"Routes": {
"content": {
"ClusterId": "content",
"Match": { "Path": "/api/content/{**catch-all}" }
},
"orders": {
"ClusterId": "orders",
"Match": { "Path": "/orders/{**catch-all}" }
}
},
"Clusters": {
"content": {
"Destinations": {
"d1": { "Address": "http://content-api:8080/" }
}
},
"orders": {
"Destinations": {
"d1": { "Address": "http://orders-api:8080/" }
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
body {
color: #222;
font-family: system-ui, sans-serif;
margin: 3rem;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Page not found</h1>
<p>The edge frontend served this branded 404 page.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>Service temporarily unavailable</h1>
<p>The original 5xx status code is preserved.</p>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Contoso Edge Frontend</title>
<link rel="stylesheet" href="/assets/app.css">
</head>
<body>
<h1>Contoso Edge Frontend</h1>
<p>This sample combines static hosting, route exclusions, redirects, rewrites, proxying, and custom error pages.</p>
</body>
</html>

25 changes: 25 additions & 0 deletions samples/YarpApplication.SampleApps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# YARP Application sample apps

These samples are small, realistic app layouts for the YARP application static-hosting and routing-rule features. They are not .NET projects; each sample is just an `appsettings.json` file plus any static `wwwroot` assets it needs.

Run any sample from the repository root:

```bash
export DOTNET_ROOT="$PWD/.dotnet"
export DOTNET_MULTILEVEL_LOOKUP=0
export PATH="$PWD/.dotnet:$PATH"

ASPNETCORE_URLS=http://127.0.0.1:5000 \
dotnet run --no-launch-profile --project src/Application/Yarp.Application.csproj -- \
samples/YarpApplication.SampleApps/01-marketing-site/appsettings.json
```

Then open `http://127.0.0.1:5000`. Samples that include `ReverseProxy` use realistic placeholder backend addresses such as `http://catalog-api:8080/`; point those at your own local services when trying them.

| Sample app | Demonstrates |
| --- | --- |
| `01-marketing-site` | A static marketing site with SPA-style campaign fallback and long-lived asset caching headers. |
| `02-docs-site` | A documentation site with old URL redirects, "current docs" rewrites, and docs-specific headers. |
| `03-dashboard-spa` | A dashboard SPA that falls back to `index.html` while forwarding `/api` traffic to a backend. |
| `04-commerce-errors` | A commerce frontend with branded exact and wildcard custom error pages. |
| `05-edge-composition` | A composed edge frontend using rewrites, redirects, static assets, proxy routes, SPA fallback exclusions, and custom error pages together. |
11 changes: 11 additions & 0 deletions src/Application/Configuration/HeaderRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Yarp.Application.Configuration;

public sealed class HeaderRule
{
public RequestMatch Match { get; set; } = new();

public Dictionary<string, string?> Set { get; set; } = [];
}
Loading
Loading