2.12. RESTEasy 필터 및 인터셉터

Jakarta RESTful Web Services에는 인터셉션을 위한 두 가지 개념, 즉 필터와 인터셉터가 있습니다. 필터는 주로 수신 및 발신 요청 헤더 또는 응답 헤더를 수정하거나 처리하는 데 사용됩니다. 요청 및 응답 처리 전후에 실행됩니다.

2.12.1. 서버 측 필터

서버 측에는 두 가지 유형의 필터가 있습니다. ContainerRequestFiltersContainerResponseFilters. ContainerRequestFilters는 Jakarta RESTful Web Services 리소스 메서드를 호출하기 전에 실행됩니다. ContainerResponseFilters는 Jakarta RESTful Web Services 리소스 메서드가 호출된 후 실행됩니다.

또한 사전 일치 및 사후 일치라는 두 가지 유형의 ContainerRequestFilters 가 있습니다. ContainerRequestFilters@PreMatching 주석으로 지정되고 Jakarta RESTful Web Services 리소스 메서드가 들어오는 HTTP 요청과 일치하기 전에 실행됩니다. Post-matching ContainerRequestFilters@PostMatching 주석으로 지정되며 Jakarta RESTful Web Services 리소스 메서드가 들어오는 HTTP 요청과 일치된 후 실행됩니다.

사전 일치 필터는 요청 속성을 수정하여 특정 리소스 메서드와 일치하는 방식을 변경하는 데 사용됩니다(예: strip .xmlAccept 헤더 추가). ContainerRequestFilters는 ContainerRequestContext.abortWith(응답) 을 호출하여 요청을 중단할 수 있습니다. 예를 들어 사용자 지정 인증 프로토콜을 구현하는 경우 필터를 중단할 수 있습니다.

리소스 클래스 메서드가 실행된 후 Jakarta RESTful Web Services는 모든 ContainerResponseFilters를 실행합니다. 이러한 필터를 사용하면 발신 응답을 마샬링하여 클라이언트에 전송하기 전에 수정할 수 있습니다.

예제: 요청 필터

public class RoleBasedSecurityFilter implements ContainerRequestFilter {
  protected String[] rolesAllowed;
  protected boolean denyAll;
  protected boolean permitAll;

  public RoleBasedSecurityFilter(String[] rolesAllowed, boolean denyAll, boolean permitAll) {
    this.rolesAllowed = rolesAllowed;
    this.denyAll = denyAll;
    this.permitAll = permitAll;
  }

  @Override
  public void filter(ContainerRequestContext requestContext) throws IOException  {
    if (denyAll) {
       requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build());
       return;
    }
    if (permitAll) return;
    if (rolesAllowed != null) {
       SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class);
       if (context != null) {
          for (String role : rolesAllowed) {
             if (context.isUserInRole(role)) return;
          }
          requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build());
          return;
       }
    }
    return;
  }
}

예제: 응답 필터

public class CacheControlFilter implements ContainerResponseFilter {
   private int maxAge;

   public CacheControlFilter(int maxAge) {
      this.maxAge = maxAge;
   }

   public void filter(ContainerRequestContext req, ContainerResponseContext res)
           throws IOException {
      if (req.getMethod().equals("GET")) {
         CacheControl cc = new CacheControl();
         cc.setMaxAge(this.maxAge);
         res.getHeaders().add("Cache-Control", cc);
      }
   }
}

2.12.2. 클라이언트측 필터

클라이언트 측 필터에 대한 자세한 내용은 이 가이드의 Jakarta RESTful Web Services Client API 섹션에서 확인할 수 있습니다.

2.12.3. RESTEasy 인터셉터

2.12.3.1. Jakarta RESTful 웹 서비스 호출 인터셉트

RESTEasy는 Jakarta RESTful Web Services 호출을 인터셉터라는 리스너와 유사한 개체를 통해 라우팅할 수 있습니다.

