Filtros e interceptores de Jersey

Filtros e interceptores de Jersey

1. Introdução

Neste artigo, vamos explicar como os filtros e interceptores funcionam na estrutura de Jersey, bem como as principais diferenças entre eles.

Usaremos Jersey 2 aqui e testaremos nosso aplicativo usando um servidor Tomcat 9.

2. Configuração do aplicativo

Vamos primeiro criar um recurso simples em nosso servidor:

@Path("/greetings")
public class Greetings {

    @GET
    public String getHelloGreeting() {
        return "hello";
    }
}

Além disso, vamos criar a configuração de servidor correspondente para nosso aplicativo:

@ApplicationPath("/*")
public class ServerConfig extends ResourceConfig {

    public ServerConfig() {
        packages("com.example.jersey.server");
    }
}

Se você deseja se aprofundar em como criar uma API com Jersey, você pode verificarthis article.

Você também pode dar uma olhada emour client-focused articlee aprender a criar um cliente Java com Jersey.

3. Filtros

Agora, vamos começar com os filtros.

Simplificando,filters let us modify the properties of requests and responses - por exemplo, cabeçalhos HTTP. Os filtros podem ser aplicados no servidor e no lado do cliente.

Lembre-se de quefilters are always executed, regardless of whether the resource was found or not.

3.1. Implementando um Filtro de Servidor de Solicitação

Vamos começar com os filtros no lado do servidor e criar um filtro de solicitação.

Faremos isso implementando a interfaceContainerRequestFilter e registrando-a comoProvider em nosso servidor:

@Provider
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getLanguage() != null && "EN".equals(ctx.getLanguage()
          .getLanguage())) {

            ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
              .entity("Cannot access")
              .build());
        }
    }
}

Este filtro simples apenas rejeita as solicitações com a linguagem“EN” na solicitação chamando o métodoabortWith().

Como o exemplo mostra, tivemos que implementar apenas um método que recebe o contexto da solicitação, que podemos modificar conforme necessário.

Vamos ter em mente quethis filter is executed after the resource was matched.

Caso desejemos executar um filtro antes da correspondência do recurso,we can use a pre-matching filter by annotating our filter with the @PreMatching annotation:

@Provider
@PreMatching
public class PrematchingRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getMethod().equals("DELETE")) {
            LOG.info("\"Deleting request");
        }
    }
}

Se tentarmos acessar nosso recurso agora, podemos verificar se nosso filtro de pré-correspondência é executado primeiro:

2018-02-25 16:07:27,800 [http-nio-8080-exec-3] INFO  c.b.j.s.f.PrematchingRequestFilter - prematching filter
2018-02-25 16:07:27,816 [http-nio-8080-exec-3] INFO  c.b.j.s.f.RestrictedOperationsRequestFilter - Restricted operations filter

3.2. Implementando um Filtro de Servidor de Resposta

Agora implementaremos um filtro de resposta no lado do servidor que simplesmente adicionará um novo cabeçalho à resposta.

Para fazer isso,our filter has to implement the ContainerResponseFilter interfacee implemente seu único método:

@Provider
public class ResponseServerFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext,
      ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("X-Test", "Filter test");
    }
}

Observe que o parâmetroContainerRequestContext é usado apenas como somente leitura - uma vez que já estamos processando a resposta.

2.3. Implementando um Filtro de Cliente

Vamos trabalhar agora com filtros do lado do cliente. Esses filtros funcionam da mesma maneira que os filtros do servidor, e as interfaces que temos que implementar são muito semelhantes às do lado do servidor.

Vamos ver isso em ação com um filtro que adiciona uma propriedade à solicitação:

@Provider
public class RequestClientFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.setProperty("test", "test client request filter");
    }
}

Vamos também criar um cliente Jersey para testar este filtro:

public class JerseyClient {

    private static String URI_GREETINGS = "http://localhost:8080/jersey/greetings";

    public static String getHelloGreeting() {
        return createClient().target(URI_GREETINGS)
          .request()
          .get(String.class);
    }

    private static Client createClient() {
        ClientConfig config = new ClientConfig();
        config.register(RequestClientFilter.class);

        return ClientBuilder.newClient(config);
    }
}

Observe que precisamos adicionar o filtro à configuração do cliente para registrá-lo.

Por fim, também criaremos um filtro para a resposta no cliente.

Funciona de maneira muito semelhante à do servidor, mas implementando a interfaceClientResponseFilter:

@Provider
public class ResponseClientFilter implements ClientResponseFilter {

    @Override
    public void filter(ClientRequestContext requestContext,
      ClientResponseContext responseContext) throws IOException {
        responseContext.getHeaders()
          .add("X-Test-Client", "Test response client filter");
    }

}

Novamente, oClientRequestContext é para fins somente leitura.

4. Interceptores

Os interceptores estão mais conectados com a organização e a não organização dos corpos das mensagens HTTP contidas nas solicitações e nas respostas. Eles podem ser usados ​​no servidor e no lado do cliente.

Lembre-se de quethey’re executed after the filters and only if a message body is present.

Existem dois tipos de interceptores:ReaderInterceptoreWriterInterceptor, e eles são os mesmos para o servidor e para o cliente.

A seguir, vamos criar outro recurso em nosso servidor - que é acessado via POST e recebe um parâmetro no corpo, para que os interceptores sejam executados ao acessá-lo:

@POST
@Path("/custom")
public Response getCustomGreeting(String name) {
    return Response.status(Status.OK.getStatusCode())
      .build();
}

Também adicionaremos um novo método ao nosso cliente Jersey - para testar este novo recurso:

