aspnet / HttpAbstractions

[Archived] HTTP abstractions such as HttpRequest, HttpResponse, and HttpContext, as well as common web utilities. Project moved to https://github.com/aspnet/AspNetCore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add a marker in built-in terminal middleware to distinguish between "soft" and "hard" 404

khellang opened this issue · comments

I've come across a scenario where I'd like to determine if a 404 response coming up the pipeline was the result of hitting the built-in terminal middleware ("hard" 404) or if someone actually handled the request, but returned 404 anyway ("soft" 404). This can be solved easily by adding a marker in HttpContext.Items for middleware to check. Would you accept a PR for something like that?

What would it do if it detected a soft 404?

So, the scenario is that I have an API that also serves a SPA using StaticFiles. Right before StaticFiles, I have a MW that runs the pipeline and then checks if a 404 comes back. When a 404 comes back, I "fall back" to /index.html and re-execute the pipeline. This results in client-side routes getting served index.html, and the client-side JS routing can take over. The only problem is if the API actually sends a soft 404. In that case, I don't want to fall back to index.html, but send a application/problem+json response instead.

Wouldn’t it be better to react to the requested content type instead? If the client asks for HTML respond with that. If it wants JSON, respond with that. Just saying :)

We're working on some changes over in the routing repo that will solve this if you want to opt in to the new model in 2.1.0

Basically we want to decide fairly early what endpoint is going to match, and then let middleware see that. When you get to the end of the chain, only then will it call into the actual endpoint. (usually you will have routing at the end today).

Wouldn’t it be better to react to the requested content type instead? If the client asks for HTML respond with that. If it wants JSON, respond with that. Just saying :)

That's not what this is about. If I have a client-side route, https://my-app.com/customer-info, and share that with a friend. When she goes to that URL, there's nothing on the server that can handle it. In that case, I want to serve index.html (which includes JavaScript for client-side routing).

In another case, a client requests https://my-app.com/customer/123, which can be handled on the server, but because it doesn't have access to customer 123 and you don't want to disclose that customer 123 exists, you return a soft 404. In this case, I absolutely don't want it to serve index.html.

I basically want a catch-all route that uses StaticFiles and doesn't respond to soft 404s 😄

I've seen people tackle this SPA problem from the other end, rewriting requests as they come in rather than reacting to 404s.

I've seen people tackle this SPA problem from the other end, rewriting requests as they come in rather than reacting to 404s.

How do you know which requests to rewrite up-front though? That would only work if you have all your API routes at a sub path. The point of this MW is that it should be independent from any routing or other MW 😄

I want the same as MapSpaFallbackRoute in Microsoft.AspNetCore.SpaServices, but something a bit more robust and not coupled to MVC's routing.

Feel free to build a sample and we can see if it's general purpose enough to fit in the stack somewhere. Otherwise aspnet-contrib may be a good place for it.

We're working on some changes over in the routing repo that will solve this if you want to opt in to the new model in 2.1.0

Feel free to build a sample and we can see if it's general purpose enough to fit in the stack somewhere. Otherwise aspnet-contrib may be a good place for it.

@Tratcher @rynowak Does that mean adding a marker in the current MW is out of the question?

Here's what I ended up with: https://gist.github.com/khellang/47e8fac5b1bad2df74cb8c145d32f98e.

It's used like this:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace SpaFallback
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSpaFallback(); // <-- Would love to remove this...
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseStaticFiles();
            app.UseMvc();
        }
    }
}

As you can see, if the built-in terminal MW added a marker, it would substantially simplify the code. Mostly in the implementation, but also slightly from a user's POV.

That's not terribly complex code to begin with...

@SteveSandersonMS Maybe you'd be interested in pulling something like #945 (comment) into JavaScriptServices?

This issue was moved to dotnet/aspnetcore#2687