Skip to content

Provide RequestPredicate with no side effects #25814

Open
@tt4g

Description

I'm developing a Spring WebFlux application that serve Single Page Application front-end resources.
I have registered front-end resources in the ResourceHandlerRegistry for distribution, but some of them have had to change the Cache-Control header.

The ResourceHandlerRegistry cannot dynamically set the response headers according to the request URL or file extension.
I figured this goal could be accomplished with the WebFilter as well as the Servet API.

I used WebFilter in WebFlux to check the HTTP request and tried to change the HTTP header of the response, but the
I've noticed that WebFlux has different caveats than the Servlet API.
e.g, when I was getting a path from ServerHttpRequest, I had to call request.getPath().pathWithinApplication() or else the context path would be included.

Spring MVC provided an AntMatcher, so I looked for Spring WebFlux to provide a useful matcher as well, so I found the RequestPredicate, but this is a class for the RouterFunction and seems to have some side effects.
e.g, RequestPredicates#path(String) rewrites the path variable in the request.

I then discovered that Spring Security had a ServerWebExchangeMatchers with no side effects, and I implemented my own matcher based on its implementation.

But this is reinventing the wheel.

I was referring to the Spring Security implementation in this case, but I think it would be a time saver for someone like me who wants to do conditional processing with WebFilter, if a similar feature was provided as part of Spring WebFlux.

The following code is a sample of my WebFilter implementation.
my.application.matcher.ServerWebExchangeMatcher is a matcher based on Spring Security ServerWebExchangeMatchers.

package my.application;

import java.util.List;

import my.application.matcher.ServerWebExchangeMatcher;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

public class NoCacheWebFilter implements WebFilter {

    public static final String CACHE_CONTROL_VALUE = "no-cache, no-store, max-age=0, must-revalidate";

    public static final String PRAGMA_VALUE = "no-cache";

    public static final String EXPIRES_VALUE = "0";

    private final ServerWebExchangeMatcher serverWebExchangeMatcher;

    public NoCacheWebFilter(
        ServerWebExchangeMatcher serverWebExchangeMatcher) {

        this.serverWebExchangeMatcher = serverWebExchangeMatcher;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        if (this.serverWebExchangeMatcher.matches(exchange)) {
            exchange.getResponse().beforeCommit(() ->
                Mono.fromRunnable(() -> writeNoCacheHeader(exchange)));
        }

        return chain.filter(exchange);
    }

    private void writeNoCacheHeader(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();

        if (response.getStatusCode() == HttpStatus.NOT_MODIFIED) {
            return;
        }

        HttpHeaders responseHeaders = response.getHeaders();
        responseHeaders.put(HttpHeaders.CACHE_CONTROL, List.of(CACHE_CONTROL_VALUE));
        responseHeaders.put(HttpHeaders.PRAGMA, List.of(PRAGMA_VALUE));
        responseHeaders.put(HttpHeaders.EXPIRES, List.of(EXPIRES_VALUE));
    }

}

Metadata

Assignees

No one assigned

    Labels

    in: webIssues in web modules (web, webmvc, webflux, websocket)status: feedback-providedFeedback has been providedstatus: pending-design-workNeeds design work before any code can be developedtype: enhancementA general enhancement

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions