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

Webflux: Default Resource as fallback for non-existing resources [SPR-16788] #21328

Closed
spring-projects-issues opened this issue May 2, 2018 · 3 comments
Assignees
Labels
in: web type: enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented May 2, 2018

Michael Gmeiner opened SPR-16788 and commented

I'm currently migrating a spring-boot app to spring-boot 2 and webflux. Everything looks great and webflux is an amazing integration of Project Reactor into the spring ecosystem. So first thanks for the great work!

Our app serves a SPA which takes use of the History-API, which means there are urls they can only be accessed via a client side router and do not have a corresponding resource in the serving app. That means it should return the index for every resource which cannot be found on the server because the client side router takes care of that. Before Webflux I implemented this with customizing the WebMvcConfigurer like this:

@Configuration
class WebMvcConfig @Autowired constructor(val resourceProperties: ResourceProperties): WebMvcConfigurer {

    override fun addResourceHandlers( registry: ResourceHandlerRegistry ) {
        registry.addResourceHandler( "/**" )
                .addResourceLocations( *resourceProperties.staticLocations )
                .setCachePeriod( 0 ) // no cache
                .resourceChain( resourceProperties.chain.isCache )
                .addResolver(object: PathResourceResolver() {
                    override fun resolveResource(request: HttpServletRequest?, requestPath: String, locations: MutableList<out Resource>, chain: ResourceResolverChain): Resource {

                        return super.resolveResource(request, requestPath, locations, chain) ?: super.resolveResource( request, "/index.html", locations, chain )

                    }
                })
    }
}

 In Webflux this could be also possbile when i register my own lookup function for resources which just overrides the 

org.springframework.web.reactive.function.server.PathResourceLookupFunction

But that is not possible because the PathResourceLookupFunction class is package private.

My question is now if it would be possible to make the PathResourceLookupFunction class public and make it easy to add something like a fallback resource for it?

Or: provide a function on RouterFunctions to create resource mappings with fallback option:

For example like:

RouterFunctions.resourcesWithFallback("/**", ClassPathResource("static/"), ClassPathResource("static/index.html"))

Please let me know your thoughts about that or if you have any other suggestions to achieve that. If you're ok with some possible solution i described above i could also provide a PR for this. 

Thanks in advance


Affects: 5.0.5

Referenced from: commits bfb2eff

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented May 4, 2018

Arjen Poutsma commented

Fixed in bfb2eff

I did not make the PathResourceLookupFunction public, for two reasons:

  • All other non-utility classes in the package are package protected, so exposing this class would make it the odd one out.
  • Keeping the class non-public allows us to change it without breaking extending classes.

Instead, we now expose the resource router function in RouterFunctions.resourceLookupFunction(String, Resource), returning a Function<ServerRequest, Mono<Resource>> that can be composed upon. This allows for a functional solution to the problem, rather than one based on inheritance.

For instance:

Mono<Resource> defaultResource = Mono.just(new ClassPathResource("index.html"));
Function<ServerRequest, Mono<Resource>> lookupFunction =
  RouterFunctions.resourceLookupFunction("/resources/**", new FileSystemResource("public-resources/"))
    .andThen(resourceMono -> resourceMono.switchIfEmpty(defaultResource));

RouterFunction<ServerResponse> resources = RouterFunctions.resources(lookupFunction);

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented May 7, 2018

Michael Gmeiner commented

Thanks for the fast solution!

Yes i understand why making it just public is not a good solution but the one you just implemented is great! Thanks again for the great work!

@spring-projects-issues spring-projects-issues added type: enhancement in: web labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.1 RC1 milestone Jan 11, 2019
@danbim
Copy link

@danbim danbim commented May 15, 2020

@poutsma I've run into exactly the same issue as Michael Gmeiner here before and adopted your solution for my application. Delivery of the defaultResource (and all other resources from my React-generated build directory) works just fine.

However, with this configuration all requests are mapped to this RouterFunction, even the ones for e.g. /actuator/mappings/ or my /api/graphql/ endpoint (created by [1]). Is there a way I can make route configurations coexist, e.g. by configuring a precedence over routes?

[1] https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/RoutesConfiguration.kt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web type: enhancement
Projects
None yet
Development

No branches or pull requests

3 participants