To simplify this, Micronaut includes the ability to define classes that are applied to all matching HTTP client requests.

    As an example say you want to build a client to communicate with the Bintray REST API. It would be terribly tedious to have to specify authentication for every single HTTP call.

    To resolve this burden you can define a filter. The following is an example :

    1. class BintrayApi {
    2. public static final String URL = 'https://api.bintray.com'
    3. }
    4. @Singleton
    5. class BintrayService {
    6. final RxHttpClient client
    7. final String org
    8. BintrayService(
    9. @Client(BintrayApi.URL) RxHttpClient client, (1)
    10. @Value('${bintray.organization}') String org ) {
    11. this.client = client
    12. this.org = org
    13. }
    14. Flowable<HttpResponse<String>> fetchRepositories() {
    15. return client.exchange(HttpRequest.GET("/repos/$org"), String) (2)
    16. }
    17. Flowable<HttpResponse<String>> fetchPackages(String repo) {
    18. return client.exchange(HttpRequest.GET("/repos/${org}/${repo}/packages"), String) (2)
    19. }
    20. }
    1. class BintrayApi {
    2. public static final String URL = 'https://api.bintray.com'
    3. }
    4. @Singleton
    5. internal class BintrayService(
    6. @param:Client(BintrayApi.URL) val client: RxHttpClient, (1)
    7. @param:Value("\${bintray.organization}") val org: String) {
    8. fun fetchRepositories(): Flowable<HttpResponse<String>> {
    9. return client.exchange(HttpRequest.GET<Any>("/repos/$org"), String::class.java) (2)
    10. }
    11. fun fetchPackages(repo: String): Flowable<HttpResponse<String>> {
    12. return client.exchange(HttpRequest.GET<Any>("/repos/$org/$repo/packages"), String::class.java) (2)
    13. }
    14. }
    1. @Filter("/repos/**") (1)
    2. class BintrayFilter implements HttpClientFilter {
    3. final String username;
    4. final String token;
    5. BintrayFilter(
    6. @Value("${bintray.username}") String username, (2)
    7. @Value("${bintray.token}") String token ) { (2)
    8. this.username = username;
    9. this.token = token;
    10. }
    11. @Override
    12. public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
    13. return chain.proceed(
    14. request.basicAuth(username, token) (3)
    15. );
    16. }
    17. }
    1. @Filter('/repos/**') (1)
    2. class BintrayFilter implements HttpClientFilter {
    3. final String username
    4. final String token
    5. BintrayFilter(
    6. @Value('${bintray.username}') String username, (2)
    7. @Value('${bintray.token}') String token ) { (2)
    8. this.username = username
    9. this.token = token
    10. }
    11. @Override
    12. Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
    13. return chain.proceed(
    14. request.basicAuth(username, token) (3)
    15. )
    16. }
    17. }
    1. @Filter("/repos/**") (1)
    2. internal class BintrayFilter(
    3. @param:Value("\${bintray.token}") val token: String)(2)
    4. : HttpClientFilter {
    5. override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>> {
    6. return chain.proceed(
    7. )
    8. }
    9. }
    1You can match only a subset of paths with a Client filter.
    2The username and token are injected via configuration
    3The basicAuth method is used include the HTTP BASIC credentials

    Now, whenever you invoke the bintrayService.fetchRepositories() method, the Authorization HTTP header is included in the request.

    It should be noted that in order to create a Micronaut needs to resolve all of the HttpClientFilter instances which creates a circular dependency in the case where you need to inject another RxHttpClient or a @Client bean into an instance of a HttpClientFilter.

    To resolve this issue you should use the javax.inject.Provider interface to inject another or a @Client bean into an instance of HttpClientFilter.

    1. import io.micronaut.context.annotation.Requires
    2. import io.micronaut.context.env.Environment
    3. import io.micronaut.http.HttpRequest
    4. import io.micronaut.http.HttpResponse
    5. import io.micronaut.http.MutableHttpRequest
    6. import io.micronaut.http.annotation.Filter
    7. import io.micronaut.http.client.RxHttpClient
    8. import io.micronaut.http.filter.ClientFilterChain
    9. import io.micronaut.http.filter.HttpClientFilter
    10. import io.reactivex.Flowable
    11. import org.reactivestreams.Publisher
    12. import javax.inject.Provider
    13. @Requires(env = Environment.GOOGLE_COMPUTE)
    14. @Filter(patterns = "/google-auth/api/**")
    15. class GoogleAuthFilter implements HttpClientFilter {
    16. private final Provider<RxHttpClient> authClientProvider
    17. GoogleAuthFilter(Provider<RxHttpClient> httpClientProvider) { (1)
    18. this.authClientProvider = httpClientProvider
    19. }
    20. @Override
    21. Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
    22. Flowable<String> token = Flowable.fromCallable(() -> encodeURI(request))
    23. .flatMap(authURI -> authClientProvider.get().retrieve(HttpRequest.GET(authURI).header( (2)
    24. "Metadata-Flavor", "Google"
    25. )))
    26. return token.flatMap(t -> chain.proceed(request.bearerAuth(t)))
    27. }
    28. private static String encodeURI(MutableHttpRequest<?> request) {
    29. String receivingURI = "$request.uri.scheme://$request.uri.host"
    30. return "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" +
    31. URLEncoder.encode(receivingURI, "UTF-8")
    32. }
    33. }
    1. import io.micronaut.context.annotation.Requires
    2. import io.micronaut.context.env.Environment
    3. import io.micronaut.http.HttpRequest
    4. import io.micronaut.http.HttpResponse
    5. import io.micronaut.http.MutableHttpRequest
    6. import io.micronaut.http.annotation.Filter
    7. import io.micronaut.http.client.RxHttpClient
    8. import io.micronaut.http.filter.ClientFilterChain
    9. import io.micronaut.http.filter.HttpClientFilter
    10. import io.reactivex.Flowable
    11. import org.reactivestreams.Publisher
    12. import java.net.URLEncoder
    13. import javax.inject.Provider
    14. @Requires(env = [Environment.GOOGLE_COMPUTE])
    15. @Filter(patterns = ["/google-auth/api/**"])
    16. class GoogleAuthFilter (
    17. private val authClientProvider: Provider<RxHttpClient>) : HttpClientFilter { (1)
    18. override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>?> {
    19. val token = Flowable.fromCallable { encodeURI(request) }
    20. .flatMap { authURI: String ->
    21. authClientProvider.get().retrieve(HttpRequest.GET<Any>(authURI).header( (2)
    22. "Metadata-Flavor", "Google"
    23. ))
    24. }
    25. return token.flatMap { t -> chain.proceed(request.bearerAuth(t)) }
    26. }
    27. private fun encodeURI(request: MutableHttpRequest<*>): String {
    28. val receivingURI = "${request.uri.scheme}://${request.uri.host}"
    29. return "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" +
    30. URLEncoder.encode(receivingURI, "UTF-8")
    31. }
    32. }

    Filter Matching By Annotation

    For cases where a filter should be applied to a client, regardless of the URL, filters can be matched by the presence of an annotation applied to both the filter and the client. Given the following client:

    1. import io.micronaut.http.annotation.Get;
    2. @Client("/message")
    3. public interface BasicAuthClient {
    4. @Get
    5. String getMessage();
    6. }
    1. import io.micronaut.http.annotation.Get
    2. import io.micronaut.http.client.annotation.Client
    3. @BasicAuth (1)
    4. @Client("/message")
    5. interface BasicAuthClient {
    6. @Get
    7. String getMessage()
    8. }
    1. import io.micronaut.http.annotation.Get
    2. import io.micronaut.http.client.annotation.Client
    3. @BasicAuth (1)
    4. @Client("/message")
    5. interface BasicAuthClient {
    6. @Get
    7. fun getMessage(): String
    8. }
    1The @BasicAuth annotation is applied to the client

    The following filter will filter the client requests:

    1. import io.micronaut.http.HttpResponse
    2. import io.micronaut.http.MutableHttpRequest
    3. import io.micronaut.http.filter.ClientFilterChain
    4. import io.micronaut.http.filter.HttpClientFilter
    5. import org.reactivestreams.Publisher
    6. import javax.inject.Singleton
    7. @BasicAuth (1)
    8. @Singleton (2)
    9. class BasicAuthClientFilter implements HttpClientFilter {
    10. @Override
    11. Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
    12. chain.proceed(request.basicAuth("user", "pass"))
    13. }
    14. }
    1. import io.micronaut.http.HttpResponse
    2. import io.micronaut.http.MutableHttpRequest
    3. import io.micronaut.http.filter.ClientFilterChain
    4. import io.micronaut.http.filter.HttpClientFilter
    5. import org.reactivestreams.Publisher
    6. import javax.inject.Singleton
    7. @BasicAuth (1)
    8. @Singleton (2)
    9. class BasicAuthClientFilter : HttpClientFilter {
    10. override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>> {
    11. return chain.proceed(request.basicAuth("user", "pass"))
    12. }
    13. }

    The @BasicAuth annotation is just an example and can be replaced with your own custom annotation.

    1. import io.micronaut.http.annotation.FilterMatcher;
    2. import java.lang.annotation.*;
    3. @FilterMatcher (1)
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Target({ElementType.TYPE, ElementType.PARAMETER})
    7. public @interface BasicAuth {
    8. }
    1. import io.micronaut.http.annotation.FilterMatcher
    2. import java.lang.annotation.*
    3. @FilterMatcher (1)
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Target([ElementType.TYPE, ElementType.PARAMETER])
    7. @interface BasicAuth {
    8. }
    1. import io.micronaut.http.annotation.FilterMatcher
    2. @FilterMatcher (1)
    3. @MustBeDocumented
    4. @Retention(AnnotationRetention.RUNTIME)
    5. @Target(AnnotationTarget.CLASS, AnnotationTarget.VALUE_PARAMETER)
    6. annotation class BasicAuth
    1The only requirement for custom annotations is that the annotation must be present