[스프링 Web Servlet 공식문서 읽기]1. Spring MVC (6) - Asynchronous Requests

ybw·2021년 12월 19일

1.6 Asynchronous Requests

Spring MVC는 Servlet 3.0 비동기 요청 처리와 광범위하게 통합됩니다.

컨트롤러 메서드의 DeferredResultCallable 반환 값은 단일 비동기 반환 값에 대한 기본 지원을 제공합니다.

컨트롤러는 SSEraw 데이터를 포함하여 여러 값을 스트리밍할 수 있습니다.

컨트롤러는 reactive 클라이언트를 사용하고 응답 처리를 위해 reactive types을 반환할 수 있습니다.

1.6.1 DeferredResult

서블릿 컨테이너에서 비동기식 요청 처리 기능이 활성화되면 다음 예제와 같이 컨트롤러 메서드는 지원되는 컨트롤러 메서드 반환 값을 DeferredResult로 래핑할 수 있습니다.

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(result);

컨트롤러는 외부 이벤트(JMS 메시지), 예약된 작업 또는 기타 이벤트에 대한 응답으로 다른 스레드에서 비동기적으로 반환 값을 생성할 수 있습니다.

1.6.2 Callable

컨트롤러는 다음 예제와 같이 지원되는 모든 반환 값을 java.util.concurrent.Callable로 래핑할 수 있습니다.

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}

그런 다음 구성된 TaskExecutor를 통해 지정된 작업을 실행하여 반환 값을 얻을 수 있습니다.

1.6.3 Processing

다음은 Servlet 비동기 요청 처리에 대한 매우 간결한 개요입니다.

  • ServletRequestrequest.startAsync()를 호출하여 비동기 모드로 설정할 수 있습니다. 그렇게 하는 main effect는 Servlet(및 모든 필터)이 종료될 수 있지만 나중에 처리가 완료될 수 있도록 응답이 열린 상태로 유지된다는 것입니다.

  • request.startAsync()를 호출하면 비동기 처리에 대한 추가 제어에 사용할 수 있는 AsyncContext가 반환됩니다. 예를 들어, 애플리케이션이 서블릿 컨테이너 스레드에서 요청 처리를 재개할 수 있다는 점을 제외하면 서블릿 API에서 전달과 유사한 디스패치 메소드를 제공합니다.

  • ServletRequest는 초기 요청 처리, 비동기 디스패치, 전달 및 기타 디스패처 유형을 구별하는 데 사용할 수 있는 현재 DispatcherType에 대한 액세스를 제공합니다.

DeferredResult 처리는 다음과 같이 작동합니다.

  • 컨트롤러는 DeferredResult를 반환하고 액세스할 수 있는 메모리 내 대기열 또는 목록에 저장합니다.

  • Spring MVC는 request.startAsync()를 호출합니다.

  • 한편 DispatcherServlet 및 구성된 모든 필터는 요청 처리 스레드를 종료하지만 응답은 열린 상태로 유지됩니다.

  • 애플리케이션은 일부 스레드에서 DeferredResult를 설정하고 Spring MVC는 요청을 서블릿 컨테이너로 다시 전달합니다.

  • DispatcherServlet이 다시 호출되고 비동기적으로 생성된 반환 값으로 처리가 재개됩니다.

Callable 처리는 다음과 같이 작동합니다.

  • 컨트롤러는 Callable을 반환합니다.

  • Spring MVC는 request.startAsync()를 호출하고 별도의 스레드에서 처리하기 위해 TaskExecutorCallable을 제출합니다.

  • 한편 DispatcherServlet과 모든 필터는 Servlet 컨테이너 스레드를 종료하지만 응답은 열린 상태로 유지됩니다.

  • 결국 Callable은 결과를 생성하고 Spring MVC는 처리를 완료하기 위해 요청을 Servlet 컨테이너로 다시 발송합니다.

  • DispatcherServlet이 다시 호출되고 Callable에서 비동기적으로 생성된 반환 값으로 처리가 재개됩니다.