필터는 요청 또는 응답 헤더를 수정하는 반면 인터셉터는 메시지 본문을 처리합니다. 인터셉터는 해당 리더 또는 작성자와 동일한 호출 스택에서 실행됩니다. ReaderInterceptors는 MessageBodyReaders 실행을 래핑합니다. WriterInterceptors는 MessageBodyWriters 실행을 래핑합니다. 특정 콘텐츠 인코딩을 구현하는 데 사용할 수 있습니다. 디지털 서명을 생성하거나 마샬링 전후에 Java 개체 모델을 게시하거나 사전 처리하는 데 사용할 수 있습니다.

ReaderInterceptorsWriterInterceptors 는 서버 또는 클라이언트 측에서 사용할 수 있습니다. 이 주석에는 @Provider와 @ ServerInterceptor 또는 @ClientInterceptor 가 주석이 추가되어 RESTEasy에서 인터셉터 목록에 추가할지 여부를 알 수 있습니다.

이러한 인터셉터는 MessageBodyReader.readFrom() 또는 MessageBodyWriter.writeTo() 의 호출을 중심으로 줄입니다. 출력 또는 입력 스트림을 래핑하는 데 사용할 수 있습니다.

예제: 인터셉터

@Provider
public class BookReaderInterceptor implements ReaderInterceptor {
    @Inject private Logger log;
    @Override
    @ReaderInterceptorBinding
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
        log.info("*** Intercepting call in BookReaderInterceptor.aroundReadFrom()");
        VisitList.add(this);
        Object result = context.proceed();
        log.info("*** Back from intercepting call in BookReaderInterceptor.aroundReadFrom()"); return result;
    }
}

인터셉터와 MessageBodyReader 또는 Writer 는 하나의 큰 Java 호출 스택에서 호출됩니다. ReaderInterceptorContext.proceed() 또는 WriterInterceptorContext.proceed() 는 다음 인터셉터로 이동하기 위해 호출되거나 더 이상 호출할 인터셉터가 없는 경우 MessageBodyReader 또는 MessageBody WriterreadFrom() 또는 writeTo() 메서드입니다. 이 래핑을 사용하면 Reader 또는 Writer 로 돌아가기 전에 객체를 수정한 다음, proceed() 가 반환된 후 정리할 수 있습니다.

아래 예는 응답에 헤더 값을 추가하는 서버 측 인터셉터입니다.

@Provider
public class BookWriterInterceptor implements WriterInterceptor {
   @Inject private Logger log;

   @Override
   @WriterInterceptorBinding
   public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
      log.info("*** Intercepting call in BookWriterInterceptor.aroundWriteTo()");
      VisitList.add(this);
      context.proceed();
      log.info("*** Back from intercepting call in BookWriterInterceptor.aroundWriteTo()");
   }
}

2.12.3.2. 인터셉터 등록

애플리케이션에 RESTEasy Jakarta RESTful Web Services 인터셉터를 등록하려면 context-param 요소의 resteasy .providers 매개변수 아래에 web. xml 파일에 나열하거나 클래스 또는 Application.getClasses() 또는 Application.getSingletons() 메서드의 개체로 반환합니다.

<context-param>
    <param-name>resteasy.providers</param-name>
    <param-value>my.app.CustomInterceptor</paramvalue>
</context-param>
package org.jboss.resteasy.example;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class MyApp extends Application {

  public java.util.Set<java.lang.Class<?>> getClasses() {
    Set<Class<?>> resources = new HashSet<Class<?>>();
    resources.add(MyResource.class);
    resources.add(MyProvider.class);
    return resources;
  }
}
package org.jboss.resteasy.example;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class MyApp extends Application {

    protected Set<Object> singletons = new HashSet<Object>();

    public MyApp() {
        singletons.add(new MyResource());
        singletons.add(new MyProvider());
    }

    @Override
    public Set<Object> getSingletons() {
        return singletons;
    }
}

2.12.4. GZIP 압축 및 압축 해제

