Skip to content

Add routing rules to the YARP application#3022

Open
davidfowl wants to merge 18 commits intomainfrom
app-routing-rules
Open

Add routing rules to the YARP application#3022
davidfowl wants to merge 18 commits intomainfrom
app-routing-rules

Conversation

@davidfowl
Copy link
Copy Markdown
Member

@davidfowl davidfowl commented Apr 28, 2026

Summary

Adds routing-rule features to the YARP application static-hosting experience so the pre-built app can cover common static site, SPA, edge frontend, and simple reverse-proxy composition scenarios without writing code.

This includes:

  • Headers[] for app-generated non-proxy responses
  • Redirects[] as routed short-circuit endpoints
  • NavigationFallback.Exclude[] for paths that should not fall back to the SPA shell
  • Rewrites[] using the ASP.NET Core rewrite middleware syntax
  • ErrorPages for custom exact-code and Nxx wildcard error pages
  • YARP-aligned match criteria for routed app features: Path, Hosts, Methods, and QueryParameters
  • documentation for the request pipeline mental model
  • realistic sample app configs under samples/YarpApplication.SampleApps

Mental model

The app is composed as a small request pipeline. Each feature has a clear position and responsibility:

1. Rewrites             mutate request path before routing
2. ErrorPages           wrap downstream 4xx/5xx responses and re-execute custom pages
3. Routing              select endpoints
4. Redirects            routed endpoints that short-circuit
5. Static files         serve existing files before proxy/fallback endpoints
6. Headers              mutate app-generated non-proxy responses
7. Reverse proxy        proxy matched backend routes
8. Fallback exclusions  routed 404 endpoints for excluded SPA paths
9. SPA fallback         serve NavigationFallback.Path for remaining non-file routes

Important consequences:

  • Rewrites run first, so every downstream feature sees the rewritten path.
  • Redirects are endpoints, so they participate in routing and win before static/proxy/fallback behavior.
  • Static files are special: the app preserves their historical precedence even though routing runs earlier, so existing files still win over catch-all proxy/fallback endpoints.
  • Headers apply to app-generated responses: static files, redirects, fallback exclusions, SPA fallback, and custom error pages. They do not apply to proxied responses; use YARP response transforms for proxy headers.
  • Fallback exclusions are endpoint-routed 404s, which means excluded paths still compose with routing and error pages.
  • Error pages wrap downstream behavior and re-execute the configured page while preserving the original status code.

Matching model

There are two intentional matching syntaxes.

Routed features use YARP-style route criteria

This applies to:

  • Headers[].Match
  • Redirects[].Match
  • NavigationFallback.Exclude[]

Supported match criteria:

  • Path — ASP.NET route template syntax, e.g. /api/{**catch-all} or /docs/{slug}. Omit Path to match all paths.
  • Hosts — endpoint-routing host patterns, e.g. example.com, *.example.com, example.com:8443.
  • Methods — HTTP methods, e.g. GET, HEAD, POST.
  • QueryParameters — YARP-style query parameter matchers with Exact, Contains, NotContains, Prefix, and Exists modes.

Example:

{
  "Match": {
    "Path": "/docs/{slug}",
    "Hosts": [ "example.com", "*.example.com" ],
    "Methods": [ "GET", "HEAD" ],
    "QueryParameters": [
      {
        "Name": "preview",
        "Values": [ "true" ],
        "Mode": "Exact"
      }
    ]
  }
}

Redirects[].Destination can reference route values captured from Match.Path:

{
  "Match": { "Path": "/docs/{**slug}" },
  "Destination": "/articles/{slug}",
  "StatusCode": 301
}

Rewrites use ASP.NET Core rewrite middleware syntax

No new rewrite DSL is invented. Rewrites use regex + replacement capture groups:

{
  "Regex": "^legacy/(.*)$",
  "Replacement": "api/$1"
}

SkipRemainingRules defaults to true, matching “first rewrite wins” behavior.

Error pages

ErrorPages maps status codes to custom pages:

{
  "ErrorPages": {
    "404": "/errors/not-found.html",
    "5xx": "/errors/server-error.html"
  }
}

Rules support:

  • exact codes: "404", "503"
  • class wildcards: "4xx", "5xx"
  • exact code wins over wildcard

Values must be rooted request paths such as /errors/not-found.html. The implementation re-executes the request against the configured page, clears/resets response state so routed/proxied targets can run, and preserves the original HTTP status code before the response is sent.

Samples

Adds five config/static-file sample apps:

Sample Demonstrates
01-marketing-site Static site, SPA-style fallback, cache/security headers
02-docs-site Legacy redirects, current-version rewrites, docs headers
03-dashboard-spa SPA fallback plus /api proxy exclusion
04-commerce-errors Branded exact and wildcard error pages
05-edge-composition Rewrites, redirects, static assets, proxying, fallback exclusions, and error pages together

These are intentionally not .NET apps; they are realistic appsettings.json + wwwroot layouts for the YARP application.

Validation

  • dotnet test test/Application.Tests/Yarp.Application.Tests.csproj
  • Markdown/schema validation for the application README and config schema
  • Manual Caddy comparison fixture covering redirects, rewrites, static files, SPA fallback, fallback exclusions, error pages, and rewrite-to-proxy behavior