추가 배경 및 컨텍스트에 대해서는 Spring MVC 3.2에서 비동기 요청 처리 지원을 도입한 블로그 게시물을 읽을 수도 있습니다.

Exception Handling

DeferredResult를 사용할 때 예외와 함께 setResult 또는 setErrorResult를 호출할지 여부를 선택할 수 있습니다. 두 경우 모두 Spring MVC는 처리를 완료하기 위해 요청을 서블릿 컨테이너로 다시 발송합니다. 그런 다음 컨트롤러 메서드가 지정된 값을 반환하거나 지정된 예외를 생성한 것처럼 처리됩니다. 그런 다음 예외는 일반 예외 처리 메커니즘(예: @ExceptionHandler 메서드 호출)을 거칩니다.

Callable을 사용하면 유사한 처리 로직이 발생하는데, 주된 차이점은 Callable에서 결과가 반환되거나 예외가 발생한다는 것입니다.

Interception

HandlerInterceptor 인스턴스는 비동기 처리를 시작하는 초기 요청(postHandleafterCompletion 대신)에서 afterConcurrentHandlingStarted 콜백을 수신하기 위해 AsyncHandlerInterceptor type이 될 수 있습니다.

HandlerInterceptor 구현은 CallableProcessingInterceptor 또는 DeferredResultProcessingInterceptor를 등록하여 비동기 요청의 수명 주기와 더 깊이 통합할 수도 있습니다(예: 타임아웃 이벤트 처리). 자세한 내용은 AsyncHandlerInterceptor를 참조하세요.

DeferredResultonTimeout(Runnable)onCompletion(Runnable) 콜백을 제공합니다. 자세한 내용은 DeferredResult의 javadoc을 참조하십시오. Callable은 시간 초과 및 완료 콜백에 대한 추가 메서드를 노출하는 WebAsyncTask를 대체할 수 있습니다.

Compared to WebFlux

Servlet API는 원래 Filter-Servlet 체인을 통해 단일 패스를 만들기 위해 만들어졌습니다. Servlet 3.0에 추가된 비동기식 요청 처리를 통해 애플리케이션은 필터-서블릿 체인을 종료하지만 추가 처리를 위해 응답을 열어 둡니다. Spring MVC 비동기 지원은 해당 메커니즘을 중심으로 구축됩니다. 컨트롤러가 DeferredResult를 반환하면 필터-서블릿 체인이 종료되고 서블릿 컨테이너 스레드가 해제됩니다. 나중에 DeferredResult가 설정되면 ASYNC 디스패치(동일한 URL로)가 이루어지며 그 동안 컨트롤러가 다시 매핑되지만 호출하는 대신 DeferredResult 값이 사용되어(컨트롤러가 반환한 것처럼) 처리를 재개합니다.

대조적으로 Spring WebFlux는 Servlet API를 기반으로 하지 않으며 설계상 비동기식이기 때문에 이러한 비동기식 요청 처리 기능이 필요하지 않습니다. 비동기 처리는 모든 프레임워크 contracts에 내장되어 있으며 요청 처리의 모든 단계를 통해 본질적으로 지원됩니다.

프로그래밍 모델 관점에서 Spring MVC와 Spring WebFlux는 모두 컨트롤러 메서드의 반환 값으로 비동기 및 Ractive Types을 지원합니다. Spring MVC는 반응 배압을 포함하여 스트리밍도 지원합니다. 그러나 응답에 대한 개별 쓰기는 non-blocking I/O에 의존하고 각 쓰기에 대해 추가 스레드가 필요하지 않은 WebFlux와 달리 차단 상태를 유지하고 별도의 스레드에서 수행됩니다.

또 다른 근본적인 차이점은 Spring MVC는 컨트롤러 메서드 인수(예: @RequestBody, @RequestPart 및 기타)에서 비동기 또는 반응 유형을 지원하지 않으며 모델 속성으로 비동기 및 반응 유형을 명시적으로 지원하지 않는다는 것입니다. Spring WebFlux는 이 모든 것을 지원합니다.