RESTEasy는 GZIP 압축 및 압축 해제를 지원합니다. GZIP 압축 해제를 지원하기 위해 클라이언트 프레임워크 또는 Jakarta RESTful Web Services 서비스는 gzipContent-Encoding 를 사용하여 메시지 본문을 자동으로 압축 해제하며 Accept-Encoding 헤더를 gzip으로 자동 설정할 수 있습니다. 이 헤더를 수동으로 설정하지 않도록 해제합니다. GZIP 압축을 지원하기 위해 RESTEasy는 클라이언트 프레임워크가 요청을 보내거나 서버가 gzip 으로 설정된 Content-Encoding 헤더로 응답을 보내는 경우 나가는 메시지를 압축합니다. @org.jboss.resteasy.annotation.GZIP 주석을 사용하여 Content-Encoding 헤더를 설정할 수 있습니다.

다음 예제에서는 나가는 메시지 본문 순서에 gzip 압축 태그가 지정됩니다.

예제: GZIP 압축

@Path("/")
public interface MyProxy {

   @Consumes("application/xml")
   @PUT
   public void put(@GZIP Order order);
}

예제: GZIP 압축 태그 지정 서버 응답

@Path("/")
public class MyService {

   @GET
   @Produces("application/xml")
   @GZIP
   public String getData() {...}
}

2.12.4.1. GZIP 압축 및 압축 해제 구성

참고

RESTEasy는 크기가 크지만 공격자가 압축하여 서버로 전송된 엔터티의 압축 해제를 방지하기 위해 기본적으로 GZIP 압축을 비활성화하고 압축 해제합니다.

GZIP 압축 및 압축 해제와 관련된 세 가지 인터셉터가 있습니다.

  • org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor: Content-Encoding 헤더가 있고 값이 gzip 인 경우GZIPDecodingInterceptor 는 메시지 본문의 압축을 해제하는 InputStream 을 설치합니다.
  • org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor: Content-Encoding 헤더가 있고 값이 gzip 인 경우GZIPEncodingInterceptor 는 메시지 본문을 압축하는 OutputStream 을 설치합니다.
  • org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter: Accept-Encoding 헤더가 없는 경우 AcceptEncodingGZIPFiltergzip 값을 사용하여 Accept-Encoding 헤더를 추가하고 deflate을 추가합니다. Accept-Encoding 헤더가 있지만 gzip 이 포함되지 않은 경우AcceptEncodingGZIPFilter 인터셉터는 gzip 값을 추가합니다.

    참고

    GZIP 압축 또는 압축 해제를 활성화해도 AcceptEncodingGZIPFilter 인터셉터가 존재하지 않습니다.

GZIP 압축 해제를 사용하면 GZIPDecodingInterceptor 가 압축된 메시지 본문에서 추출할 수 있는 바이트 수에 대한 상한을 설정합니다. 기본 제한은 10,000,000 입니다.

2.12.4.2. 서버 측 GZIP 구성

클래스 경로의 javax.ws.rs.ext.Providers 파일에 클래스 이름을 포함하여 인터셉터를 활성화할 수 있습니다. 삭제된 파일의 상한은 웹 애플리케이션 컨텍스트 매개 변수 resteasy.gzip.max.input 을 사용하여 설정됩니다. 서버 측에서 이 제한을 초과하는 경우 GZIPDecodingInterceptor 는 상태 413 - Request Entity Too Large 및 상한값을 지정하는 메시지를 반환합니다.

2.12.4.2.1. 클라이언트 측 GZIP 구성

클라이언트 또는 WebTarget 과 같이 에 등록하여 GZIP 인터셉터를 활성화할 수 있습니다. 예를 들면 다음과 같습니다.

Client client = new ResteasyClientBuilder() // Activate gzip compression on client:
    .register(AcceptEncodingGZIPFilter.class)
    .register(GZIPDecodingInterceptor.class)
    .register(GZIPEncodingInterceptor.class)
    .build();

특정 값을 사용하여 GZIPDecodingInterceptor 인스턴스를 생성하여 분리된 파일에 상한을 구성할 수 있습니다.

Client client = new ResteasyClientBuilder() // Activate gzip compression on client:
    .register(AcceptEncodingGZIPFilter.class)
    .register(new GZIPDecodingInterceptor(256))
    .register(GZIPEncodingInterceptor.class)
    .build();

