001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.context.request.async; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.concurrent.atomic.AtomicBoolean; 023import java.util.function.Consumer; 024 025import javax.servlet.AsyncContext; 026import javax.servlet.AsyncEvent; 027import javax.servlet.AsyncListener; 028import javax.servlet.http.HttpServletRequest; 029import javax.servlet.http.HttpServletResponse; 030 031import org.springframework.util.Assert; 032import org.springframework.web.context.request.ServletWebRequest; 033 034/** 035 * A Servlet 3.0 implementation of {@link AsyncWebRequest}. 036 * 037 * <p>The servlet and all filters involved in an async request must have async 038 * support enabled using the Servlet API or by adding an 039 * <code><async-supported>true</async-supported></code> element to servlet and filter 040 * declarations in {@code web.xml}. 041 * 042 * @author Rossen Stoyanchev 043 * @since 3.2 044 */ 045public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener { 046 047 private Long timeout; 048 049 private AsyncContext asyncContext; 050 051 private AtomicBoolean asyncCompleted = new AtomicBoolean(false); 052 053 private final List<Runnable> timeoutHandlers = new ArrayList<>(); 054 055 private final List<Consumer<Throwable>> exceptionHandlers = new ArrayList<>(); 056 057 private final List<Runnable> completionHandlers = new ArrayList<>(); 058 059 060 /** 061 * Create a new instance for the given request/response pair. 062 * @param request current HTTP request 063 * @param response current HTTP response 064 */ 065 public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { 066 super(request, response); 067 } 068 069 070 /** 071 * In Servlet 3 async processing, the timeout period begins after the 072 * container processing thread has exited. 073 */ 074 @Override 075 public void setTimeout(Long timeout) { 076 Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress"); 077 this.timeout = timeout; 078 } 079 080 @Override 081 public void addTimeoutHandler(Runnable timeoutHandler) { 082 this.timeoutHandlers.add(timeoutHandler); 083 } 084 085 @Override 086 public void addErrorHandler(Consumer<Throwable> exceptionHandler) { 087 this.exceptionHandlers.add(exceptionHandler); 088 } 089 090 @Override 091 public void addCompletionHandler(Runnable runnable) { 092 this.completionHandlers.add(runnable); 093 } 094 095 @Override 096 public boolean isAsyncStarted() { 097 return (this.asyncContext != null && getRequest().isAsyncStarted()); 098 } 099 100 /** 101 * Whether async request processing has completed. 102 * <p>It is important to avoid use of request and response objects after async 103 * processing has completed. Servlet containers often re-use them. 104 */ 105 @Override 106 public boolean isAsyncComplete() { 107 return this.asyncCompleted.get(); 108 } 109 110 @Override 111 public void startAsync() { 112 Assert.state(getRequest().isAsyncSupported(), 113 "Async support must be enabled on a servlet and for all filters involved " + 114 "in async request processing. This is done in Java code using the Servlet API " + 115 "or by adding \"<async-supported>true</async-supported>\" to servlet and " + 116 "filter declarations in web.xml."); 117 Assert.state(!isAsyncComplete(), "Async processing has already completed"); 118 119 if (isAsyncStarted()) { 120 return; 121 } 122 this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); 123 this.asyncContext.addListener(this); 124 if (this.timeout != null) { 125 this.asyncContext.setTimeout(this.timeout); 126 } 127 } 128 129 @Override 130 public void dispatch() { 131 Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); 132 this.asyncContext.dispatch(); 133 } 134 135 136 // --------------------------------------------------------------------- 137 // Implementation of AsyncListener methods 138 // --------------------------------------------------------------------- 139 140 @Override 141 public void onStartAsync(AsyncEvent event) throws IOException { 142 } 143 144 @Override 145 public void onError(AsyncEvent event) throws IOException { 146 this.exceptionHandlers.forEach(consumer -> consumer.accept(event.getThrowable())); 147 } 148 149 @Override 150 public void onTimeout(AsyncEvent event) throws IOException { 151 this.timeoutHandlers.forEach(Runnable::run); 152 } 153 154 @Override 155 public void onComplete(AsyncEvent event) throws IOException { 156 this.completionHandlers.forEach(Runnable::run); 157 this.asyncContext = null; 158 this.asyncCompleted.set(true); 159 } 160 161}