ConnectionDrainingFilter.java
/* | |
Copyright (c) 2017, Sky UK Ltd All rights reserved. | |
Redistribution and use in source and binary forms, with or without modification, are permitted provided | |
that the following conditions are met: | |
Redistributions of source code must retain the above copyright notice, this list of conditions and the | |
following disclaimer. | |
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and | |
the following disclaimer in the documentation and/or other materials provided with the distribution. | |
Neither the name of feed nor the names of its contributors may be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED | |
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | |
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | |
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | |
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
package uk.sky; | |
import com.google.common.util.concurrent.Uninterruptibles; | |
import io.dropwizard.Configuration; | |
import io.dropwizard.jetty.setup.ServletEnvironment; | |
import io.dropwizard.setup.Environment; | |
import org.eclipse.jetty.util.component.AbstractLifeCycle; | |
import org.eclipse.jetty.util.component.LifeCycle; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import javax.servlet.*; | |
import javax.servlet.http.HttpServletResponse; | |
import javax.servlet.http.HttpServletResponseWrapper; | |
import java.io.IOException; | |
import java.time.Duration; | |
import java.util.EnumSet; | |
import java.util.concurrent.TimeUnit; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
/** | |
* ConnectionDrainingFilter provides a filter for gracefully terminating a dropwizard application, allowing | |
* clients to terminate the connection before the server shuts down. | |
* <p> | |
* When used, it forces Jetty to wait for the provided duration before shutting down. During this time it | |
* attaches a `Connection: close` header on all responses so that clients will close their persistent connections. | |
* <p> | |
* To use, call {@link #addToDropwizard} in your {@link io.dropwizard.Application#run(Configuration, Environment)} method. | |
*/ | |
public final class ConnectionDrainingFilter extends AbstractLifeCycle.AbstractLifeCycleListener implements Filter { | |
private static final Logger LOG = LoggerFactory.getLogger(ConnectionDrainingFilter.class); | |
private static final String PATH_SPEC = "/*"; | |
public static final String FILTER_NAME = "ConnectionDrainingFilter"; | |
private final AtomicBoolean connectionCloseHeaderEnabled = new AtomicBoolean(false); | |
private final Duration drainDuration; | |
private ConnectionDrainingFilter(Duration drainDuration) { | |
this.drainDuration = drainDuration; | |
} | |
/** | |
* Create a new servlet filter. It should be added as a filter, and also registered with Jetty's container lifecycle. | |
* | |
* @param drainDuration amount of time to drain client connections before shutdown. Should be slightly larger than idle timeouts. | |
* @return a new servlet filter and lifecycle listener | |
*/ | |
public static ConnectionDrainingFilter create(Duration drainDuration) { | |
return new ConnectionDrainingFilter(drainDuration); | |
} | |
/** | |
* Adds the filter to your Dropwizard environment, which will register it with Jetty. | |
* | |
* @param environment dropwizard environment | |
* @param drainDuration amount of time to drain client connections before shutdown. Should be slightly larger than idle timeouts. | |
*/ | |
public static void addToDropwizard(Environment environment, io.dropwizard.util.Duration drainDuration) { | |
ConnectionDrainingFilter connectionDrainingFilter = new ConnectionDrainingFilter(Duration.ofNanos(drainDuration.toNanoseconds())); | |
registerFilterWithServletEnvironment(connectionDrainingFilter, environment.servlets()); | |
registerFilterWithServletEnvironment(connectionDrainingFilter, environment.admin()); | |
environment.lifecycle().addLifeCycleListener(connectionDrainingFilter); | |
} | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException { | |
// nada | |
} | |
@Override | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |
if (!connectionCloseHeaderEnabled.get()) { | |
chain.doFilter(request, response); | |
return; | |
} | |
if (!(response instanceof HttpServletResponse)) { | |
LOG.warn("Unable to drain connections safely, unexpected response type %s", response.getClass()); | |
chain.doFilter(request, response); | |
return; | |
} | |
HttpServletResponseWrapper connectionCloseResponse = new HttpServletResponseWrapper((HttpServletResponse) response); | |
connectionCloseResponse.addHeader("Connection", "close"); | |
chain.doFilter(request, connectionCloseResponse); | |
} | |
@Override | |
public void destroy() { | |
// nada | |
} | |
@Override | |
public void lifeCycleStopping(LifeCycle event) { | |
LOG.info("Draining server before shutdown"); | |
connectionCloseHeaderEnabled.set(true); | |
Uninterruptibles.sleepUninterruptibly(drainDuration.toNanos(), TimeUnit.NANOSECONDS); | |
LOG.info("Finished drain, exiting"); | |
} | |
private static void registerFilterWithServletEnvironment(ConnectionDrainingFilter connectionDrainingFilter, ServletEnvironment servletEnvironment) { | |
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST); | |
servletEnvironment.addFilter(FILTER_NAME, connectionDrainingFilter) | |
.addMappingForUrlPatterns(dispatcherTypes, true, PATH_SPEC); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
For Dropwizard 1.0.