클라이언트 측에서 상한을 초과하면 GZIPDecodingInterceptor 에서 상한값을 지정하는 메시지와 함께 ProcessingException 이 발생합니다.

2.12.5. 리소스별 방법 필터 및 인터셉터

필터 또는 인터셉터가 특정 리소스 메서드에서만 실행되도록 하는 경우도 있습니다. 다음 두 가지 방법으로 이 작업을 수행할 수 있습니다.

DynamicFeature 인터페이스 구현

DynamicFeature 인터페이스에는 배포된 모든 자카르타 RESTful 웹 서비스 메서드에 대해 호출되는 콜백 메서드 , configure(ResourceInfo resourceInfo, FeatureContext context) 가 포함됩니다. ResourceInfo 매개변수에는 배포 중인 현재 Jakarta RESTful Web Services 메서드에 대한 정보가 포함되어 있습니다. FeatureContext구성 가능한 인터페이스의 확장입니다. 이 매개변수의 register() 메서드를 사용하여 이 메서드에 할당할 필터 및 인터셉터를 바인딩할 수 있습니다.

예제: DynamicFeature 인터페이스 사용

@Provider
public class AnimalTypeFeature implements DynamicFeature {
    @Override
    public void configure(ResourceInfo info, FeatureContext context) {
        if (info.getResourceMethod().getAnnotation(GET.class) != null)
            AnimalFilter filter = new AnimalFilter();
            context.register(filter);
        }
    }
}

위의 예에서 scheduling TypeFeature 를 사용하여 등록한 공급자는 인터페이스 중 하나를 구현해야 합니다. 이 예제는 다음 인터페이스 중 하나를 구현해야 하는 공급자 Filter 를 등록합니다. ContainerRequestFilter,ContainerResponseFilter,ReaderInterceptor,WriterInterceptor 또는 기능. 이 경우에는 GET 주석 을 사용하여 주석이 추가된 모든 리소스 메서드에 적용됩니다. 자세한 내용은 DynamicFeature 문서를 참조하십시오.

@NameBinding Annotation 사용

@NameBinding 은 Jakarta Contexts 및 Dependency Injection 인터셉터와 유사하게 작동합니다. @NameBinding 으로 사용자 정의 주석에 주석을 추가한 다음 필터 및 리소스 메서드에 해당 사용자 정의 주석을 적용합니다.

예제: @NameBinding 사용

@NameBinding
public @interface DoIt {}

@DoIt
public class MyFilter implements ContainerRequestFilter {...}

@Path("/root")
public class MyResource {

   @GET
   @DoIt
   public String get() {...}
}

자세한 내용은 NameBinding Documentation 을 참조하십시오.

2.12.6. 순서

filter 또는 interceptor 클래스에서 @Priority 주석을 사용하여 순서가 수행됩니다.

2.12.7. 필터 및 인터셉터를 사용한 예외 처리

필터 또는 인터셉터와 관련된 예외는 클라이언트 측 또는 서버 측에서 발생할 수 있습니다. 클라이언트 측에는 처리해야 하는 두 가지 유형의 예외, javax.ws.rs.client.ProcessingException 및 javax. ws.rs.client.ResponseProcessingException 이 있습니다. 서버에 요청을 보내기 전에 오류가 있는 경우 클라이언트 측에서 javax.ws.rs.ProcessingException 이 발생합니다. 서버에서 클라이언트에서 수신한 응답을 처리하는 중에 오류가 있는 경우 클라이언트 측에서 javax.ws.rs.client.ResponseProcessingException 이 발생합니다.

서버 측에서 필터 또는 인터셉터에 의해 throw된 예외는 Jakarta RESTful Web Services 메서드에서 throw된 다른 예외와 동일한 방식으로 처리됩니다. 이 예외는 예외가 throw되는 경우 ExceptionMapper 를 찾으려고 합니다. Jakarta RESTful Web Services 메서드에서 예외 처리 방법에 대한 자세한 내용은 Exception Handling(예외 처리) 섹션에서 확인할 수 있습니다.