Merge branch 'jetty-9.3.x' of github.com:eclipse/jetty.project into jetty-9.3.x

This commit is contained in:
Joakim Erdfelt 2016-03-08 14:32:13 -07:00
commit 63f9224009
5 changed files with 507 additions and 606 deletions

View File

@ -28,7 +28,6 @@ import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@ -49,16 +48,16 @@ public abstract class SslBytesTest
public static class TLSRecord
{
private final SslBytesServerTest.TLSRecord.Type type;
private final Type type;
private final byte[] bytes;
public TLSRecord(SslBytesServerTest.TLSRecord.Type type, byte[] bytes)
public TLSRecord(Type type, byte[] bytes)
{
this.type = type;
this.bytes = bytes;
}
public SslBytesServerTest.TLSRecord.Type getType()
public Type getType()
{
return type;
}
@ -80,15 +79,15 @@ public abstract class SslBytesTest
private int code;
private Type(int code)
Type(int code)
{
this.code = code;
SslBytesServerTest.TLSRecord.Type.Mapper.codes.put(this.code, this);
Mapper.codes.put(this.code, this);
}
public static SslBytesServerTest.TLSRecord.Type from(int code)
public static Type from(int code)
{
SslBytesServerTest.TLSRecord.Type result = SslBytesServerTest.TLSRecord.Type.Mapper.codes.get(code);
Type result = Mapper.codes.get(code);
if (result == null)
throw new IllegalArgumentException("Invalid TLSRecord.Type " + code);
return result;
@ -96,7 +95,7 @@ public abstract class SslBytesTest
private static class Mapper
{
private static final Map<Integer, SslBytesServerTest.TLSRecord.Type> codes = new HashMap<>();
private static final Map<Integer, Type> codes = new HashMap<>();
}
}
}
@ -218,7 +217,7 @@ public abstract class SslBytesTest
}
}
private TLSRecord read(SslBytesServerTest.TLSRecord.Type type, InputStream input, byte[] bytes, int offset, int length) throws IOException
private TLSRecord read(TLSRecord.Type type, InputStream input, byte[] bytes, int offset, int length) throws IOException
{
while (length > 0)
{
@ -291,57 +290,51 @@ public abstract class SslBytesTest
}
}
public SslBytesServerTest.SimpleProxy.AutomaticFlow startAutomaticFlow() throws InterruptedException
public SslBytesTest.SimpleProxy.AutomaticFlow startAutomaticFlow() throws InterruptedException
{
final CountDownLatch startLatch = new CountDownLatch(2);
final CountDownLatch stopLatch = new CountDownLatch(2);
Future<Object> clientToServer = threadPool.submit(new Callable<Object>()
Future<Object> clientToServer = threadPool.submit(() ->
{
public Object call() throws Exception
startLatch.countDown();
logger.debug("Automatic flow C --> S started");
try
{
startLatch.countDown();
logger.debug("Automatic flow C --> S started");
try
while (true)
{
while (true)
{
flushToServer(readFromClient(), 0);
}
}
catch (InterruptedIOException x)
{
return null;
}
finally
{
stopLatch.countDown();
logger.debug("Automatic flow C --> S finished");
flushToServer(readFromClient(), 0);
}
}
});
Future<Object> serverToClient = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
catch (InterruptedIOException x)
{
startLatch.countDown();
logger.debug("Automatic flow C <-- S started");
try
return null;
}
finally
{
stopLatch.countDown();
logger.debug("Automatic flow C --> S finished");
}
});
Future<Object> serverToClient = threadPool.submit(() ->
{
startLatch.countDown();
logger.debug("Automatic flow C <-- S started");
try
{
while (true)
{
while (true)
{
flushToClient(readFromServer());
}
}
catch (InterruptedIOException x)
{
return null;
}
finally
{
stopLatch.countDown();
logger.debug("Automatic flow C <-- S finished");
flushToClient(readFromServer());
}
}
catch (InterruptedIOException x)
{
return null;
}
finally
{
stopLatch.countDown();
logger.debug("Automatic flow C <-- S finished");
}
});
Assert.assertTrue(startLatch.await(5, TimeUnit.SECONDS));
return new SslBytesServerTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient);

View File