public static Response getCustomGreeting() {
    return createClient().target(URI_GREETINGS + "/custom")
      .request()
      .post(Entity.text("custom"));
}

4.1. Implementando umReaderInterceptor

Os interceptadores de leitor nos permitem manipular fluxos de entrada, para que possamos usá-los para modificar a solicitação no lado do servidor ou a resposta no lado do cliente.

Vamos criar um interceptor no lado do servidor para escrever uma mensagem personalizada no corpo da solicitação interceptada:

@Provider
public class RequestServerReaderInterceptor implements ReaderInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context)
      throws IOException, WebApplicationException {
        InputStream is = context.getInputStream();
        String body = new BufferedReader(new InputStreamReader(is)).lines()
          .collect(Collectors.joining("\n"));

        context.setInputStream(new ByteArrayInputStream(
          (body + " message added in server reader interceptor").getBytes()));

        return context.proceed();
    }
}

Observe quewe have to call the proceed() methodto call the next interceptor in the chain. Depois que todos os interceptores forem executados, o leitor apropriado do corpo da mensagem será chamado.

3.2. Implementando umWriterInterceptor

Os interceptores do Writer funcionam de maneira muito semelhante aos interceptadores do leitor, mas eles manipulam os fluxos de saída - para que possamos usá-los com a solicitação no lado do cliente ou com a resposta no lado do servidor.

Vamos criar um interceptor escritor para adicionar uma mensagem à solicitação, no lado do cliente:

@Provider
public class RequestClientWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
      throws IOException, WebApplicationException {
        context.getOutputStream()
          .write(("Message added in the writer interceptor in the client side").getBytes());

        context.proceed();
    }
}

Novamente, temos que chamar o métodoproceed() para chamar o próximo interceptor.

Quando todos os interceptores forem executados, o gravador do corpo da mensagem apropriado será chamado.

Don’t forget that you have to register this interceptor in the client configuration, como fizemos antes com o filtro do cliente:

private static Client createClient() {
    ClientConfig config = new ClientConfig();
    config.register(RequestClientFilter.class);
    config.register(RequestWriterInterceptor.class);

    return ClientBuilder.newClient(config);
}

5. Ordem de Execução

Vamos resumir tudo o que vimos até agora em um diagrama que mostra quando os filtros e interceptores são executados durante uma solicitação de um cliente para um servidor:

image

Como podemos ver,the filters are always executed first, and the interceptors are executed right before calling the appropriate message body reader or writer.

Se dermos uma olhada nos filtros e interceptores que criamos, eles serão executados na seguinte ordem:

  1. RequestClientFilter

  2. RequestClientWriterInterceptor

  3. PrematchingRequestFilter

  4. RestrictedOperationsRequestFilter

  5. RequestServerReaderInterceptor

  6. ResponseServerFilter

  7. ResponseClientFilter

Além disso, quando temos vários filtros ou interceptores, podemos especificar a ordem de execução exata anotando-os com a anotação@Priority.

A prioridade é especificada comIntegere classifica os filtros e interceptores em ordem crescente para as solicitações e em ordem decrescente para as respostas.

Vamos adicionar uma prioridade ao nossoRestrictedOperationsRequestFilter:

@Provider
@Priority(Priorities.AUTHORIZATION)
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

Observe que usamos uma prioridade predefinida para fins de autorização.

6. Vinculação de nome

Os filtros e interceptores que vimos até agora são chamados de globais porque são executados para cada solicitação e resposta.

No entanto,they can also be defined to be executed only for specific resource methods, que é chamado de vinculação de nome.

6.1. Ligação estática

Uma maneira de vincular o nome é estaticamente, criando uma anotação específica que será usada no recurso desejado. Esta anotação deve incluir a metanotação@NameBinding.

Vamos criar um em nosso aplicativo:

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloBinding {
}

Depois disso, podemos anotar alguns recursos com esta anotação@HelloBinding:

@GET
@HelloBinding
public String getHelloGreeting() {
    return "hello";
}

Finalmente, iremos anotar um de nossos filtros com esta anotação também, então este filtro será executado apenas para solicitações e respostas que estão acessando o métodogetHelloGreeting():

@Provider
@Priority(Priorities.AUTHORIZATION)
@HelloBinding
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

Lembre-se de que nossoRestrictedOperationsRequestFilter não será mais acionado para o restante dos recursos.

6.2. Ligação dinâmica

Outra maneira de fazer isso é usar uma ligação dinâmica, carregada na configuração durante a inicialização.

Vamos primeiro adicionar outro recurso ao nosso servidor para esta seção:

@GET
@Path("/hi")
public String getHiGreeting() {
    return "hi";
}

Agora, vamos criar uma ligação para este recurso implementando a interfaceDynamicFeature:

@Provider
public class HelloDynamicBinding implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (Greetings.class.equals(resourceInfo.getResourceClass())
          && resourceInfo.getResourceMethod().getName().contains("HiGreeting")) {
            context.register(ResponseServerFilter.class);
        }
    }
}

Neste caso, estamos associando o métodogetHiGreeting() aoResponseServerFilter que criamos antes.

É importante lembrar que tivemos que excluir a anotação@Provider deste filtro, pois agora a estamos configurando por meio deDynamicFeature.

Se não fizermos isso, o filtro será executado duas vezes: uma vez como um filtro global e outra vez como um filtro vinculado ao métodogetHiGreeting().

7. Conclusão

Neste tutorial, nos concentramos em entender como os filtros e interceptores funcionam em Jersey 2 e como podemos usá-los em um aplicativo da web.

Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.