1.6.4 HTTP Streaming

단일 비동기 반환 값에 대해 DeferredResultCallable을 사용할 수 있습니다. 여러 비동기 값을 생성하고 해당 값을 응답에 기록하려면 어떻게 해야 합니까? 이 섹션에서는 그렇게 하는 방법에 대해 설명합니다.

Objects

다음 예제와 같이 ResponseBodyEmitter 반환 값을 사용하여 각 객체가 HttpMessageConverter로 직렬화되고 응답에 기록되는 객체 스트림을 생성할 수 있습니다.

@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

ResponseBodyEmitterResponseEntity의 본문으로 사용하여 응답의 상태와 헤더를 사용자 지정할 수도 있습니다.

emitterIOException을 던지면(예를 들어, 원격 클라이언트가 사라진 경우) 애플리케이션은 연결을 정리할 책임이 없으며 emitter.complete 또는 emitter.completeWithError를 호출해서는 안 됩니다. 대신 서블릿 컨테이너는 AsyncListener 오류 알림을 자동으로 시작하며, 여기서 Spring MVC는 completeWithError 호출을 수행합니다. 이 호출은 차례로 Spring MVC가 구성된 예외 resolvers를 호출하고 요청을 완료하는 동안 애플리케이션에 대한 최종 ASYNC 디스패치를 수행합니다.

SSE

SseEmitter(ResponseBodyEmitter의 하위 클래스)는 서버에서 보낸 이벤트가 W3C SSE 사양에 따라 형식이 지정되는 Server-Sent Events에 대한 지원을 제공합니다. 컨트롤러에서 SSE 스트림을 생성하려면 다음 예제와 같이 SseEmitter를 반환합니다.

@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

SSE는 브라우저로 스트리밍하기 위한 기본 옵션이지만 Internet Explorer는 서버 전송 이벤트를 지원하지 않습니다. 다양한 브라우저를 대상으로 하는 SockJS 폴백 전송(SSE 포함)과 함께 Spring의 WebSocket 메시징을 사용하는 것을 고려하십시오.

예외 처리에 대한 참고 사항은 이전 섹션도 참조하십시오.

Raw Data

때로는 메시지 변환을 우회하고 응답 OutputStream으로 직접 스트리밍하는 것이 유용합니다(예: 파일 다운로드의 경우). 다음 예제와 같이 StreamingResponseBody 반환 값 유형을 사용하여 이를 수행할 수 있습니다.

@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

StreamingResponseBodyResponseEntity의 본문으로 사용하여 응답의 상태와 헤더를 사용자 지정할 수 있습니다.

1.6.5 Reactive Types

Spring MVC는 컨트롤러에서 반응형 클라이언트 라이브러리 사용을 지원합니다(WebFlux 섹션에서 Reactive Libraries도 읽어보세요). 여기에는 spring-webflux 및 Spring Data 반응 데이터 저장소와 같은 기타의 WebClient가 포함됩니다. 이러한 시나리오에서는 컨트롤러 메서드에서 reactive types을 반환할 수 있는 것이 편리합니다.

반응형 반환 값은 다음과 같이 처리됩니다.

  • 단일 값 약속은 DeferredResult를 사용하는 것과 유사하게 적용됩니다. 예로는 Mono(Reactor) 또는 Single(RxJava)이 있습니다.

  • 스트리밍 미디어 유형(예: application/x-ndjson 또는 text/event-stream)이 있는 다중 값 스트림은 ResponseBodyEmitter 또는 SseEmitter를 사용하는 것과 유사하게 적용됩니다. 예를 들면 Flux(Reactor) 또는 Observable(RxJava)이 있습니다. 응용 프로그램은 Flux<ServerSentEvent> 또는 Observable<ServerSentEvent>를 반환할 수도 있습니다.

  • 다른 미디어 유형(예: application/json)이 있는 다중 값 스트림은 DeferredResult<List<?>>를 사용하는 것과 유사하게 적용됩니다.