davidfowl and others added 9 commits April 27, 2026 20:14
Routes-first design for the YARP container app's static-host pipeline:

- Redirects -> Map(...).ShortCircuit() routed endpoints (early order).
- NavigationFallback.Exclude -> MapFallback(...) endpoints ordered just
  ahead of the SPA fallback so proxy and other real routes still win.
- Headers -> middleware that targets static-file responses
  (OnPrepareResponse) and the SPA fallback (OnStarting via endpoint
  metadata). Static files are not endpoints, so this remains the only
  consumer of RequestMatchEvaluator.TryMatch.
- StaticFilesFeature wraps UseFileServer to stash/clear and restore the
  selected endpoint, preserving the rule that static files beat routed
  fallback endpoints while routed real endpoints still win.
- RequestMatchEvaluator exposes a static ValidatePath helper so callers
  that delegate matching to ASP.NET routing skip the TemplateMatcher
  allocation entirely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delegates to the standard ASP.NET rewrite middleware
(Microsoft.AspNetCore.Rewrite) instead of inventing a new syntax.
Config maps directly to RewriteOptions.AddRewrite parameters:

  { "Regex": "^old/(.*)$", "Replacement": "new/$1" }

Slots in before UseRouting so every downstream stage (route matching,
static files, redirects, SPA fallback, reverse proxy) sees the
rewritten path. SkipRemainingRules defaults to true (first match wins);
set false to chain rewrites.

7 new tests cover passthrough, capture-group substitution, ordering vs.
routing/redirects/proxy/fallback exclusions, and the no-chaining
default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RedirectsFeature and NavigationFallbackExclusionsFeature captured the
  loop variable 'i' in the .Add() callback. Endpoint convention
  callbacks run after the loop completes, so all endpoints were getting
  the same final Order value. Hoist Order into a per-iteration local.
  Existing tests didn't catch this because each rule has a unique path,
  so Order ties weren't observable.
- Wrap RewriteOptions.AddRewrite in try/catch so an invalid Regex
  pattern surfaces as 'Rewrite rule at index N has an invalid Regex
  pattern "..."' instead of a generic ArgumentException from the
  Regex constructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a 'Request pipeline' section to the application README that lays
out the eight stages (rewrites -> routing match -> redirects -> static
files -> headers -> reverse proxy -> fallback exclusions -> SPA
fallback) as the central mental model, plus a 'Match syntax' note
explaining why routed features use route templates while Rewrites use
regex (delegation to the standard rewrite middleware).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Maps HTTP status codes to custom response files via re-execute. Supports
exact codes ("404") and class wildcards ("5xx"); exact wins over wildcard.
Slots between Rewrites and Routing in the request pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clear the response before re-executing a configured error page so routed and proxied targets can run normally, then restore the original status code before the response is sent. Add coverage for a proxied error page target that writes 200 OK while the client still receives the original 404.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add five realistic config/static-file sample apps that demonstrate the YARP application static-hosting and routing-rule features: marketing site headers, docs redirects/rewrites, dashboard API proxying, custom commerce error pages, and a composed edge frontend.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Normalize table spacing and remove an extra trailing blank line so markdownlint passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous Azure Ubuntu leg failed in ReverseProxy.FunctionalTests Expect100Continue coverage, unrelated to the YARP Application changes in this branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl davidfowl requested a review from sebastienros April 29, 2026 04:38
davidfowl and others added 3 commits April 28, 2026 21:54
Add comments explaining why custom error pages clear response state, restore the original status code, and reset routing state during re-execute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the route-template, rewrite regex, redirect destination, and error-page key parsing paths with concrete examples so the config syntax is easier to follow in code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move relative endpoint order values to named constants and explain how endpoint routing uses those ranges for redirects and SPA fallback exclusions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl davidfowl marked this pull request as ready for review May 2, 2026 18:42
@davidfowl davidfowl requested a review from MihaZupan as a code owner May 2, 2026 18:42
@davidfowl davidfowl requested a review from Copilot May 2, 2026 18:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds declarative routing/static-hosting features to the prebuilt YARP application so common frontend/static-site scenarios can be configured without custom code, alongside expanded docs, tests, and sample app layouts.

Changes:

  • Adds configurable Headers, Redirects, Rewrites, NavigationFallback.Exclude, and ErrorPages features to the application pipeline.
  • Updates the request pipeline and configuration model/schema to support those features and document their ordering/behavior.
  • Expands application tests and adds realistic sample app configurations demonstrating the new scenarios.
