001/* 002 * Copyright 2002-2015 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 javax.servlet.AsyncContext; 024import javax.servlet.AsyncEvent; 025import javax.servlet.AsyncListener; 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletResponse; 028 029import org.springframework.util.Assert; 030import org.springframework.web.context.request.ServletWebRequest; 031 032/** 033 * A Servlet 3.0 implementation of {@link AsyncWebRequest}. 034 * 035 * <p>The servlet and all filters involved in an async request must have async 036 * support enabled using the Servlet API or by adding an 037 * {@code <async-supported>true</async-supported>} element to servlet and filter 038 * declarations in {@code web.xml}. 039 * 040 * @author Rossen Stoyanchev 041 * @since 3.2 042 */ 043public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener { 044 045 private Long timeout; 046 047 private AsyncContext asyncContext; 048 049 private AtomicBoolean asyncCompleted = new AtomicBoolean(false); 050 051 private final List<Runnable> timeoutHandlers = new ArrayList<Runnable>(); 052 053 private ErrorHandler errorHandler; 054 055 private final List<Runnable> completionHandlers = new ArrayList<Runnable>(); 056 057 058 /** 059 * Create a new instance for the given request/response pair. 060 * @param request current HTTP request 061 * @param response current HTTP response 062 */ 063 public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { 064 super(request, response); 065 } 066 067 068 /** 069 * In Servlet 3 async processing, the timeout period begins after the 070 * container processing thread has exited. 071 */ 072 @Override 073 public void setTimeout(Long timeout) { 074 Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress"); 075 this.timeout = timeout; 076 } 077 078 @Override 079 public void addTimeoutHandler(Runnable timeoutHandler) { 080 this.timeoutHandlers.add(timeoutHandler); 081 } 082 083 void setErrorHandler(ErrorHandler errorHandler) { 084 this.errorHandler = errorHandler; 085 } 086 087 @Override 088 public void addCompletionHandler(Runnable runnable) { 089 this.completionHandlers.add(runnable); 090 } 091 092 @Override 093 public boolean isAsyncStarted() { 094 return (this.asyncContext != null && getRequest().isAsyncStarted()); 095 } 096 097 /** 098 * Whether async request processing has completed. 099 * <p>It is important to avoid use of request and response objects after async 100 * processing has completed. Servlet containers often re-use them. 101 */ 102 @Override 103 public boolean isAsyncComplete() { 104 return this.asyncCompleted.get(); 105 } 106 107 @Override 108 public void startAsync() { 109 Assert.state(getRequest().isAsyncSupported(), 110 "Async support must be enabled on a servlet and for all filters involved " + 111 "in async request processing. This is done in Java code using the Servlet API " + 112 "or by adding \"<async-supported>true</async-supported>\" to servlet and " + 113 "filter declarations in web.xml."); 114 Assert.state(!isAsyncComplete(), "Async processing has already completed"); 115 116 if (isAsyncStarted()) { 117 return; 118 } 119 this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); 120 this.asyncContext.addListener(this); 121 if (this.timeout != null) { 122 this.asyncContext.setTimeout(this.timeout); 123 } 124 } 125 126 @Override 127 public void dispatch() { 128 Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); 129 this.asyncContext.dispatch(); 130 } 131 132 133 // --------------------------------------------------------------------- 134 // Implementation of AsyncListener methods 135 // --------------------------------------------------------------------- 136 137 @Override 138 public void onStartAsync(AsyncEvent event) throws IOException { 139 } 140 141 @Override 142 public void onError(AsyncEvent event) throws IOException { 143 if (this.errorHandler != null) { 144 this.errorHandler.handle(event.getThrowable()); 145 } 146 } 147 148 @Override 149 public void onTimeout(AsyncEvent event) throws IOException { 150 for (Runnable handler : this.timeoutHandlers) { 151 handler.run(); 152 } 153 } 154 155 @Override 156 public void onComplete(AsyncEvent event) throws IOException { 157 for (Runnable handler : this.completionHandlers) { 158 handler.run(); 159 } 160 this.asyncContext = null; 161 this.asyncCompleted.set(true); 162 } 163 164 165 interface ErrorHandler { 166 167 void handle(Throwable ex); 168 169 } 170 171}