Spring MVC는 Spring-coreReactiveAdapterRegistry를 통해 Reactor 및 RxJava를 지원하므로 여러 reactive 라이브러리에서 적응할 수 있습니다.

응답으로 스트리밍하기 위해 반응적인 back pressure이 지원되지만 응답에 대한 쓰기는 여전히 차단되고 구성된 TaskExecutor를 통해 별도의 스레드에서 실행되어 업스트림 소스(예: WebClient에서 반환된 Flux)를 차단하지 않도록 합니다. 기본적으로 SimpleAsyncTaskExecutor는 쓰기 차단에 사용되지만 로드 시에는 적합하지 않습니다. 반응형으로 스트리밍하려는 경우 MVC 구성을 사용하여 작업 실행기를 구성해야 합니다.

1.6.6 Disconnects

Servlet API는 원격 클라이언트가 사라질 때 알림을 제공하지 않습니다. 따라서 응답으로 스트리밍하는 동안 SseEmitter 또는 reactive types을 통해 클라이언트 연결이 끊어지면 쓰기가 실패하므로 주기적으로 데이터를 보내는 것이 중요합니다. 보내기는 빈(주석 전용) SSE 이벤트 또는 상대방이 하트비트로 해석하고 무시해야 하는 기타 데이터의 형태를 취할 수 있습니다.

또는 내장된 하트비트 메커니즘이 있는 웹 메시징 솔루션(예: WebSocket을 통한 STOMP 또는 SockJS가 있는 WebSocket)을 사용하는 것이 좋습니다.

1.6.7 Configuration

비동기 요청 처리 기능은 서블릿 컨테이너 수준에서 활성화되어야 합니다. MVC 구성은 또한 비동기 요청에 대한 여러 옵션을 노출합니다.

Servlet Container

필터 및 서블릿 선언에는 비동기 요청 처리를 활성화하기 위해 true로 설정해야 하는 asyncSupported 플래그가 있습니다. 또한 ASYNC javax.servlet.DispatchType을 처리하도록 필터 매핑을 선언해야 합니다.

Java 구성에서 AbstractAnnotationConfigDispatcherServletInitializer를 사용하여 Servlet 컨테이너를 초기화하면 자동으로 수행됩니다.

web.xml 구성에서 DispatcherServletFilter 선언에 <async-supported>true</async-supported>를 추가하고 필터 매핑에 <dispatcher>ASYNC</dispatcher>를 추가할 수 있습니다.

Spring MVC

MVC 구성은 비동기 요청 처리와 관련된 다음 옵션을 노출합니다.

  • Java 구성: WebMvcConfigurer에서 configureAsyncSupport 콜백을 사용합니다.

  • XML 네임스페이스: <mvc:annotation-driven> 아래에 <async-support> 요소를 사용합니다.

다음을 구성할 수 있습니다.

  • 설정되지 않은 경우 기본 서블릿 컨테이너에 따라 달라지는 비동기 요청의 기본 시간 초과 값입니다.

  • AsyncTaskExecutorReactive Types으로 스트리밍할 때 쓰기를 차단하고 컨트롤러 메서드에서 반환된 Callable 인스턴스를 실행하는 데 사용합니다. 기본적으로 SimpleAsyncTaskExecutor이기 때문에 반응형으로 스트리밍하거나 Callable을 반환하는 컨트롤러 메서드가 있는 경우 이 속성을 구성하는 것이 좋습니다.

  • DeferredResultProcessingInterceptor 구현 및 CallableProcessingInterceptor 구현.

DeferredResult, ResponseBodyEmitterSseEmitter에서 기본 시간 초과 값을 설정할 수도 있습니다. Callable의 경우, WebAsyncTask를 사용하여 시간 초과 값을 제공할 수 있습니다.

profile
유병우

0개의 댓글