@ -27,10 +27,12 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Predicate;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.component.LifeCycle;
@ -39,19 +41,20 @@ import org.eclipse.jetty.util.thread.ShutdownThread;
/**
* Shutdown/Stop Monitor thread.
* <p>
* This thread listens on the host/port specified by the STOP.HOST/STOP.PORT system parameter (defaults to 127.0.0.1/-1 for not listening) for
* request authenticated with the key given by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
* This thread listens on the host/port specified by the STOP.HOST/STOP.PORT
* system parameter (defaults to 127.0.0.1/-1 for not listening) for request
* authenticated with the key given by the STOP.KEY system parameter
* (defaults to "eclipse") for admin requests.
* <p>
* If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
* If the stop port is set to zero, then a random port is assigned and the
* port number is printed to stdout.
* <p>
* Commands "stop" and "status" are currently supported.
*/
public class ShutdownMonitor
public class ShutdownMonitor
{
private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>();
// Implementation of safe lazy init, using Initialization on Demand Holder technique.
static class Holder
private static class Holder
{
static ShutdownMonitor instance = new ShutdownMonitor();
}
@ -60,283 +63,32 @@ public class ShutdownMonitor
{
return Holder.instance;
}
/* ------------------------------------------------------------ */
public static synchronized void register(LifeCycle... lifeCycles)
public static void register(LifeCycle... lifeCycles)
{
getInstance()._lifeCycles.addAll(Arrays.asList(lifeCycles));
getInstance().addLifeCycles(lifeCycles);
}
/* ------------------------------------------------------------ */
public static synchronized void deregister(LifeCycle lifeCycle)
public static void deregister(LifeCycle lifeCycle)
{
getInstance()._lifeCycles.remove(lifeCycle);
getInstance().removeLifeCycle(lifeCycle);
}
/* ------------------------------------------------------------ */
public static synchronized boolean isRegistered(LifeCycle lifeCycle)
public static boolean isRegistered(LifeCycle lifeCycle)
{
return getInstance()._lifeCycles.contains(lifeCycle);
return getInstance().containsLifeCycle(lifeCycle);
}
/* ------------------------------------------------------------ */
/**
* ShutdownMonitorRunnable
*
* Thread for listening to STOP.PORT for command to stop Jetty.
* If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
* called after the stop.
*
*/
private class ShutdownMonitorRunnable implements Runnable
{
public ShutdownMonitorRunnable()
{
startListenSocket();
}
@Override
public void run()
{
if (serverSocket == null)
{
return;
}
while (serverSocket != null)
{
Socket socket = null;
try
{
socket = serverSocket.accept();
LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
String receivedKey = lin.readLine();
if (!key.equals(receivedKey))
{
System.err.println("Ignoring command with incorrect key");
continue;
}
OutputStream out = socket.getOutputStream();
String cmd = lin.readLine();
debug("command=%s",cmd);
if ("stop".equalsIgnoreCase(cmd)) //historic, for backward compatibility
{
//Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting
debug("Issuing stop...");
for (LifeCycle l:_lifeCycles)
{
try
{
if (l.isStarted() && ShutdownThread.isRegistered(l))
{
l.stop();
}
if ((l instanceof Destroyable) && exitVm)
((Destroyable)l).destroy();
}
catch (Exception e)
{
debug(e);
}
}
//Stop accepting any more commands
stopInput(socket);
// Reply to client
debug("Informing client that we are stopped.");
informClient(out, "Stopped\r\n");
//Stop the output and close the monitor socket
stopOutput(socket);
if (exitVm)
{
// Kill JVM
debug("Killing JVM");
System.exit(0);
}
}
else if ("forcestop".equalsIgnoreCase(cmd))
{
debug("Issuing force stop...");
//Ensure that objects are stopped, destroyed only if vm is forcibly exiting
stopLifeCycles(exitVm);
//Stop accepting any more commands
stopInput(socket);
// Reply to client
debug("Informing client that we are stopped.");
informClient(out, "Stopped\r\n");
//Stop the output and close the monitor socket
stopOutput(socket);
//Honour any pre-setup config to stop the jvm when this command is given
if (exitVm)
{
// Kill JVM
debug("Killing JVM");
System.exit(0);
}
}
else if ("stopexit".equalsIgnoreCase(cmd))
{
debug("Issuing stop and exit...");
//Make sure that objects registered with the shutdown thread will be stopped
stopLifeCycles(true);
//Stop accepting any more input
stopInput(socket);
// Reply to client
debug("Informing client that we are stopped.");
informClient(out, "Stopped\r\n");
//Stop the output and close the monitor socket
stopOutput(socket);
debug("Killing JVM");
System.exit(0);
}
else if ("exit".equalsIgnoreCase(cmd))
{
debug("Killing JVM");
System.exit(0);
}
else if ("status".equalsIgnoreCase(cmd))
{
// Reply to client
informClient(out, "OK\r\n");
}
}
catch (Exception e)
{
debug(e);
System.err.println(e.toString());
}
finally
{
close(socket);
socket = null;
}
}
}
public void stopInput (Socket socket)
{
//Stop accepting any more input
close(serverSocket);
serverSocket = null;
//Shutdown input from client
shutdownInput(socket);
}
public void stopOutput (Socket socket) throws IOException
{
socket.shutdownOutput();
close(socket);
socket = null;
debug("Shutting down monitor");
serverSocket = null;
}
public void informClient (OutputStream out, String message) throws IOException
{
out.write(message.getBytes(StandardCharsets.UTF_8));
out.flush();
}
/**
* Stop the registered lifecycles, optionally
* calling destroy on them.
*
* @param destroy true if {@link Destroyable}'s should also be destroyed.
*/
public void stopLifeCycles (boolean destroy)
{
for (LifeCycle l:_lifeCycles)
{
try
{
if (l.isStarted())
{
l.stop();
}
if ((l instanceof Destroyable) && destroy)
((Destroyable)l).destroy();
}
catch (Exception e)
{
debug(e);
}
}
}
public void startListenSocket()
{
if (port < 0)
{
if (DEBUG)
System.err.println("ShutdownMonitor not in use (port < 0): " + port);
return;
}
try
{
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port), 1);
if (port == 0)
{
// server assigned port in use
port = serverSocket.getLocalPort();
System.out.printf("STOP.PORT=%d%n",port);
}
if (key == null)
{
// create random key
key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
System.out.printf("STOP.KEY=%s%n",key);
}
}
catch (Exception e)
{
debug(e);
System.err.println("Error binding monitor port " + port + ": " + e.toString());
serverSocket = null;
}
finally
{
// establish the port and key that are in use
debug("STOP.PORT=%d",port);
debug("STOP.KEY=%s",key);
debug("%s",serverSocket);
}
}
}
private boolean DEBUG;
private String host;
private final Set<LifeCycle> _lifeCycles = new LinkedHashSet<>();
private boolean debug;
private final String host;
private int port;
private String key;
private boolean exitVm;
private ServerSocket serverSocket;
private Thread thread;
private boolean alive;
/**
* Create a ShutdownMonitor using configuration from the System properties.
* Creates a ShutdownMonitor using configuration from the System properties.
* <p>
* <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
* <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
@ -345,106 +97,76 @@ public class ShutdownMonitor
*/
private ShutdownMonitor()
{
this.DEBUG = System.getProperty("DEBUG") != null;
// Use values passed thru via /jetty-start/
this.host = System.getProperty("STOP.HOST","127.0.0.1");
this.port = Integer.parseInt(System.getProperty("STOP.PORT","-1"));
this.key = System.getProperty("STOP.KEY",null);
this.debug = System.getProperty("DEBUG") != null;
this.host = System.getProperty("STOP.HOST", "127.0.0.1");
this.port = Integer.parseInt(System.getProperty("STOP.PORT", "-1"));
this.key = System.getProperty("STOP.KEY", null);
this.exitVm = true;
}
private void close(ServerSocket server)
private void addLifeCycles(LifeCycle... lifeCycles)
{
if (server == null)
synchronized (this)
{
return;
}
try
{
server.close();
}
catch (IOException ignore)
{
debug(ignore);
_lifeCycles.addAll(Arrays.asList(lifeCycles));
}
}
private void close(Socket socket)
private void removeLifeCycle(LifeCycle lifeCycle)
{
if (socket == null)
synchronized (this)
{
return;
}
try
{
socket.close();
}
catch (IOException ignore)
{
debug(ignore);
_lifeCycles.remove(lifeCycle);
}
}
private void shutdownInput(Socket socket)
private boolean containsLifeCycle(LifeCycle lifeCycle)
{
if (socket == null)
return;
try
synchronized (this)
{
socket.shutdownInput();
}
catch (IOException ignore)
{
debug(ignore);
return _lifeCycles.contains(lifeCycle);
}
}
private void debug(String format, Object... args)
{
if (DEBUG)
{
System.err.printf("[ShutdownMonitor] " + format + "%n",args);
}
if (debug)
System.err.printf("[ShutdownMonitor] " + format + "%n", args);
}
private void debug(Throwable t)
{
if (DEBUG)
{
if (debug)
t.printStackTrace(System.err);
}
}
public String getKey()
{
return key;
synchronized (this)
{
return key;
}
}
public int getPort()
{
return port;
}
public ServerSocket getServerSocket()
{
return serverSocket;
synchronized (this)
{
return port;
}
}
public boolean isExitVm()
{
return exitVm;
synchronized (this)
{
return exitVm;
}
}
public void setDebug(boolean flag)
{
this.DEBUG = flag;
this.debug = flag;
}
/**
@ -454,10 +176,8 @@ public class ShutdownMonitor
{
synchronized (this)
{
if (thread != null && thread.isAlive())
{
throw new IllegalStateException("ShutdownMonitorThread already started");
}
if (alive)
throw new IllegalStateException("ShutdownMonitor already started");
this.exitVm = exitVm;
}
}
@ -466,10 +186,8 @@ public class ShutdownMonitor
{
synchronized (this)
{
if (thread != null && thread.isAlive())
{
throw new IllegalStateException("ShutdownMonitorThread already started");
}
if (alive)
throw new IllegalStateException("ShutdownMonitor already started");
this.key = key;
}
}
@ -478,52 +196,254 @@ public class ShutdownMonitor
{
synchronized (this)
{
if (thread != null && thread.isAlive())
{
throw new IllegalStateException("ShutdownMonitorThread already started");
}
if (alive)
throw new IllegalStateException("ShutdownMonitor already started");
this.port = port;
}
}
protected void start() throws Exception
{
Thread t = null;
synchronized (this)
{
if (thread != null && thread.isAlive())
if (alive)
{
if (DEBUG)
System.err.printf("ShutdownMonitorThread already started");
debug("Already started");
return; // cannot start it again
}
thread = new Thread(new ShutdownMonitorRunnable());
thread.setDaemon(true);
thread.setName("ShutdownMonitor");
t = thread;
ServerSocket serverSocket = listen();
if (serverSocket != null)
{
alive = true;
Thread thread = new Thread(new ShutdownMonitorRunnable(serverSocket));
thread.setDaemon(true);
thread.setName("ShutdownMonitor");
thread.start();
}
}
if (t != null)
t.start();
}
protected boolean isAlive ()
private void stop()
{
boolean result = false;
synchronized (this)
{
result = (thread != null && thread.isAlive());
alive = false;
notifyAll();
}
return result;
}
// For test purposes only.
void await() throws InterruptedException
{
synchronized (this)
{
while (alive)
{
wait();
}
}
}
protected boolean isAlive()
{
synchronized (this)
{
return alive;
}
}
private ServerSocket listen()
{
int port = getPort();
if (port < 0)
{
debug("Not enabled (port < 0): %d", port);
return null;
}
String key = getKey();
try
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port));
if (port == 0)
{
port = serverSocket.getLocalPort();
System.out.printf("STOP.PORT=%d%n", port);
setPort(port);
}
if (key == null)
{
key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()), 36);
System.out.printf("STOP.KEY=%s%n", key);
setKey(key);
}
return serverSocket;
}
catch (Throwable x)
{
debug(x);
System.err.println("Error binding ShutdownMonitor to port " + port + ": " + x.toString());
return null;
}
finally
{
// establish the port and key that are in use
debug("STOP.PORT=%d", port);
debug("STOP.KEY=%s", key);
}
}
@Override
public String toString()
{
return String.format("%s[port=%d]",this.getClass().getName(),port);
return String.format("%s[port=%d,alive=%b]", this.getClass().getName(), getPort(), isAlive());
}
/**
* Thread for listening to STOP.PORT for command to stop Jetty.
* If ShutdownMonitor.exitVm is true, then System.exit will also be
* called after the stop.
*/
private class ShutdownMonitorRunnable implements Runnable
{
private final ServerSocket serverSocket;
private ShutdownMonitorRunnable(ServerSocket serverSocket)
{
this.serverSocket = serverSocket;
}
@Override
public void run()
{
debug("Started");
try
{
String key = getKey();
while (true)
{
try (Socket socket = serverSocket.accept())
{
LineNumberReader reader = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
String receivedKey = reader.readLine();
if (!key.equals(receivedKey))
{
debug("Ignoring command with incorrect key: %s", receivedKey);
continue;
}
String cmd = reader.readLine();
debug("command=%s", cmd);
OutputStream out = socket.getOutputStream();
boolean exitVm = isExitVm();
if ("stop".equalsIgnoreCase(cmd)) //historic, for backward compatibility
{
//Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting
debug("Performing stop command");
stopLifeCycles(ShutdownThread::isRegistered, exitVm);
// Reply to client
debug("Informing client that we are stopped");
informClient(out, "Stopped\r\n");
if (!exitVm)
break;
// Kill JVM
debug("Killing JVM");
System.exit(0);
}
else if ("forcestop".equalsIgnoreCase(cmd))
{
debug("Performing forced stop command");
stopLifeCycles(l -> true, exitVm);
// Reply to client
debug("Informing client that we are stopped");
informClient(out, "Stopped\r\n");
if (!exitVm)
break;
// Kill JVM
debug("Killing JVM");
System.exit(0);
}
else if ("stopexit".equalsIgnoreCase(cmd))
{
debug("Performing stop and exit commands");
stopLifeCycles(ShutdownThread::isRegistered, true);
// Reply to client
debug("Informing client that we are stopped");
informClient(out, "Stopped\r\n");
debug("Killing JVM");
System.exit(0);
}
else if ("exit".equalsIgnoreCase(cmd))
{
debug("Killing JVM");
System.exit(0);
}
else if ("status".equalsIgnoreCase(cmd))
{
// Reply to client
informClient(out, "OK\r\n");
}
}
catch (Throwable x)
{
debug(x);
}
}
}
catch (Throwable x)
{
debug(x);
}
finally
{
stop();
debug("Stopped");
}
}
private void informClient(OutputStream out, String message) throws IOException
{
out.write(message.getBytes(StandardCharsets.UTF_8));
out.flush();
}
private void stopLifeCycles(Predicate<LifeCycle> predicate, boolean destroy)
{
List<LifeCycle> lifeCycles = new ArrayList<>();
synchronized (this)
{
lifeCycles.addAll(_lifeCycles);
}
for (LifeCycle l : lifeCycles)
{
try
{
if (l.isStarted() && predicate.test(l))
l.stop();
if ((l instanceof Destroyable) && destroy)
((Destroyable)l).destroy();
}
catch (Throwable x)
{
debug(x);
}
}
}
}
}

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.server.handler.gzip;
import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
import java.io.File;
import java.io.IOException;
import java.util.Set;
@ -47,9 +45,8 @@ import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A Handler that can dynamically GZIP compress responses. Unlike
* A Handler that can dynamically GZIP compress responses. Unlike
* previous and 3rd party GzipFilters, this mechanism works with asynchronously
* generated responses and does not need to wrap the response or it's output
* stream. Instead it uses the efficient {@link org.eclipse.jetty.server.HttpOutput.Interceptor} mechanism.
@ -69,22 +66,22 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
private boolean _checkGzExists = true;
private boolean _syncFlush = false;
// non-static, as other GzipHandler instances may have different configurations
private final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
private final ThreadLocal<Deflater> _deflater = new ThreadLocal<>();
private final IncludeExclude<String> _agentPatterns=new IncludeExclude<>(RegexSet.class);
private final IncludeExclude<String> _methods = new IncludeExclude<>();
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
private HttpField _vary;
/* ------------------------------------------------------------ */
/**
* Instantiates a new gzip handler.
* The excluded Mime Types are initialized to common known
* The excluded Mime Types are initialized to common known
* images, audio, video and other already compressed types.
* The included methods is initialized to GET.
* The excluded agent patterns are set to exclude MSIE 6.0
@ -107,7 +104,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude("application/bzip2");
_mimeTypes.exclude("application/x-rar-compressed");
LOG.debug("{} mime types {}",this,_mimeTypes);
_agentPatterns.exclude(".*MSIE 6.0.*");
}
@ -145,7 +142,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* @param pathspecs Path specs (as per servlet spec) to exclude. If a
* @param pathspecs Path specs (as per servlet spec) to exclude. If a
* ServletContext is available, the paths are relative to the context path,
* otherwise they are absolute.
* For backward compatibility the pathspecs may be comma separated strings, but this
@ -165,7 +162,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
_agentPatterns.include(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to include in compression
@ -214,7 +211,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* Add path specs to include. Inclusion takes precedence over exclusion.
* @param pathspecs Path specs (as per servlet spec) to include. If a
* @param pathspecs Path specs (as per servlet spec) to include. If a
* ServletContext is available, the paths are relative to the context path,
* otherwise they are absolute
* For backward compatibility the pathspecs may be comma separated strings, but this
@ -225,7 +222,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
for (String p : pathspecs)
_paths.include(StringUtil.csvSplit(p));
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
@ -245,7 +242,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
return _compressionLevel;
}
/* ------------------------------------------------------------ */
@Override
public Deflater getDeflater(Request request, long content_length)
@ -256,7 +253,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
LOG.debug("{} excluded user agent {}",this,request);
return null;
}
if (content_length>=0 && content_length<_minGzipSize)
{
LOG.debug("{} excluded minGzipSize {}",this,request);
@ -281,16 +278,16 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return null;
}
}
Deflater df = _deflater.get();
if (df==null)
df=new Deflater(_compressionLevel,true);
df=new Deflater(_compressionLevel,true);
else
_deflater.set(null);
return df;
}
/* ------------------------------------------------------------ */
public String[] getExcludedAgentPatterns()
{
@ -325,7 +322,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
Set<String> includes=_agentPatterns.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMethods()
{
@ -365,6 +362,11 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _minGzipSize;
}
protected HttpField getVaryField()
{
return _vary;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
@ -375,8 +377,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
ServletContext context = baseRequest.getServletContext();
String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
LOG.debug("{} handle {} in {}",this,baseRequest,context);
HttpOutput out = baseRequest.getResponse().getHttpOutput();
HttpOutput out = baseRequest.getResponse().getHttpOutput();
// Are we already being gzipped?
HttpOutput.Interceptor interceptor = out.getInterceptor();
while (interceptor!=null)
@ -389,7 +391,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
interceptor=interceptor.getNextInterceptor();
}
// If not a supported method - no Vary because no matter what client, this URI is always excluded
if (!_methods.matches(baseRequest.getMethod()))
{
@ -397,7 +399,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_handler.handle(target,baseRequest, request, response);
return;
}
// If not a supported URI- no Vary because no matter what client, this URI is always excluded
// Use pathInfo because this is be
if (!isPathGzipable(path))
@ -420,7 +422,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return;
}
}
if (_checkGzExists && context!=null)
{
String realpath=request.getServletContext().getRealPath(path);
@ -436,25 +438,26 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
}
}
// Special handling for etags
String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
if (etag!=null)
{
int i=etag.indexOf(ETAG_GZIP_QUOTE);
int i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE);
if (i>0)
{
while (i>=0)
{
etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
i=etag.indexOf(ETAG_GZIP_QUOTE,i);
i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE,i);
}
baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
}
}
// install interceptor and handle
out.setInterceptor(new GzipHttpOutputInterceptor(this,_vary,baseRequest.getHttpChannel(),out.getInterceptor(),_syncFlush));
out.setInterceptor(new GzipHttpOutputInterceptor(this,getVaryField(),baseRequest.getHttpChannel(),out.getInterceptor(),isSyncFlush()));
if (_handler!=null)
_handler.handle(target,baseRequest, request, response);
}
@ -470,7 +473,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
if (ua == null)
return false;
return _agentPatterns.matches(ua);
}
@ -483,7 +486,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* Checks to see if the path is included or not excluded
* Checks to see if the path is included or not excluded
*
* @param requestURI
* the request uri
@ -493,7 +496,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
if (requestURI == null)
return true;
return _paths.matches(requestURI);
}
@ -515,7 +518,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
_checkGzExists = checkGzExists;
}
/* ------------------------------------------------------------ */
/**
* @param compressionLevel The compression level to use to initialize {@link Deflater#setLevel(int)}
@ -558,7 +561,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* @param pathspecs Path specs (as per servlet spec) to exclude. If a
* @param pathspecs Path specs (as per servlet spec) to exclude. If a
* ServletContext is available, the paths are relative to the context path,
* otherwise they are absolute.
*/
@ -577,7 +580,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_agentPatterns.getIncluded().clear();
addIncludedAgentPatterns(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to include in compression
@ -587,7 +590,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.getIncluded().clear();
_methods.include(methods);
}
/* ------------------------------------------------------------ */
/**
* Set included mime types. Inclusion takes precedence over
@ -603,7 +606,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* Set the path specs to include. Inclusion takes precedence over exclusion.
* @param pathspecs Path specs (as per servlet spec) to include. If a
* @param pathspecs Path specs (as per servlet spec) to include. If a
* ServletContext is available, the paths are relative to the context path,
* otherwise they are absolute
*/

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingNestedCallback;
@ -46,7 +47,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
public final static HttpField VARY_ACCEPT_ENCODING_USER_AGENT=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
public final static HttpField VARY_ACCEPT_ENCODING=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING.asString());
private enum GZState { MIGHT_COMPRESS, NOT_COMPRESSING, COMMITTING, COMPRESSING, FINISHED};
private final AtomicReference<GZState> _state = new AtomicReference<>(GZState.MIGHT_COMPRESS);
private final CRC32 _crc = new CRC32();
@ -57,7 +58,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
private final HttpField _vary;
private final int _bufferSize;
private final boolean _syncFlush;
private Deflater _deflater;
private ByteBuffer _buffer;
@ -65,12 +66,12 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
this(factory,VARY_ACCEPT_ENCODING_USER_AGENT,channel.getHttpConfiguration().getOutputBufferSize(),channel,next,syncFlush);
}
public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, HttpChannel channel, HttpOutput.Interceptor next,boolean syncFlush)
{
this(factory,vary,channel.getHttpConfiguration().getOutputBufferSize(),channel,next,syncFlush);
}
public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, int bufferSize, HttpChannel channel, HttpOutput.Interceptor next,boolean syncFlush)
{
_factory=factory;
@ -85,14 +86,14 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
return _interceptor;
}
@Override
public boolean isOptimizedForDirectBuffers()
{
return false; // No point as deflator is in user space.
}
@Override
public void write(ByteBuffer content, boolean complete, Callback callback)
{
@ -101,11 +102,11 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
case MIGHT_COMPRESS:
commit(content,complete,callback);
break;
case NOT_COMPRESSING:
_interceptor.write(content, complete, callback);
return;
case COMMITTING:
callback.failed(new WritePendingException());
break;
@ -124,21 +125,21 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
int i=_buffer.limit();
_buffer.limit(i+8);
int v=(int)_crc.getValue();
_buffer.put(i++,(byte)(v & 0xFF));
_buffer.put(i++,(byte)((v>>>8) & 0xFF));
_buffer.put(i++,(byte)((v>>>16) & 0xFF));
_buffer.put(i++,(byte)((v>>>24) & 0xFF));
v=_deflater.getTotalIn();
_buffer.put(i++,(byte)(v & 0xFF));
_buffer.put(i++,(byte)((v>>>8) & 0xFF));
_buffer.put(i++,(byte)((v>>>16) & 0xFF));
_buffer.put(i++,(byte)((v>>>24) & 0xFF));
}
private void gzip(ByteBuffer content, boolean complete, final Callback callback)
{
if (content.hasRemaining() || complete)
@ -150,7 +151,8 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
protected void commit(ByteBuffer content, boolean complete, Callback callback)
{
// Are we excluding because of status?
int sc = _channel.getResponse().getStatus();
Response response = _channel.getResponse();
int sc = response.getStatus();
if (sc>0 && (sc<200 || sc==204 || sc==205 || sc>=300))
{
LOG.debug("{} exclude by status {}",this,sc);
@ -158,9 +160,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
_interceptor.write(content, complete, callback);
return;
}
// Are we excluding because of mime-type?
String ct = _channel.getResponse().getContentType();
String ct = response.getContentType();
if (ct!=null)
{
ct=MimeTypes.getContentTypeWithoutCharset(ct);
@ -172,9 +174,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
return;
}
}
// Has the Content-Encoding header already been set?
String ce=_channel.getResponse().getHeader("Content-Encoding");
String ce=response.getHeader("Content-Encoding");
if (ce != null)
{
LOG.debug("{} exclude by content-encoding {}",this,ce);
@ -182,20 +184,21 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
_interceptor.write(content, complete, callback);
return;
}
// Are we the thread that commits?
if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.COMMITTING))
{
// We are varying the response due to accept encoding header.
HttpFields fields = _channel.getResponse().getHttpFields();
fields.add(_vary);
HttpFields fields = response.getHttpFields();
if (_vary != null)
fields.add(_vary);
long content_length = _channel.getResponse().getContentLength();
long content_length = response.getContentLength();
if (content_length<0 && complete)
content_length=content.remaining();
_deflater = _factory.getDeflater(_channel.getRequest(),content_length);
if (_deflater==null)
{
LOG.debug("{} exclude no deflater",this);
@ -210,7 +213,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
// Adjust headers
_channel.getResponse().setContentLength(-1);
response.setContentLength(-1);
String etag=fields.get(HttpHeader.ETAG);
if (etag!=null)
{
@ -218,10 +221,10 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHttpContent.ETAG_GZIP+'"':etag+GzipHttpContent.ETAG_GZIP;
fields.put(HttpHeader.ETAG,etag);
}
LOG.debug("{} compressing {}",this,_deflater);
_state.set(GZState.COMPRESSING);
gzip(content,complete,callback);
}
else
@ -268,14 +271,14 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
}
}
}
public boolean mightCompress()
{
return _state.get()==GZState.MIGHT_COMPRESS;
}
private class GzipBufferCB extends IteratingNestedCallback
{
{
private ByteBuffer _copy;
private final ByteBuffer _content;
private final boolean _last;
@ -291,11 +294,11 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
if (_deflater==null)
return Action.SUCCEEDED;
if (_deflater.needsInput())
{
{
if (BufferUtil.isEmpty(_content))
{
{
if (_deflater.finished())
{
_factory.recycle(_deflater);
@ -309,12 +312,12 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
}
return Action.SUCCEEDED;
}
if (!_last)
{
return Action.SUCCEEDED;
}
_deflater.finish();
}
else if (_content.hasArray())
@ -323,9 +326,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
int off=_content.arrayOffset()+_content.position();
int len=_content.remaining();
BufferUtil.clear(_content);
_crc.update(array,off,len);
_deflater.setInput(array,off,len);
_deflater.setInput(array,off,len);
if (_last)
_deflater.finish();
}
@ -338,13 +341,13 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
BufferUtil.flipToFlush(_copy,0);
if (took==0)
throw new IllegalStateException();
byte[] array=_copy.array();
int off=_copy.arrayOffset()+_copy.position();
int len=_copy.remaining();
_crc.update(array,off,len);
_deflater.setInput(array,off,len);
_deflater.setInput(array,off,len);
if (_last && BufferUtil.isEmpty(_content))
_deflater.finish();
}
@ -359,10 +362,10 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
_buffer.limit(_buffer.limit()+produced);
}
boolean finished=_deflater.finished();
if (finished)
addTrailer();
_interceptor.write(_buffer,finished,this);
return Action.SCHEDULED;
}

View File

@ -18,196 +18,151 @@
package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.junit.Test;
/**
* ShutdownMonitorTest
*/
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class ShutdownMonitorTest
{
public class TestableServer extends Server
{
boolean destroyed = false;
boolean stopped = false;
@Override
protected void doStop() throws Exception
{
stopped = true;
super.doStop();
}
@Override
public void destroy()
{
destroyed = true;
super.destroy();
}
@Override
protected void doStart() throws Exception
{
stopped = false;
destroyed = false;
super.doStart();
}
}
@Test
public void testShutdownMonitor() throws Exception
{
// test port and key assignment
ShutdownMonitor.getInstance().setPort(0);
ShutdownMonitor.getInstance().setExitVm(false);
ShutdownMonitor.getInstance().start();
String key = ShutdownMonitor.getInstance().getKey();
int port = ShutdownMonitor.getInstance().getPort();
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setDebug(true);
monitor.setPort(0);
monitor.setExitVm(false);
monitor.start();
String key = monitor.getKey();
int port = monitor.getPort();
// try starting a 2nd time (should be ignored)
ShutdownMonitor.getInstance().start();
monitor.start();
stop("stop", port,key,true);
assertTrue(!ShutdownMonitor.getInstance().isAlive());
stop("stop", port, key, true);
monitor.await();
assertTrue(!monitor.isAlive());
// should be able to change port and key because it is stopped
ShutdownMonitor.getInstance().setPort(0);
ShutdownMonitor.getInstance().setKey("foo");
ShutdownMonitor.getInstance().start();
monitor.setPort(0);
String newKey = "foo";
monitor.setKey(newKey);
monitor.start();
key = ShutdownMonitor.getInstance().getKey();
port = ShutdownMonitor.getInstance().getPort();
assertTrue(ShutdownMonitor.getInstance().isAlive());
key = monitor.getKey();
assertEquals(newKey, key);
port = monitor.getPort();
assertTrue(monitor.isAlive());
stop("stop", port,key,true);
assertTrue(!ShutdownMonitor.getInstance().isAlive());
stop("stop", port, key, true);
monitor.await();
assertTrue(!monitor.isAlive());
}
@Test
public void testForceStopCommand() throws Exception
{
//create a testable Server with stop(), destroy() overridden to instrument
//start server
//call "forcestop" and check that server stopped but not destroyed
// test port and key assignment
System.setProperty("DEBUG", "true");
ShutdownMonitor.getInstance().setPort(0);
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setPort(0);
TestableServer server = new TestableServer();
server.start();
//shouldn't be registered for shutdown on jvm
assertTrue(!ShutdownThread.isRegistered(server));
assertTrue(ShutdownMonitor.isRegistered(server));
String key = ShutdownMonitor.getInstance().getKey();
int port = ShutdownMonitor.getInstance().getPort();
stop("forcestop", port,key,true);
assertTrue(!ShutdownMonitor.getInstance().isAlive());
String key = monitor.getKey();
int port = monitor.getPort();
stop("forcestop", port, key, true);
monitor.await();
assertTrue(!monitor.isAlive());
assertTrue(server.stopped);
assertTrue(!server.destroyed);
assertTrue(!ShutdownThread.isRegistered(server));
assertTrue(!ShutdownMonitor.isRegistered(server));
}
@Test
public void testOldStopCommandWithStopOnShutdownTrue() throws Exception
{
//create a testable Server with stop(), destroy() overridden to instrument
//call server.setStopAtShudown(true);
//start server
//call "stop" and check that server stopped but not destroyed
//stop server
//call server.setStopAtShutdown(false);
//start server
//call "stop" and check that the server is not stopped and not destroyed
System.setProperty("DEBUG", "true");
ShutdownMonitor.getInstance().setExitVm(false);
ShutdownMonitor.getInstance().setPort(0);
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setExitVm(false);
monitor.setPort(0);
TestableServer server = new TestableServer();
server.setStopAtShutdown(true);
server.start();
//should be registered for shutdown on exit
assertTrue(ShutdownThread.isRegistered(server));
assertTrue(ShutdownMonitor.isRegistered(server));
String key = ShutdownMonitor.getInstance().getKey();
int port = ShutdownMonitor.getInstance().getPort();
String key = monitor.getKey();
int port = monitor.getPort();
stop("stop", port, key, true);
assertTrue(!ShutdownMonitor.getInstance().isAlive());
monitor.await();
assertTrue(!monitor.isAlive());
assertTrue(server.stopped);
assertTrue(!server.destroyed);
assertTrue(!ShutdownThread.isRegistered(server));
assertTrue(!ShutdownMonitor.isRegistered(server));
}
@Test
public void testOldStopCommandWithStopOnShutdownFalse() throws Exception
{
//change so stopatshutdown is false, so stop does nothing in this case (as exitVm is false otherwise we couldn't run test)
ShutdownMonitor.getInstance().setExitVm(false);
System.setProperty("DEBUG", "true");
ShutdownMonitor.getInstance().setPort(0);
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setExitVm(false);
monitor.setPort(0);
TestableServer server = new TestableServer();
server.setStopAtShutdown(false);
server.start();
assertTrue(!ShutdownThread.isRegistered(server));
assertTrue(ShutdownMonitor.isRegistered(server));
String key = ShutdownMonitor.getInstance().getKey();
int port = ShutdownMonitor.getInstance().getPort();
stop ("stop", port, key, true);
assertTrue(!ShutdownMonitor.getInstance().isAlive());
String key = monitor.getKey();
int port = monitor.getPort();
stop("stop", port, key, true);
monitor.await();
assertTrue(!monitor.isAlive());
assertTrue(!server.stopped);
assertTrue(!server.destroyed);
assertTrue(!ShutdownThread.isRegistered(server));
assertTrue(ShutdownMonitor.isRegistered(server));
}
public void stop(String command, int port, String key, boolean check) throws Exception
{
System.out.printf("Attempting to send "+command+" to localhost:%d (%b)%n",port,check);
try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"),port))
System.out.printf("Attempting to send " + command + " to localhost:%d (%b)%n", port, check);
try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"), port))
{
// send stop command
try (OutputStream out = s.getOutputStream())
{
out.write((key + "\r\n"+command+"\r\n").getBytes());
out.write((key + "\r\n" + command + "\r\n").getBytes());
out.flush();
if (check)
{
// wait a little
TimeUnit.MILLISECONDS.sleep(600);
// check for stop confirmation
LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
String response;
if ((response = lin.readLine()) != null)
{
assertEquals("Stopped",response);
}
assertEquals("Stopped", response);
else
throw new IllegalStateException("No stop confirmation");
}
@ -215,4 +170,31 @@ public class ShutdownMonitorTest
}
}
public class TestableServer extends Server
{
boolean destroyed = false;
boolean stopped = false;
@Override
protected void doStop() throws Exception
{
stopped = true;
super.doStop();
}
@Override
public void destroy()
{
destroyed = true;
super.destroy();
}
@Override
protected void doStart() throws Exception
{
stopped = false;
destroyed = false;
super.doStart();
}
}
}