Micronaut Filter Regex

Since Micronaut® framework 3.1, you can use a regular expression in the HTTP server filter patterns.

Micronaut HTTP Server filters supported Ant-style path patterns. Since 3.1, they support also regular expressions. The default pattern style is Ant-style path pattern.

Example

Imagine you have an API, which returns a list of secrets. You use a url path variable to version your API. For example: /api/v1/secrets.

My first version was a bit naive, I did not even returned JSON, but plain text instead:

package com.example.api.v1;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

import java.util.Collections;
import java.util.List;

@Controller("/api/v1")
class SecretController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/secret")
    List<String> index() {
        return Collections.singletonList("I admire Moriarty");
    }
}

for v2, I returned JSON:

package com.example.api.v2;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

import java.util.Collections;
import java.util.Map;

@Controller("/api/v2")
public class SecretController {
    @Get("/secret")
    Map<String, Object> index() {
        return Collections.singletonMap("messages", 
            Collections.singletonList("I admire Moriarty"));
    }
}

For v3, I changed the JSON key from messages to secrets.

package com.example.api.v3;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

import java.util.Collections;
import java.util.Map;

@Controller("/api/v3")
public class SecretsController {
    @Get("/secret")
    Map<String, Object> index() {
        return Collections.singletonMap("secrets", 
        Collections.singletonList("I admire Moriarty"));
    }
}

I want to keep v1, v2 endpoints alive. However, I want to notify my consumers that they should migrate to v3.

I can add a Deprecation HTTP Header to the HTTP Responses emitted by v1 and v2 endpoints. Thanks to the support of regular expressions since Micronaut Framework 3.1, it is really easy to do it:

package com.example;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.FilterPatternStyle;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;

@Filter(patternStyle = FilterPatternStyle.REGEX, 
        patterns = "/api/v(1|2)/.*")
public class DeprecatedApiVersionsFilter implements HttpServerFilter {

    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        return Flux.from(chain.proceed(request))
                .map(response -> response.header("Deprecation", "true"));
    }
}

The key in the above Filter is that I am defining my pattern style to be a regular expression with patternStyle = FilterPatternStyle.REGEX and then I can use a regular expression: /api/v(1|2)/.* as the pattern value.

/api/v(1|2)/.* matches:

/api/v1/secrets
/api/v2/secrets
⭕️ /api/v3/secrets

Regular expressions is an art. To work with regular expressions I use Patterns for MacOS or RegexBuddy in Windows.

Tags: #micronaut
Oct 2021, 11.