Show a summary per file
File Description
test/Application.Tests/wwwroot/server-error.html Adds test asset for custom 5xx error-page scenarios.
test/Application.Tests/wwwroot/404.html Adds test asset for custom 404 error-page scenarios.
test/Application.Tests/YarpAppConfigBinderTests.cs Adds binder coverage for new config sections and defaults.
test/Application.Tests/SpaFallbackTests.cs Adds integration tests for routing rules, headers, rewrites, redirects, exclusions, and error pages.
src/Application/yarp-config.schema.json Extends JSON schema for new routing/static-host config sections.
src/Application/README.md Documents pipeline order, matching rules, and new config features.
src/Application/Program.cs Reorders pipeline and wires in rewrites, error pages, headers, redirects, and exclusions.
src/Application/Features/StaticHostHeadersFeature.cs Implements header application for static-file and SPA-fallback responses.
src/Application/Features/StaticFilesFeature.cs Preserves static-file precedence when routing executes earlier.
src/Application/Features/RewritesFeature.cs Adds rewrite-middleware integration from config.
src/Application/Features/RequestMatchEvaluator.cs Adds shared route-template matching and destination expansion helpers.
src/Application/Features/RedirectsFeature.cs Adds redirect endpoints with configurable status/destination behavior.
src/Application/Features/NavigationFallbackFeature.cs Tags SPA fallback endpoint with custom metadata.
src/Application/Features/NavigationFallbackExclusionsFeature.cs Adds routed 404 exclusions ahead of SPA fallback.
src/Application/Features/NavigationFallbackEndpointMetadata.cs Defines marker metadata for fallback-specific behavior.
src/Application/Features/ErrorPagesFeature.cs Adds status-code-based re-execution for custom error pages.
src/Application/Configuration/YarpAppConfigBinder.cs Binds new config sections into the object model.
src/Application/Configuration/YarpAppConfig.cs Adds new top-level config properties.
src/Application/Configuration/RewriteRule.cs Adds config model for rewrite rules.
src/Application/Configuration/RequestMatch.cs Adds shared path-match config model.
src/Application/Configuration/RedirectRule.cs Adds config model for redirect rules.
src/Application/Configuration/NavigationFallbackOptions.cs Adds fallback exclusion list to config.
src/Application/Configuration/HeaderRule.cs Adds config model for static-host header rules.
samples/YarpApplication.SampleApps/README.md Documents how to run the new sample app layouts.
samples/YarpApplication.SampleApps/05-edge-composition/wwwroot/index.html Sample edge frontend landing page.
samples/YarpApplication.SampleApps/05-edge-composition/wwwroot/errors/server-error.html Sample branded 5xx error page.
samples/YarpApplication.SampleApps/05-edge-composition/wwwroot/errors/not-found.html Sample branded 404 error page.
samples/YarpApplication.SampleApps/05-edge-composition/wwwroot/assets/app.css Sample static asset for edge-composition app.
samples/YarpApplication.SampleApps/05-edge-composition/appsettings.json Demonstrates combined rewrites, redirects, exclusions, proxying, headers, and error pages.
samples/YarpApplication.SampleApps/04-commerce-errors/wwwroot/index.html Sample commerce homepage.
samples/YarpApplication.SampleApps/04-commerce-errors/wwwroot/errors/server-error.html Sample wildcard 5xx page.
samples/YarpApplication.SampleApps/04-commerce-errors/wwwroot/errors/not-found.html Sample 404 page.
samples/YarpApplication.SampleApps/04-commerce-errors/wwwroot/errors/maintenance.html Sample exact 503 page.
samples/YarpApplication.SampleApps/04-commerce-errors/appsettings.json Demonstrates exact/wildcard error-page behavior with proxied checkout routes.
samples/YarpApplication.SampleApps/03-dashboard-spa/wwwroot/index.html Sample SPA shell.
samples/YarpApplication.SampleApps/03-dashboard-spa/wwwroot/assets/site.css Sample dashboard asset.
samples/YarpApplication.SampleApps/03-dashboard-spa/appsettings.json Demonstrates SPA fallback exclusions plus proxy/API composition.
samples/YarpApplication.SampleApps/02-docs-site/wwwroot/index.html Sample docs landing page.
samples/YarpApplication.SampleApps/02-docs-site/wwwroot/docs/v2/intro.html Sample rewritten docs target.
samples/YarpApplication.SampleApps/02-docs-site/wwwroot/docs/intro.html Sample redirect destination page.
samples/YarpApplication.SampleApps/02-docs-site/appsettings.json Demonstrates redirects, rewrites, and docs-specific headers.
samples/YarpApplication.SampleApps/01-marketing-site/wwwroot/index.html Sample marketing-site shell.
samples/YarpApplication.SampleApps/01-marketing-site/wwwroot/assets/site.css Sample marketing asset.
samples/YarpApplication.SampleApps/01-marketing-site/appsettings.json Demonstrates static hosting, fallback, and header rules.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 44/44 changed files
  • Comments generated: 2

Comment thread src/Application/Features/NavigationFallbackExclusionsFeature.cs Outdated
Comment thread src/Application/Features/StaticFilesFeature.cs
davidfowl and others added 6 commits May 2, 2026 22:32
Use regular endpoints for SPA fallback exclusions so dotted file-like paths can be excluded, and clear preserved static-file endpoint state so status-code re-execution cannot restore stale endpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add comments explaining the dotted-path fallback exclusion case and stale endpoint re-execute case covered by the feedback regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover additional feature combinations: rewrites feeding static-host header matching, redirects winning over fallback exclusions, and static-file error pages receiving header rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants