diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java
index cdae1be57fe..d151af1bcff 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java
@@ -22,8 +22,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
-import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -45,7 +45,6 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.NetworkTrafficListener;
import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint;
-import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.NetworkTrafficServerConnector;
@@ -508,16 +507,9 @@ public class NetworkTrafficListenerTest
super(new HttpClientTransportOverHTTP(new ClientConnector()
{
@Override
- protected SelectorManager newSelectorManager()
+ protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey)
{
- return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors())
- {
- @Override
- protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
- {
- return new NetworkTrafficSocketChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout().toMillis(), listener.get());
- }
- };
+ return new NetworkTrafficSocketChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout().toMillis(), listener.get());
}
}));
this.listener = listener;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index b304a422741..87e310c5462 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -50,10 +50,10 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnection;
@@ -189,11 +189,11 @@ public class SslBytesServerTest extends SslBytesTest
ServerConnector connector = new ServerConnector(server, null, null, null, 1, 1, sslFactory, httpFactory)
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
- ChannelEndPoint endp = super.newEndPoint(channel, selectSet, key);
- serverEndPoint.set(endp);
- return endp;
+ SocketChannelEndPoint endPoint = super.newEndPoint(channel, selectSet, key);
+ serverEndPoint.set(endPoint);
+ return endPoint;
}
};
connector.setIdleTimeout(idleTimeout);
diff --git a/jetty-client/src/test/resources/jetty-logging.properties b/jetty-client/src/test/resources/jetty-logging.properties
index e6bbf9a6ca0..2bbfa1a3add 100644
--- a/jetty-client/src/test/resources/jetty-logging.properties
+++ b/jetty-client/src/test/resources/jetty-logging.properties
@@ -1,6 +1,6 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
-#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
+#org.eclipse.jetty.io.SocketChannelEndPoint.LEVEL=DEBUG
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.http.LEVEL=DEBUG
diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java
index 3579cf670b1..7340254f09f 100644
--- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java
+++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
-import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -57,7 +56,6 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.logging.StacklessLogging;
@@ -118,7 +116,7 @@ public class HTTP2ServerTest extends AbstractServerTest
startServer(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
{
latch.countDown();
}
@@ -175,7 +173,7 @@ public class HTTP2ServerTest extends AbstractServerTest
startServer(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
latch.countDown();
resp.getOutputStream().write(content);
@@ -321,7 +319,7 @@ public class HTTP2ServerTest extends AbstractServerTest
startServer(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
try
{
@@ -340,7 +338,7 @@ public class HTTP2ServerTest extends AbstractServerTest
ServerConnector connector2 = new ServerConnector(server, new HTTP2ServerConnectionFactory(new HttpConfiguration()))
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
index 7cd08ad4542..b4483f703d2 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -469,11 +469,11 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
name = c.getSimpleName();
}
- return String.format("%s@%h{%s<->%s,%s,fill=%s,flush=%s,to=%d/%d}",
+ return String.format("%s@%h{l=%s,r=%s,%s,fill=%s,flush=%s,to=%d/%d}",
name,
this,
- getRemoteAddress(),
getLocalAddress(),
+ getRemoteAddress(),
_state.get(),
_fillInterest.toStateString(),
_writeFlusher.toStateString(),
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
deleted file mode 100644
index 2c89a173753..00000000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
+++ /dev/null
@@ -1,429 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.GatheringByteChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.thread.Invocable;
-import org.eclipse.jetty.util.thread.Scheduler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Channel End Point.
- *
Holds the channel and socket for an NIO endpoint.
- */
-public abstract class ChannelEndPoint extends AbstractEndPoint implements ManagedSelector.Selectable
-{
- private static final Logger LOG = LoggerFactory.getLogger(ChannelEndPoint.class);
-
- private final ByteChannel _channel;
- private final GatheringByteChannel _gather;
- protected final ManagedSelector _selector;
- protected final SelectionKey _key;
- private boolean _updatePending;
-
- /**
- * The current value for {@link SelectionKey#interestOps()}.
- */
- protected int _currentInterestOps;
-
- /**
- * The desired value for {@link SelectionKey#interestOps()}.
- */
- protected int _desiredInterestOps;
-
- private abstract class RunnableTask implements Runnable, Invocable
- {
- final String _operation;
-
- protected RunnableTask(String op)
- {
- _operation = op;
- }
-
- @Override
- public String toString()
- {
- return String.format("CEP:%s:%s:%s", ChannelEndPoint.this, _operation, getInvocationType());
- }
- }
-
- private abstract class RunnableCloseable extends RunnableTask implements Closeable
- {
- protected RunnableCloseable(String op)
- {
- super(op);
- }
-
- @Override
- public void close()
- {
- try
- {
- ChannelEndPoint.this.close();
- }
- catch (Throwable x)
- {
- LOG.warn("Unable to close ChannelEndPoint", x);
- }
- }
- }
-
- private final ManagedSelector.SelectorUpdate _updateKeyAction = new ManagedSelector.SelectorUpdate()
- {
- @Override
- public void update(Selector selector)
- {
- updateKey();
- }
- };
-
- private final Runnable _runFillable = new RunnableCloseable("runFillable")
- {
- @Override
- public InvocationType getInvocationType()
- {
- return getFillInterest().getCallbackInvocationType();
- }
-
- @Override
- public void run()
- {
- getFillInterest().fillable();
- }
- };
-
- private final Runnable _runCompleteWrite = new RunnableCloseable("runCompleteWrite")
- {
- @Override
- public InvocationType getInvocationType()
- {
- return getWriteFlusher().getCallbackInvocationType();
- }
-
- @Override
- public void run()
- {
- getWriteFlusher().completeWrite();
- }
-
- @Override
- public String toString()
- {
- return String.format("CEP:%s:%s:%s->%s", ChannelEndPoint.this, _operation, getInvocationType(), getWriteFlusher());
- }
- };
-
- private final Runnable _runCompleteWriteFillable = new RunnableCloseable("runCompleteWriteFillable")
- {
- @Override
- public InvocationType getInvocationType()
- {
- InvocationType fillT = getFillInterest().getCallbackInvocationType();
- InvocationType flushT = getWriteFlusher().getCallbackInvocationType();
- if (fillT == flushT)
- return fillT;
-
- if (fillT == InvocationType.EITHER && flushT == InvocationType.NON_BLOCKING)
- return InvocationType.EITHER;
-
- if (fillT == InvocationType.NON_BLOCKING && flushT == InvocationType.EITHER)
- return InvocationType.EITHER;
-
- return InvocationType.BLOCKING;
- }
-
- @Override
- public void run()
- {
- getWriteFlusher().completeWrite();
- getFillInterest().fillable();
- }
- };
-
- public ChannelEndPoint(ByteChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
- {
- super(scheduler);
- _channel = channel;
- _selector = selector;
- _key = key;
- _gather = (channel instanceof GatheringByteChannel) ? (GatheringByteChannel)channel : null;
- }
-
- @Override
- public boolean isOpen()
- {
- return _channel.isOpen();
- }
-
- @Override
- public void doClose()
- {
- if (LOG.isDebugEnabled())
- LOG.debug("doClose {}", this);
- try
- {
- _channel.close();
- }
- catch (IOException e)
- {
- LOG.debug("Unable to close channel", e);
- }
- finally
- {
- super.doClose();
- }
- }
-
- @Override
- public void onClose(Throwable cause)
- {
- try
- {
- super.onClose(cause);
- }
- finally
- {
- if (_selector != null)
- _selector.destroyEndPoint(this, cause);
- }
- }
-
- @Override
- public int fill(ByteBuffer buffer) throws IOException
- {
- if (isInputShutdown())
- return -1;
-
- int pos = BufferUtil.flipToFill(buffer);
- int filled;
- try
- {
- filled = _channel.read(buffer);
- if (filled > 0)
- notIdle();
- else if (filled == -1)
- shutdownInput();
- }
- catch (IOException e)
- {
- LOG.debug("Unable to shutdown output", e);
- shutdownInput();
- filled = -1;
- }
- finally
- {
- BufferUtil.flipToFlush(buffer, pos);
- }
- if (LOG.isDebugEnabled())
- LOG.debug("filled {} {}", filled, BufferUtil.toDetailString(buffer));
- return filled;
- }
-
- @Override
- public boolean flush(ByteBuffer... buffers) throws IOException
- {
- long flushed = 0;
- try
- {
- if (buffers.length == 1)
- flushed = _channel.write(buffers[0]);
- else if (_gather != null && buffers.length > 1)
- flushed = _gather.write(buffers, 0, buffers.length);
- else
- {
- for (ByteBuffer b : buffers)
- {
- if (b.hasRemaining())
- {
- int l = _channel.write(b);
- if (l > 0)
- flushed += l;
- if (b.hasRemaining())
- break;
- }
- }
- }
- if (LOG.isDebugEnabled())
- LOG.debug("flushed {} {}", flushed, this);
- }
- catch (IOException e)
- {
- throw new EofException(e);
- }
-
- if (flushed > 0)
- notIdle();
-
- for (ByteBuffer b : buffers)
- {
- if (!BufferUtil.isEmpty(b))
- return false;
- }
-
- return true;
- }
-
- public ByteChannel getChannel()
- {
- return _channel;
- }
-
- @Override
- public Object getTransport()
- {
- return _channel;
- }
-
- @Override
- protected void needsFillInterest()
- {
- changeInterests(SelectionKey.OP_READ);
- }
-
- @Override
- protected void onIncompleteFlush()
- {
- changeInterests(SelectionKey.OP_WRITE);
- }
-
- @Override
- public Runnable onSelected()
- {
- /**
- * This method may run concurrently with {@link #changeInterests(int)}.
- */
-
- int readyOps = _key.readyOps();
- int oldInterestOps;
- int newInterestOps;
- synchronized (this)
- {
- _updatePending = true;
- // Remove the readyOps, that here can only be OP_READ or OP_WRITE (or both).
- oldInterestOps = _desiredInterestOps;
- newInterestOps = oldInterestOps & ~readyOps;
- _desiredInterestOps = newInterestOps;
- }
-
- boolean fillable = (readyOps & SelectionKey.OP_READ) != 0;
- boolean flushable = (readyOps & SelectionKey.OP_WRITE) != 0;
-
- if (LOG.isDebugEnabled())
- LOG.debug("onSelected {}->{} r={} w={} for {}", oldInterestOps, newInterestOps, fillable, flushable, this);
-
- // return task to complete the job
- Runnable task = fillable
- ? (flushable
- ? _runCompleteWriteFillable
- : _runFillable)
- : (flushable
- ? _runCompleteWrite
- : null);
-
- if (LOG.isDebugEnabled())
- LOG.debug("task {}", task);
- return task;
- }
-
- @Override
- public void updateKey()
- {
- /**
- * This method may run concurrently with {@link #changeInterests(int)}.
- */
-
- try
- {
- int oldInterestOps;
- int newInterestOps;
- synchronized (this)
- {
- _updatePending = false;
- oldInterestOps = _currentInterestOps;
- newInterestOps = _desiredInterestOps;
- if (oldInterestOps != newInterestOps)
- {
- _currentInterestOps = newInterestOps;
- _key.interestOps(newInterestOps);
- }
- }
-
- if (LOG.isDebugEnabled())
- LOG.debug("Key interests updated {} -> {} on {}", oldInterestOps, newInterestOps, this);
- }
- catch (CancelledKeyException x)
- {
- LOG.debug("Ignoring key update for concurrently closed channel {}", this);
- close();
- }
- catch (Throwable x)
- {
- LOG.warn("Ignoring key update for " + this, x);
- close();
- }
- }
-
- private void changeInterests(int operation)
- {
- /**
- * This method may run concurrently with
- * {@link #updateKey()} and {@link #onSelected()}.
- */
-
- int oldInterestOps;
- int newInterestOps;
- boolean pending;
- synchronized (this)
- {
- pending = _updatePending;
- oldInterestOps = _desiredInterestOps;
- newInterestOps = oldInterestOps | operation;
- if (newInterestOps != oldInterestOps)
- _desiredInterestOps = newInterestOps;
- }
-
- if (LOG.isDebugEnabled())
- LOG.debug("changeInterests p={} {}->{} for {}", pending, oldInterestOps, newInterestOps, this);
-
- if (!pending && _selector != null)
- _selector.submit(_updateKeyAction);
- }
-
- @Override
- public String toEndPointString()
- {
- // We do a best effort to print the right toString() and that's it.
- return String.format("%s{io=%d/%d,kio=%d,kro=%d}",
- super.toEndPointString(),
- _currentInterestOps,
- _desiredInterestOps,
- ManagedSelector.safeInterestOps(_key),
- ManagedSelector.safeReadyOps(_key));
- }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index a997de63001..e5d8730b10c 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.io;
-import java.io.Closeable;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -253,7 +252,7 @@ public class ClientConnector extends ContainerLifeCycle
// exception is being thrown, so we attempt to provide a better error message.
if (x.getClass() == SocketException.class)
x = new SocketException("Could not connect to " + address).initCause(x);
- safeClose(channel);
+ IO.close(channel);
connectFailed(x, context);
}
}
@@ -273,23 +272,23 @@ public class ClientConnector extends ContainerLifeCycle
{
if (LOG.isDebugEnabled())
LOG.debug("Could not accept {}", channel);
- safeClose(channel);
+ IO.close(channel);
Promise> promise = (Promise>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
if (promise != null)
promise.failed(failure);
}
}
- protected void safeClose(Closeable closeable)
- {
- IO.close(closeable);
- }
-
protected void configure(SocketChannel channel) throws IOException
{
channel.socket().setTcpNoDelay(true);
}
+ protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey)
+ {
+ return new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
+ }
+
protected void connectFailed(Throwable failure, Map context)
{
if (LOG.isDebugEnabled())
@@ -309,7 +308,7 @@ public class ClientConnector extends ContainerLifeCycle
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
{
- SocketChannelEndPoint endPoint = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
+ EndPoint endPoint = ClientConnector.this.newEndPoint((SocketChannel)channel, selector, selectionKey);
endPoint.setIdleTimeout(getIdleTimeout().toMillis());
return endPoint;
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
index fbdd71ad77b..47651f816a0 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
@@ -80,7 +80,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
private final AtomicBoolean _started = new AtomicBoolean(false);
- private boolean _selecting = false;
+ private boolean _selecting;
private final SelectorManager _selectorManager;
private final int _id;
private final ExecutionStrategy _strategy;
@@ -123,22 +123,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
start._started.await();
}
- protected void onSelectFailed(Throwable cause)
- {
- // override to change behavior
- }
-
- public int size()
- {
- Selector s = _selector;
- if (s == null)
- return 0;
- Set keys = s.keys();
- if (keys == null)
- return 0;
- return keys.size();
- }
-
@Override
protected void doStop() throws Exception
{
@@ -160,22 +144,119 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
super.doStop();
}
+ protected int nioSelect(Selector selector, boolean now) throws IOException
+ {
+ return now ? selector.selectNow() : selector.select();
+ }
+
+ protected int select(Selector selector) throws IOException
+ {
+ try
+ {
+ int selected = nioSelect(selector, false);
+ if (selected == 0)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Selector {} woken with none selected", selector);
+
+ if (Thread.interrupted() && !isRunning())
+ throw new ClosedSelectorException();
+
+ if (FORCE_SELECT_NOW)
+ selected = nioSelect(selector, true);
+ }
+ return selected;
+ }
+ catch (ClosedSelectorException x)
+ {
+ throw x;
+ }
+ catch (Throwable x)
+ {
+ handleSelectFailure(selector, x);
+ return 0;
+ }
+ }
+
+ protected void handleSelectFailure(Selector selector, Throwable failure) throws IOException
+ {
+ LOG.info("Caught select() failure, trying to recover: {}", failure.toString());
+ if (LOG.isDebugEnabled())
+ LOG.debug("", failure);
+
+ Selector newSelector = _selectorManager.newSelector();
+ for (SelectionKey oldKey : selector.keys())
+ {
+ SelectableChannel channel = oldKey.channel();
+ int interestOps = safeInterestOps(oldKey);
+ if (interestOps >= 0)
+ {
+ try
+ {
+ Object attachment = oldKey.attachment();
+ SelectionKey newKey = channel.register(newSelector, interestOps, attachment);
+ if (attachment instanceof Selectable)
+ ((Selectable)attachment).replaceKey(newKey);
+ oldKey.cancel();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Transferred {} iOps={} att={}", channel, interestOps, attachment);
+ }
+ catch (Throwable t)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not transfer {}", channel, t);
+ IO.close(channel);
+ }
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Invalid interestOps for {}", channel);
+ IO.close(channel);
+ }
+ }
+
+ IO.close(selector);
+ _selector = newSelector;
+ }
+
+ protected void onSelectFailed(Throwable cause)
+ {
+ // override to change behavior
+ }
+
+ public int size()
+ {
+ Selector s = _selector;
+ if (s == null)
+ return 0;
+ Set keys = s.keys();
+ if (keys == null)
+ return 0;
+ return keys.size();
+ }
+
/**
* Submit an {@link SelectorUpdate} to be acted on between calls to {@link Selector#select()}
*
* @param update The selector update to apply at next wakeup
*/
public void submit(SelectorUpdate update)
+ {
+ submit(update, false);
+ }
+
+ private void submit(SelectorUpdate update, boolean lazy)
{
if (LOG.isDebugEnabled())
- LOG.debug("Queued change {} on {}", update, this);
+ LOG.debug("Queued change lazy={} {} on {}", lazy, update, this);
Selector selector = null;
synchronized (ManagedSelector.this)
{
_updates.offer(update);
- if (_selecting)
+ if (_selecting && !lazy)
{
selector = _selector;
// To avoid the extra select wakeup.
@@ -223,7 +304,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
}
- private void processConnect(SelectionKey key, final Connect connect)
+ private void processConnect(SelectionKey key, Connect connect)
{
SelectableChannel channel = key.channel();
try
@@ -271,7 +352,18 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
Object context = selectionKey.attachment();
Connection connection = _selectorManager.newConnection(channel, endPoint, context);
endPoint.setConnection(connection);
- selectionKey.attach(endPoint);
+ submit(selector ->
+ {
+ SelectionKey key = selectionKey;
+ if (key.selector() != selector)
+ {
+ key = channel.keyFor(selector);
+ if (key != null && endPoint instanceof Selectable)
+ ((Selectable)endPoint).replaceKey(key);
+ }
+ if (key != null)
+ key.attach(endPoint);
+ }, true);
endPoint.onOpen();
endPointOpened(endPoint);
_selectorManager.connectionOpened(connection, context);
@@ -279,7 +371,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
LOG.debug("Created {}", endPoint);
}
- public void destroyEndPoint(final EndPoint endPoint, Throwable cause)
+ void destroyEndPoint(EndPoint endPoint, Throwable cause)
{
// Waking up the selector is necessary to clean the
// cancelled-key set and tell the TCP stack that the
@@ -330,8 +422,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
Selector selector = _selector;
if (selector != null && selector.isOpen())
{
- final DumpKeys dump = new DumpKeys();
- final String updatesAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
+ DumpKeys dump = new DumpKeys();
+ String updatesAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
synchronized (ManagedSelector.this)
{
updates = new ArrayList<>(_updates);
@@ -387,6 +479,14 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
* {@link ManagedSelector} for this endpoint have been processed.
*/
void updateKey();
+
+ /**
+ * Callback method invoked when the SelectionKey is replaced
+ * because the channel has been moved to a new selector.
+ *
+ * @param newKey the new SelectionKey
+ */
+ void replaceKey(SelectionKey newKey);
}
private class SelectorProducer implements ExecutionStrategy.Producer
@@ -434,9 +534,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
LOG.debug("update {}", update);
update.update(_selector);
}
- catch (Throwable th)
+ catch (Throwable x)
{
- LOG.warn("Cannot update selector {}", _selector, th);
+ LOG.warn("Cannot update selector {}", ManagedSelector.this, x);
}
}
_updateable.clear();
@@ -466,39 +566,33 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
try
{
Selector selector = _selector;
- if (selector != null && selector.isOpen())
+ if (selector != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Selector {} waiting with {} keys", selector, selector.keys().size());
- int selected = selector.select();
- if (selected == 0)
+ int selected = ManagedSelector.this.select(selector);
+ // The selector may have been recreated.
+ selector = _selector;
+ if (selector != null)
{
if (LOG.isDebugEnabled())
- LOG.debug("Selector {} woken with none selected", selector);
+ LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size());
- if (Thread.interrupted() && !isRunning())
- throw new ClosedSelectorException();
+ int updates;
+ synchronized (ManagedSelector.this)
+ {
+ // finished selecting
+ _selecting = false;
+ updates = _updates.size();
+ }
- if (FORCE_SELECT_NOW)
- selected = selector.selectNow();
+ _keys = selector.selectedKeys();
+ _cursor = _keys.isEmpty() ? Collections.emptyIterator() : _keys.iterator();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Selector {} processing {} keys, {} updates", selector, _keys.size(), updates);
+
+ return true;
}
- if (LOG.isDebugEnabled())
- LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size());
-
- int updates;
- synchronized (ManagedSelector.this)
- {
- // finished selecting
- _selecting = false;
- updates = _updates.size();
- }
-
- _keys = selector.selectedKeys();
- _cursor = _keys.isEmpty() ? Collections.emptyIterator() : _keys.iterator();
- if (LOG.isDebugEnabled())
- LOG.debug("Selector {} processing {} keys, {} updates", selector, _keys.size(), updates);
-
- return true;
}
}
catch (Throwable x)
@@ -514,7 +608,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
else
{
LOG.warn(x.toString());
- LOG.debug("select() failure", x);
+ if (LOG.isDebugEnabled())
+ LOG.debug("select() failure", x);
}
}
return false;
@@ -525,9 +620,10 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
while (_cursor.hasNext())
{
SelectionKey key = _cursor.next();
+ Object attachment = key.attachment();
+ SelectableChannel channel = key.channel();
if (key.isValid())
{
- Object attachment = key.attachment();
if (LOG.isDebugEnabled())
LOG.debug("selected {} {} {} ", safeReadyOps(key), key, attachment);
try
@@ -550,24 +646,21 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
catch (CancelledKeyException x)
{
- LOG.debug("Ignoring cancelled key for channel {}", key.channel());
- if (attachment instanceof EndPoint)
- IO.close((EndPoint)attachment);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring cancelled key for channel {}", channel);
+ IO.close(attachment instanceof EndPoint ? (EndPoint)attachment : channel);
}
catch (Throwable x)
{
- LOG.warn("Could not process key for channel " + key.channel(), x);
- if (attachment instanceof EndPoint)
- IO.close((EndPoint)attachment);
+ LOG.warn("Could not process key for channel {}", channel, x);
+ IO.close(attachment instanceof EndPoint ? (EndPoint)attachment : channel);
}
}
else
{
if (LOG.isDebugEnabled())
- LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
- Object attachment = key.attachment();
- if (attachment instanceof EndPoint)
- IO.close((EndPoint)attachment);
+ LOG.debug("Selector loop ignoring invalid key for channel {}", channel);
+ IO.close(attachment instanceof EndPoint ? (EndPoint)attachment : channel);
}
}
return null;
@@ -616,7 +709,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
private static class DumpKeys implements SelectorUpdate
{
- private CountDownLatch latch = new CountDownLatch(1);
+ private final CountDownLatch latch = new CountDownLatch(1);
private List keys;
@Override
@@ -652,9 +745,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
private final SelectableChannel _channel;
private SelectionKey _key;
- public Acceptor(SelectableChannel channel)
+ Acceptor(SelectableChannel channel)
{
- this._channel = channel;
+ _channel = channel;
}
@Override
@@ -662,31 +755,26 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
try
{
- if (_key == null)
- {
- _key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);
- }
-
+ _key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);
if (LOG.isDebugEnabled())
- LOG.debug("{} acceptor={}", this, _key);
+ LOG.debug("{} acceptor={}", this, _channel);
}
catch (Throwable x)
{
IO.close(_channel);
- LOG.warn("Unable to register OP_ACCEPT on selector", x);
+ LOG.warn("Unable to register OP_ACCEPT on selector for {}", _channel, x);
}
}
@Override
public Runnable onSelected()
{
- SelectableChannel server = _key.channel();
SelectableChannel channel = null;
try
{
while (true)
{
- channel = _selectorManager.doAccept(server);
+ channel = _selectorManager.doAccept(_channel);
if (channel == null)
break;
_selectorManager.accepted(channel);
@@ -694,10 +782,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
catch (Throwable x)
{
+ LOG.warn("Accept failed for channel {}", channel, x);
IO.close(channel);
- LOG.warn("Accept failed for channel " + channel, x);
}
-
return null;
}
@@ -706,13 +793,18 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
}
+ @Override
+ public void replaceKey(SelectionKey newKey)
+ {
+ _key = newKey;
+ }
+
@Override
public void close() throws IOException
{
- SelectionKey key = _key;
- _key = null;
- if (key != null && key.isValid())
- key.cancel();
+ // May be called from any thread.
+ // Implements AbstractConnector.setAccepting(boolean).
+ submit(selector -> _key.cancel());
}
}
@@ -732,7 +824,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
@Override
public void close()
{
- LOG.debug("closed accept of {}", channel);
+ if (LOG.isDebugEnabled())
+ LOG.debug("closed accept of {}", channel);
IO.close(channel);
}
@@ -748,7 +841,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
IO.close(channel);
_selectorManager.onAcceptFailed(channel, x);
- LOG.debug("Unable to register update for accept", x);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not register channel after accept {}", channel, x);
}
}
@@ -762,7 +856,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
catch (Throwable x)
{
- LOG.debug("Unable to accept", x);
failed(x);
}
}
@@ -770,10 +863,17 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
protected void failed(Throwable failure)
{
IO.close(channel);
- LOG.warn("ManagedSelector#Accept failure : {}", Objects.toString(failure));
- LOG.debug("ManagedSelector#Accept failure", failure);
+ LOG.warn("Could not accept {}: {}", channel, String.valueOf(failure));
+ if (LOG.isDebugEnabled())
+ LOG.debug("", failure);
_selectorManager.onAcceptFailed(channel, failure);
}
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), channel);
+ }
}
class Connect implements SelectorUpdate, Runnable
@@ -833,16 +933,15 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
private class CloseConnections implements SelectorUpdate
{
- final Set _closed;
- final CountDownLatch _noEndPoints = new CountDownLatch(1);
- final CountDownLatch _complete = new CountDownLatch(1);
+ private final Set _closed;
+ private final CountDownLatch _complete = new CountDownLatch(1);
- public CloseConnections()
+ private CloseConnections()
{
this(null);
}
- public CloseConnections(Set closed)
+ private CloseConnections(Set closed)
{
_closed = closed;
}
@@ -852,7 +951,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
if (LOG.isDebugEnabled())
LOG.debug("Closing {} connections on {}", selector.keys().size(), ManagedSelector.this);
- boolean zero = true;
for (SelectionKey key : selector.keys())
{
if (key != null && key.isValid())
@@ -861,14 +959,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
Object attachment = key.attachment();
if (attachment instanceof EndPoint)
{
- EndPoint endp = (EndPoint)attachment;
- if (!endp.isOutputShutdown())
- zero = false;
- Connection connection = endp.getConnection();
- if (connection != null)
- closeable = connection;
- else
- closeable = endp;
+ EndPoint endPoint = (EndPoint)attachment;
+ Connection connection = endPoint.getConnection();
+ closeable = Objects.requireNonNullElse(connection, endPoint);
}
if (closeable != null)
@@ -885,30 +978,26 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
}
}
-
- if (zero)
- _noEndPoints.countDown();
_complete.countDown();
}
}
private class StopSelector implements SelectorUpdate
{
- CountDownLatch _stopped = new CountDownLatch(1);
+ private final CountDownLatch _stopped = new CountDownLatch(1);
@Override
public void update(Selector selector)
{
for (SelectionKey key : selector.keys())
{
- if (key != null && key.isValid())
- {
- Object attachment = key.attachment();
- if (attachment instanceof EndPoint)
- IO.close((EndPoint)attachment);
- }
+ // Key may be null when using the UnixSocket selector.
+ if (key == null)
+ continue;
+ Object attachment = key.attachment();
+ if (attachment instanceof Closeable)
+ IO.close((Closeable)attachment);
}
-
_selector = null;
IO.close(selector);
_stopped.countDown();
@@ -936,8 +1025,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
catch (Throwable failure)
{
IO.close(_connect.channel);
- LOG.warn("ManagedSelector#CreateEndpoint failure : {}", Objects.toString(failure));
- LOG.debug("ManagedSelector#CreateEndpoint failure", failure);
+ LOG.warn("Could not create EndPoint {}: {}", _connect.channel, String.valueOf(failure));
+ if (LOG.isDebugEnabled())
+ LOG.debug("", failure);
_connect.failed(failure);
}
}
@@ -945,7 +1035,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
@Override
public String toString()
{
- return String.format("CreateEndPoint@%x{%s,%s}", hashCode(), _connect, _key);
+ return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _connect);
}
}
@@ -954,7 +1044,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
private final EndPoint endPoint;
private final Throwable cause;
- public DestroyEndPoint(EndPoint endPoint, Throwable cause)
+ private DestroyEndPoint(EndPoint endPoint, Throwable cause)
{
this.endPoint = endPoint;
this.cause = cause;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
index 81fe1c04c88..272c0ecc0ac 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
@@ -20,8 +20,8 @@ package org.eclipse.jetty.io;
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
@@ -36,7 +36,7 @@ public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint
private final NetworkTrafficListener listener;
- public NetworkTrafficSocketChannelEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, NetworkTrafficListener listener)
+ public NetworkTrafficSocketChannelEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, NetworkTrafficListener listener)
{
super(channel, selectSet, key, scheduler);
setIdleTimeout(idleTimeout);
@@ -80,7 +80,7 @@ public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint
{
try
{
- listener.opened(getSocket());
+ listener.opened(getChannel().socket());
}
catch (Throwable x)
{
@@ -97,7 +97,7 @@ public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint
{
try
{
- listener.closed(getSocket());
+ listener.closed(getChannel().socket());
}
catch (Throwable x)
{
@@ -113,7 +113,7 @@ public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint
try
{
ByteBuffer view = buffer.asReadOnlyBuffer();
- listener.incoming(getSocket(), view);
+ listener.incoming(getChannel().socket(), view);
}
catch (Throwable x)
{
@@ -128,7 +128,7 @@ public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint
{
try
{
- listener.outgoing(getSocket(), view);
+ listener.outgoing(getChannel().socket(), view);
}
catch (Throwable x)
{
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
index 1bcd0080ed8..81b034f4a2d 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -195,7 +195,7 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
*/
public void accept(SelectableChannel channel, Object attachment)
{
- final ManagedSelector selector = chooseSelector();
+ ManagedSelector selector = chooseSelector();
selector.submit(selector.new Accept(channel, attachment));
}
@@ -210,7 +210,7 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
*/
public Closeable acceptor(SelectableChannel server)
{
- final ManagedSelector selector = chooseSelector();
+ ManagedSelector selector = chooseSelector();
ManagedSelector.Acceptor acceptor = selector.new Acceptor(server);
selector.submit(acceptor);
return acceptor;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
index 23809302b8c..e662c104b58 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
@@ -18,53 +18,165 @@
package org.eclipse.jetty.io;
+import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.nio.channels.SelectableChannel;
+import java.nio.ByteBuffer;
+import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class SocketChannelEndPoint extends ChannelEndPoint
+/**
+ * Channel End Point.
+ * Holds the channel and socket for an NIO endpoint.
+ */
+public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSelector.Selectable
{
private static final Logger LOG = LoggerFactory.getLogger(SocketChannelEndPoint.class);
- private final Socket _socket;
- private final InetSocketAddress _local;
- private final InetSocketAddress _remote;
- public SocketChannelEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
+ private final SocketChannel _channel;
+ private final ManagedSelector _selector;
+ private SelectionKey _key;
+ private boolean _updatePending;
+ // The current value for interestOps.
+ private int _currentInterestOps;
+ // The desired value for interestOps.
+ private int _desiredInterestOps;
+
+ private abstract class RunnableTask implements Runnable, Invocable
{
- this((SocketChannel)channel, selector, key, scheduler);
+ final String _operation;
+
+ protected RunnableTask(String op)
+ {
+ _operation = op;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s:%s:%s", SocketChannelEndPoint.this, _operation, getInvocationType());
+ }
}
+ private abstract class RunnableCloseable extends RunnableTask implements Closeable
+ {
+ protected RunnableCloseable(String op)
+ {
+ super(op);
+ }
+
+ @Override
+ public void close()
+ {
+ try
+ {
+ SocketChannelEndPoint.this.close();
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("Unable to close {}", SocketChannelEndPoint.this, x);
+ }
+ }
+ }
+
+ private final ManagedSelector.SelectorUpdate _updateKeyAction = this::updateKeyAction;
+
+ private final Runnable _runFillable = new RunnableCloseable("runFillable")
+ {
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return getFillInterest().getCallbackInvocationType();
+ }
+
+ @Override
+ public void run()
+ {
+ getFillInterest().fillable();
+ }
+ };
+
+ private final Runnable _runCompleteWrite = new RunnableCloseable("runCompleteWrite")
+ {
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return getWriteFlusher().getCallbackInvocationType();
+ }
+
+ @Override
+ public void run()
+ {
+ getWriteFlusher().completeWrite();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s:%s:%s->%s", SocketChannelEndPoint.this, _operation, getInvocationType(), getWriteFlusher());
+ }
+ };
+
+ private final Runnable _runCompleteWriteFillable = new RunnableCloseable("runCompleteWriteFillable")
+ {
+ @Override
+ public InvocationType getInvocationType()
+ {
+ InvocationType fillT = getFillInterest().getCallbackInvocationType();
+ InvocationType flushT = getWriteFlusher().getCallbackInvocationType();
+ if (fillT == flushT)
+ return fillT;
+
+ if (fillT == InvocationType.EITHER && flushT == InvocationType.NON_BLOCKING)
+ return InvocationType.EITHER;
+
+ if (fillT == InvocationType.NON_BLOCKING && flushT == InvocationType.EITHER)
+ return InvocationType.EITHER;
+
+ return InvocationType.BLOCKING;
+ }
+
+ @Override
+ public void run()
+ {
+ getWriteFlusher().completeWrite();
+ getFillInterest().fillable();
+ }
+ };
+
public SocketChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
{
- super(channel, selector, key, scheduler);
-
- _socket = channel.socket();
- _local = (InetSocketAddress)_socket.getLocalSocketAddress();
- _remote = (InetSocketAddress)_socket.getRemoteSocketAddress();
- }
-
- public Socket getSocket()
- {
- return _socket;
+ super(scheduler);
+ _channel = channel;
+ _selector = selector;
+ _key = key;
}
@Override
public InetSocketAddress getLocalAddress()
{
- return _local;
+ return (InetSocketAddress)_channel.socket().getLocalSocketAddress();
}
@Override
public InetSocketAddress getRemoteAddress()
{
- return _remote;
+ return (InetSocketAddress)_channel.socket().getRemoteSocketAddress();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return _channel.isOpen();
}
@Override
@@ -72,12 +184,250 @@ public class SocketChannelEndPoint extends ChannelEndPoint
{
try
{
- if (!_socket.isOutputShutdown())
- _socket.shutdownOutput();
+ Socket socket = _channel.socket();
+ if (!socket.isOutputShutdown())
+ socket.shutdownOutput();
+ }
+ catch (IOException e)
+ {
+ LOG.debug("Could not shutdown output for {}", _channel, e);
+ }
+ }
+
+ @Override
+ public void doClose()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("doClose {}", this);
+ try
+ {
+ _channel.close();
+ }
+ catch (IOException e)
+ {
+ LOG.debug("Unable to close channel", e);
+ }
+ finally
+ {
+ super.doClose();
+ }
+ }
+
+ @Override
+ public void onClose(Throwable cause)
+ {
+ try
+ {
+ super.onClose(cause);
+ }
+ finally
+ {
+ if (_selector != null)
+ _selector.destroyEndPoint(this, cause);
+ }
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (isInputShutdown())
+ return -1;
+
+ int pos = BufferUtil.flipToFill(buffer);
+ int filled;
+ try
+ {
+ filled = _channel.read(buffer);
+ if (filled > 0)
+ notIdle();
+ else if (filled == -1)
+ shutdownInput();
}
catch (IOException e)
{
LOG.debug("Unable to shutdown output", e);
+ shutdownInput();
+ filled = -1;
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(buffer, pos);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("filled {} {}", filled, BufferUtil.toDetailString(buffer));
+ return filled;
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ long flushed;
+ try
+ {
+ flushed = _channel.write(buffers);
+ if (LOG.isDebugEnabled())
+ LOG.debug("flushed {} {}", flushed, this);
+ }
+ catch (IOException e)
+ {
+ throw new EofException(e);
+ }
+
+ if (flushed > 0)
+ notIdle();
+
+ for (ByteBuffer b : buffers)
+ {
+ if (!BufferUtil.isEmpty(b))
+ return false;
+ }
+
+ return true;
+ }
+
+ public SocketChannel getChannel()
+ {
+ return _channel;
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return _channel;
+ }
+
+ @Override
+ protected void needsFillInterest()
+ {
+ changeInterests(SelectionKey.OP_READ);
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ changeInterests(SelectionKey.OP_WRITE);
+ }
+
+ @Override
+ public Runnable onSelected()
+ {
+ // This method runs from the selector thread,
+ // possibly concurrently with changeInterests(int).
+
+ int readyOps = _key.readyOps();
+ int oldInterestOps;
+ int newInterestOps;
+ synchronized (this)
+ {
+ _updatePending = true;
+ // Remove the readyOps, that here can only be OP_READ or OP_WRITE (or both).
+ oldInterestOps = _desiredInterestOps;
+ newInterestOps = oldInterestOps & ~readyOps;
+ _desiredInterestOps = newInterestOps;
+ }
+
+ boolean fillable = (readyOps & SelectionKey.OP_READ) != 0;
+ boolean flushable = (readyOps & SelectionKey.OP_WRITE) != 0;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("onSelected {}->{} r={} w={} for {}", oldInterestOps, newInterestOps, fillable, flushable, this);
+
+ // return task to complete the job
+ Runnable task = fillable
+ ? (flushable
+ ? _runCompleteWriteFillable
+ : _runFillable)
+ : (flushable
+ ? _runCompleteWrite
+ : null);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("task {}", task);
+ return task;
+ }
+
+ private void updateKeyAction(Selector selector)
+ {
+ updateKey();
+ }
+
+ @Override
+ public void updateKey()
+ {
+ // This method runs from the selector thread,
+ // possibly concurrently with changeInterests(int).
+
+ try
+ {
+ int oldInterestOps;
+ int newInterestOps;
+ synchronized (this)
+ {
+ _updatePending = false;
+ oldInterestOps = _currentInterestOps;
+ newInterestOps = _desiredInterestOps;
+ if (oldInterestOps != newInterestOps)
+ {
+ _currentInterestOps = newInterestOps;
+ _key.interestOps(newInterestOps);
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Key interests updated {} -> {} on {}", oldInterestOps, newInterestOps, this);
+ }
+ catch (CancelledKeyException x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring key update for cancelled key {}", this, x);
+ close();
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("Ignoring key update for {}", this, x);
+ close();
}
}
+
+ @Override
+ public void replaceKey(SelectionKey newKey)
+ {
+ _key = newKey;
+ }
+
+ private void changeInterests(int operation)
+ {
+ // This method runs from any thread, possibly
+ // concurrently with updateKey() and onSelected().
+
+ int oldInterestOps;
+ int newInterestOps;
+ boolean pending;
+ synchronized (this)
+ {
+ pending = _updatePending;
+ oldInterestOps = _desiredInterestOps;
+ newInterestOps = oldInterestOps | operation;
+ if (newInterestOps != oldInterestOps)
+ _desiredInterestOps = newInterestOps;
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("changeInterests p={} {}->{} for {}", pending, oldInterestOps, newInterestOps, this);
+
+ if (!pending && _selector != null)
+ _selector.submit(_updateKeyAction);
+ }
+
+ @Override
+ public String toEndPointString()
+ {
+ // We do a best effort to print the right toString() and that's it.
+ return String.format("%s{io=%d/%d,kio=%d,kro=%d}",
+ super.toEndPointString(),
+ _currentInterestOps,
+ _desiredInterestOps,
+ ManagedSelector.safeInterestOps(_key),
+ ManagedSelector.safeReadyOps(_key));
+ }
}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
index 5d3ca7e2772..9890de59d17 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
@@ -72,11 +72,11 @@ public class SelectorManagerTest
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
{
@Override
- protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) throws IOException
+ protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
- endp.setIdleTimeout(connectTimeout / 2);
- return endp;
+ SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
+ endPoint.setIdleTimeout(connectTimeout / 2);
+ return endPoint;
}
@Override
@@ -96,7 +96,7 @@ public class SelectorManagerTest
}
@Override
- public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
+ public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment)
{
((Callback)attachment).succeeded();
return new AbstractConnection(endpoint, executor)
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointInterestsTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointInterestsTest.java
index 3ee35a3f160..94f79c12d45 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointInterestsTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointInterestsTest.java
@@ -69,7 +69,7 @@ public class SocketChannelEndPointInterestsTest
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler())
+ SocketChannelEndPoint endp = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler())
{
@Override
protected void onIncompleteFlush()
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointTest.java
index 537174b8625..595ddb52115 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SocketChannelEndPointTest.java
@@ -465,10 +465,10 @@ public class SocketChannelEndPointTest
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
{
- SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
- _lastEndPoint = endp;
+ SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, selectionKey, getScheduler());
+ _lastEndPoint = endPoint;
_lastEndPointLatch.countDown();
- return endp;
+ return endPoint;
}
@Override
@@ -580,11 +580,11 @@ public class SocketChannelEndPointTest
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
- endp.setIdleTimeout(60000);
- _lastEndPoint = endp;
+ SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
+ endPoint.setIdleTimeout(60000);
+ _lastEndPoint = endPoint;
_lastEndPointLatch.countDown();
- return endp;
+ return endPoint;
}
@Override
@@ -743,7 +743,7 @@ public class SocketChannelEndPointTest
return;
}
- EndPoint endp = getEndPoint();
+ EndPoint endPoint = getEndPoint();
try
{
_last = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
@@ -756,17 +756,17 @@ public class SocketChannelEndPointTest
BufferUtil.compact(_in);
if (BufferUtil.isFull(_in))
throw new IllegalStateException("FULL " + BufferUtil.toDetailString(_in));
- int filled = endp.fill(_in);
+ int filled = endPoint.fill(_in);
if (filled > 0)
progress = true;
// If the tests wants to block, then block
- while (_blockAt.get() > 0 && endp.isOpen() && _in.remaining() < _blockAt.get())
+ while (_blockAt.get() > 0 && endPoint.isOpen() && _in.remaining() < _blockAt.get())
{
FutureCallback future = _blockingRead = new FutureCallback();
fillInterested();
future.get();
- filled = endp.fill(_in);
+ filled = endPoint.fill(_in);
progress |= filled > 0;
}
@@ -782,18 +782,18 @@ public class SocketChannelEndPointTest
for (int i = 0; i < _writeCount.get(); i++)
{
FutureCallback blockingWrite = new FutureCallback();
- endp.write(blockingWrite, out.asReadOnlyBuffer());
+ endPoint.write(blockingWrite, out.asReadOnlyBuffer());
blockingWrite.get();
}
progress = true;
}
// are we done?
- if (endp.isInputShutdown())
- endp.shutdownOutput();
+ if (endPoint.isInputShutdown())
+ endPoint.shutdownOutput();
}
- if (endp.isOpen())
+ if (endPoint.isOpen())
fillInterested();
}
catch (ExecutionException e)
@@ -802,9 +802,9 @@ public class SocketChannelEndPointTest
try
{
FutureCallback blockingWrite = new FutureCallback();
- endp.write(blockingWrite, BufferUtil.toBuffer("EE: " + BufferUtil.toString(_in)));
+ endPoint.write(blockingWrite, BufferUtil.toBuffer("EE: " + BufferUtil.toString(_in)));
blockingWrite.get();
- endp.shutdownOutput();
+ endPoint.shutdownOutput();
}
catch (Exception e2)
{
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
index e42e4f670bc..a0570c70920 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
@@ -506,7 +506,7 @@ public class ConnectHandler extends HandlerWrapper
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endPoint = new SocketChannelEndPoint(channel, selector, key, getScheduler());
+ SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
endPoint.setIdleTimeout(getIdleTimeout());
return endPoint;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
index fef244df7fd..27ca81906a7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
@@ -23,10 +23,10 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.NetworkTrafficListener;
import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -81,7 +81,7 @@ public class NetworkTrafficServerConnector extends ServerConnector
}
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new NetworkTrafficSocketChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), getNetworkTrafficListener());
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index 5baaa6988ff..23eb3d440df 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -35,7 +35,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -424,7 +423,7 @@ public class ServerConnector extends AbstractNetworkConnector
return _localPort;
}
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler());
endpoint.setIdleTimeout(getIdleTimeout());
@@ -511,9 +510,9 @@ public class ServerConnector extends AbstractNetworkConnector
}
@Override
- protected ChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
{
- return ServerConnector.this.newEndPoint((SocketChannel)channel, selectSet, selectionKey);
+ return ServerConnector.this.newEndPoint((SocketChannel)channel, selector, selectionKey);
}
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
index 39f78844028..b9d6c1486a6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
@@ -59,18 +59,18 @@ public class SocketCustomizationListener implements Listener
@Override
public void onOpened(Connection connection)
{
- EndPoint endp = connection.getEndPoint();
+ EndPoint endPoint = connection.getEndPoint();
boolean ssl = false;
- if (_ssl && endp instanceof DecryptedEndPoint)
+ if (_ssl && endPoint instanceof DecryptedEndPoint)
{
- endp = ((DecryptedEndPoint)endp).getSslConnection().getEndPoint();
+ endPoint = ((DecryptedEndPoint)endPoint).getSslConnection().getEndPoint();
ssl = true;
}
- if (endp instanceof SocketChannelEndPoint)
+ if (endPoint instanceof SocketChannelEndPoint)
{
- Socket socket = ((SocketChannelEndPoint)endp).getSocket();
+ Socket socket = ((SocketChannelEndPoint)endPoint).getChannel().socket();
customize(socket, connection.getClass(), ssl);
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
index 8b1a16d9c8c..d45008076e2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
@@ -223,7 +223,15 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
LOG.debug("{} compressing {}", this, _deflater);
_state.set(GZState.COMPRESSING);
- gzip(content, complete, callback);
+ if (BufferUtil.isEmpty(content))
+ {
+ // We are committing, but have no content to compress, so flush empty buffer to write headers.
+ _interceptor.write(BufferUtil.EMPTY_BUFFER, complete, callback);
+ }
+ else
+ {
+ gzip(content, complete, callback);
+ }
}
else
callback.failed(new WritePendingException());
@@ -406,7 +414,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
@Override
public String toString()
{
- return String.format("%s[content=%s last=%b copy=%s buffer=%s deflate=%s",
+ return String.format("%s[content=%s last=%b copy=%s buffer=%s deflate=%s %s]",
super.toString(),
BufferUtil.toDetailString(_content),
_last,
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java
index 5e4dd58847c..0d478f311d9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java
@@ -45,7 +45,6 @@ import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.tools.HttpTester;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -132,7 +131,7 @@ public class AsyncCompletionTest extends HttpServerTestFixture
})
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new ExtendedEndPoint(channel, selectSet, key, getScheduler());
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
index 25eab5cd3b9..88b6a5dbc0e 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
@@ -30,7 +30,6 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -61,7 +60,7 @@ public class ExtendedServerTest extends HttpServerTestBase
})
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new ExtendedEndPoint(channel, selectSet, key, getScheduler());
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
index 8504061b971..2bc445a2456 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
@@ -71,7 +71,7 @@ public class ServerConnectorTest
EndPoint endPoint = baseRequest.getHttpChannel().getEndPoint();
assertThat("Endpoint", endPoint, instanceOf(SocketChannelEndPoint.class));
SocketChannelEndPoint channelEndPoint = (SocketChannelEndPoint)endPoint;
- Socket socket = channelEndPoint.getSocket();
+ Socket socket = channelEndPoint.getChannel().socket();
ServerConnector connector = (ServerConnector)baseRequest.getHttpChannel().getConnector();
PrintWriter out = response.getWriter();
@@ -214,7 +214,7 @@ public class ServerConnectorTest
}
@Test
- public void testAddFirstConnectionFactory() throws Exception
+ public void testAddFirstConnectionFactory()
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
@@ -236,7 +236,7 @@ public class ServerConnectorTest
public void testExceptionWhileAccepting() throws Exception
{
Server server = new Server();
- try (StacklessLogging stackless = new StacklessLogging(AbstractConnector.class))
+ try (StacklessLogging ignored = new StacklessLogging(AbstractConnector.class))
{
AtomicLong spins = new AtomicLong();
ServerConnector connector = new ServerConnector(server, 1, 1)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
index 65660020142..50401f40b5a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
@@ -300,6 +300,29 @@ public class SniSslConnectionFactoryTest
assertThat(response.getStatus(), is(400));
}
+ @Test
+ public void testWrongSNIRejectedConnectionWithNonSNIKeystore() throws Exception
+ {
+ start(ssl ->
+ {
+ // Keystore has only one certificate, but we want to enforce SNI.
+ ssl.setKeyStorePath("src/test/resources/keystore.p12");
+ ssl.setSniRequired(true);
+ });
+
+ // Wrong SNI host.
+ assertThrows(SSLHandshakeException.class, () -> getResponse("wrong.com", "wrong.com", null));
+
+ // No SNI host.
+ assertThrows(SSLHandshakeException.class, () -> getResponse(null, "wrong.com", null));
+
+ // Good SNI host.
+ HttpTester.Response response = HttpTester.parseResponse(getResponse("localhost", "localhost", null));
+
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(200));
+ }
+
@Test
public void testSameConnectionRequestsForManyDomains() throws Exception
{
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerCommitTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerCommitTest.java
new file mode 100644
index 00000000000..ca7a4ee4e28
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerCommitTest.java
@@ -0,0 +1,130 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GzipHandlerCommitTest
+{
+ private Server server;
+ private HttpClient client;
+
+ public void start(Servlet servlet) throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(0);
+ server.addConnector(connector);
+
+ ServletContextHandler contextHandler = new ServletContextHandler();
+ contextHandler.setContextPath("/");
+ ServletHolder servletHolder = new ServletHolder(servlet);
+ contextHandler.addServlet(servletHolder, "/test/*");
+
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setHandler(contextHandler);
+
+ server.setHandler(gzipHandler);
+ server.start();
+
+ client = new HttpClient();
+ client.start();
+ }
+
+ @AfterEach
+ public void tearDown()
+ {
+ LifeCycle.stop(client);
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testImmediateFlushNoContent() throws Exception
+ {
+ CountDownLatch latch = new CountDownLatch(1);
+ start(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.flushBuffer();
+ assertDoesNotThrow(() -> assertTrue(latch.await(1, TimeUnit.SECONDS)));
+ }
+ });
+
+ URI uri = server.getURI().resolve("/test/");
+ Request request = client.newRequest(uri);
+ request.header(HttpHeader.CONNECTION, "Close");
+ request.onResponseHeaders((r) -> latch.countDown());
+ ContentResponse response = request.send();
+ assertThat("Response status", response.getStatus(), is(200));
+ }
+
+ @Test
+ public void testImmediateFlushWithContent() throws Exception
+ {
+ int size = 8000;
+ CountDownLatch latch = new CountDownLatch(1);
+ start(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.flushBuffer();
+ assertDoesNotThrow(() -> assertTrue(latch.await(1, TimeUnit.SECONDS)));
+ response.getOutputStream();
+ byte[] buf = new byte[size];
+ Arrays.fill(buf, (byte)'a');
+ response.getOutputStream().write(buf);
+ }
+ });
+
+ URI uri = server.getURI().resolve("/test/");
+ Request request = client.newRequest(uri);
+ request.header(HttpHeader.CONNECTION, "Close");
+ request.onResponseHeaders((r) -> latch.countDown());
+ ContentResponse response = request.send();
+ assertThat("Response status", response.getStatus(), is(200));
+ assertThat("Response content size", response.getContent().length, is(size));
+ }
+}
diff --git a/jetty-servlet/src/test/resources/jetty-logging.properties b/jetty-servlet/src/test/resources/jetty-logging.properties
index 821cf3d2f35..bd3b391a3dc 100644
--- a/jetty-servlet/src/test/resources/jetty-logging.properties
+++ b/jetty-servlet/src/test/resources/jetty-logging.properties
@@ -1,8 +1,7 @@
# Jetty Logging using jetty-slf4j-impl
-org.eclipse.jetty.LEVEL=INFO
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.servlet.LEVEL=DEBUG
-#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
+#org.eclipse.jetty.io.SocketChannelEndPoint.LEVEL=DEBUG
#org.eclipse.jetty.server.DebugListener.LEVEL=DEBUG
-#org.eclipse.jetty.server.HttpChannelState.LEVEL=DEBUG
\ No newline at end of file
+#org.eclipse.jetty.server.HttpChannelState.LEVEL=DEBUG
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractFileContentServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractFileContentServlet.java
new file mode 100644
index 00000000000..0857532c93c
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractFileContentServlet.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.servlet.http.HttpServlet;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public abstract class AbstractFileContentServlet extends HttpServlet
+{
+ protected byte[] loadContentFileBytes(final String fileName) throws IOException
+ {
+ String relPath = fileName;
+ relPath = relPath.replaceFirst("^/context/", "");
+ relPath = relPath.replaceFirst("^/", "");
+
+ String realPath = getServletContext().getRealPath(relPath);
+ assertNotNull(realPath, "Unable to find real path for " + relPath);
+
+ Path realFile = Paths.get(realPath);
+ assertTrue(Files.exists(realFile), "Content File should exist: " + realFile);
+
+ return Files.readAllBytes(realFile);
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractGzipTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractGzipTest.java
new file mode 100644
index 00000000000..b47970ae58e
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractGzipTest.java
@@ -0,0 +1,159 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.TypeUtil;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public abstract class AbstractGzipTest
+{
+ protected static final int DEFAULT_OUTPUT_BUFFER_SIZE = new HttpConfiguration().getOutputBufferSize();
+
+ protected Path workDir;
+
+ public AbstractGzipTest()
+ {
+ workDir = MavenTestingUtils.getTargetTestingPath(this.getClass().getName());
+ FS.ensureEmpty(workDir);
+ }
+
+ protected FilterInputStream newContentEncodingFilterInputStream(String contentEncoding, InputStream inputStream) throws IOException
+ {
+ if (contentEncoding == null)
+ {
+ return new FilterInputStream(inputStream) {};
+ }
+ else if (contentEncoding.contains(GzipHandler.GZIP))
+ {
+ return new GZIPInputStream(inputStream);
+ }
+ else if (contentEncoding.contains(GzipHandler.DEFLATE))
+ {
+ return new InflaterInputStream(inputStream, new Inflater(true));
+ }
+ throw new RuntimeException("Unexpected response content-encoding: " + contentEncoding);
+ }
+
+ protected UncompressedMetadata parseResponseContent(HttpTester.Response response) throws NoSuchAlgorithmException, IOException
+ {
+ UncompressedMetadata metadata = new UncompressedMetadata();
+ metadata.contentLength = response.getContentBytes().length;
+
+ String contentEncoding = response.get("Content-Encoding");
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(response.getContentBytes());
+ FilterInputStream streamFilter = newContentEncodingFilterInputStream(contentEncoding, bais);
+ ByteArrayOutputStream uncompressedStream = new ByteArrayOutputStream(metadata.contentLength);
+ DigestOutputStream digester = new DigestOutputStream(uncompressedStream, digest))
+ {
+ IO.copy(streamFilter, digester);
+ metadata.uncompressedContent = uncompressedStream.toByteArray();
+ metadata.uncompressedSize = metadata.uncompressedContent.length;
+ // Odd toUpperCase is because TypeUtil.toHexString is mixed case results!??
+ metadata.uncompressedSha1Sum = TypeUtil.toHexString(digest.digest()).toUpperCase(Locale.ENGLISH);
+ return metadata;
+ }
+ }
+
+ protected Path createFile(Path contextDir, String fileName, int fileSize) throws IOException
+ {
+ Path destPath = contextDir.resolve(fileName);
+ byte[] content = generateContent(fileSize);
+ Files.write(destPath, content, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+ return destPath;
+ }
+
+ /**
+ * Generate semi-realistic text content of arbitrary length.
+ *
+ * Note: We don't just create a single string of repeating characters
+ * as that doesn't test the gzip behavior very well. (too efficient)
+ * We also don't just generate a random byte array as that is the opposite
+ * extreme of gzip handling (terribly inefficient).
+ *
+ *
+ * @param length the length of the content to generate.
+ * @return the content.
+ */
+ private byte[] generateContent(int length)
+ {
+ StringBuilder builder = new StringBuilder();
+ do
+ {
+ builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc.\n");
+ builder.append("Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque\n");
+ builder.append("habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n");
+ builder.append("Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam\n");
+ builder.append("at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate\n");
+ builder.append("velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum.\n");
+ builder.append("Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum\n");
+ builder.append("eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa\n");
+ builder.append("sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam\n");
+ builder.append("consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque.\n");
+ builder.append("Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse\n");
+ builder.append("et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.\n");
+ }
+ while (builder.length() < length);
+
+ // Make sure we are exactly at requested length. (truncate the extra)
+ if (builder.length() > length)
+ {
+ builder.setLength(length);
+ }
+
+ return builder.toString().getBytes(UTF_8);
+ }
+
+ public static class UncompressedMetadata
+ {
+ public byte[] uncompressedContent;
+ public int contentLength;
+ public String uncompressedSha1Sum;
+ public int uncompressedSize;
+
+ public String getContentUTF8()
+ {
+ return new String(uncompressedContent, UTF_8);
+ }
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncScheduledDispatchWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncScheduledDispatchWrite.java
index d446eb44798..62e7cc1c545 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncScheduledDispatchWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncScheduledDispatchWrite.java
@@ -31,7 +31,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
-public abstract class AsyncScheduledDispatchWrite extends TestDirContentServlet
+public abstract class AsyncScheduledDispatchWrite extends AbstractFileContentServlet
{
public static class Default extends AsyncScheduledDispatchWrite
{
@@ -103,7 +103,7 @@ public abstract class AsyncScheduledDispatchWrite extends TestDirContentServlet
}
else
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
response.setContentLength(dataBytes.length);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutCompleteWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutCompleteWrite.java
index 2e4fa66e2cc..b01361458a5 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutCompleteWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutCompleteWrite.java
@@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.nullValue;
*
*/
@SuppressWarnings("serial")
-public abstract class AsyncTimeoutCompleteWrite extends TestDirContentServlet implements AsyncListener
+public abstract class AsyncTimeoutCompleteWrite extends AbstractFileContentServlet implements AsyncListener
{
public static class Default extends AsyncTimeoutCompleteWrite
{
@@ -87,7 +87,7 @@ public abstract class AsyncTimeoutCompleteWrite extends TestDirContentServlet im
// Pass Request & Response
ctx = request.startAsync(request, response);
}
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
request.setAttribute("filename", fileName);
ctx.addListener(this);
ctx.setTimeout(20);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutDispatchWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutDispatchWrite.java
index 22ac5b177dd..8c57d9794c4 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutDispatchWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncTimeoutDispatchWrite.java
@@ -29,7 +29,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
-public class AsyncTimeoutDispatchWrite extends TestDirContentServlet implements AsyncListener
+public class AsyncTimeoutDispatchWrite extends AbstractFileContentServlet implements AsyncListener
{
public static class Default extends AsyncTimeoutDispatchWrite
{
@@ -78,7 +78,7 @@ public class AsyncTimeoutDispatchWrite extends TestDirContentServlet implements
else
{
// second pass through, as result of timeout -> dispatch
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
response.setContentLength(dataBytes.length);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthStreamTypeWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthStreamTypeWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthStreamTypeWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthStreamTypeWrite.java
index 508d64fcb32..4bf3e400c17 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthStreamTypeWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthStreamTypeWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletLengthStreamTypeWrite extends TestDirContentServlet
+public class BlockingServletLengthStreamTypeWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
response.setContentLength(dataBytes.length);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthTypeStreamWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthTypeStreamWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthTypeStreamWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthTypeStreamWrite.java
index 50fdc496486..132d6494eba 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletLengthTypeStreamWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletLengthTypeStreamWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletLengthTypeStreamWrite extends TestDirContentServlet
+public class BlockingServletLengthTypeStreamWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
response.setContentLength(dataBytes.length);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWrite.java
index 5c4896bed43..94fdf891b99 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletStreamLengthTypeWrite extends TestDirContentServlet
+public class BlockingServletStreamLengthTypeWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
ServletOutputStream out = response.getOutputStream();
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWriteWithFlush.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWriteWithFlush.java
similarity index 94%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWriteWithFlush.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWriteWithFlush.java
index a823cc32bd9..7a4830efd33 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamLengthTypeWriteWithFlush.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamLengthTypeWriteWithFlush.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletStreamLengthTypeWriteWithFlush extends TestDirContentServlet
+public class BlockingServletStreamLengthTypeWriteWithFlush extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
ServletOutputStream out = response.getOutputStream();
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamTypeLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamTypeLengthWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamTypeLengthWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamTypeLengthWrite.java
index e1d9195aa26..e40a938b930 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletStreamTypeLengthWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletStreamTypeLengthWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletStreamTypeLengthWrite extends TestDirContentServlet
+public class BlockingServletStreamTypeLengthWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
ServletOutputStream out = response.getOutputStream();
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeLengthStreamWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeLengthStreamWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeLengthStreamWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeLengthStreamWrite.java
index dc3b7bd769e..cd0e6550221 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeLengthStreamWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeLengthStreamWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletTypeLengthStreamWrite extends TestDirContentServlet
+public class BlockingServletTypeLengthStreamWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
if (fileName.endsWith("txt"))
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeStreamLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeStreamLengthWrite.java
similarity index 93%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeStreamLengthWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeStreamLengthWrite.java
index 2f255c88526..61b146f6436 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletTypeStreamLengthWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BlockingServletTypeStreamLengthWrite.java
@@ -42,12 +42,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletTypeStreamLengthWrite extends TestDirContentServlet
+public class BlockingServletTypeStreamLengthWrite extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
if (fileName.endsWith("txt"))
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipContentLengthTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipContentLengthTest.java
index fda091b8e7c..3b7134a56ae 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipContentLengthTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipContentLengthTest.java
@@ -18,20 +18,27 @@
package org.eclipse.jetty.servlets;
-import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
+import javax.servlet.Servlet;
import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.tools.HttpTester;
-import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlets.GzipTester.ContentMetadata;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.junit.jupiter.api.extension.ExtendWith;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.Sha1Sum;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.resource.PathResource;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -42,317 +49,178 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
/**
- * Test the GzipHandler support for Content-Length setting variations.
- *
- * @see http://bugs.eclipse.org/354014
+ * Test the {@code GzipHandler} support for the various ways that an App can set {@code Content-Length}.
*/
-@ExtendWith(WorkDirExtension.class)
-public class GzipContentLengthTest
+public class GzipContentLengthTest extends AbstractGzipTest
{
- public WorkDir workDir;
-
- private static final HttpConfiguration defaultHttp = new HttpConfiguration();
- private static final int LARGE = defaultHttp.getOutputBufferSize() * 8;
- private static final int MEDIUM = defaultHttp.getOutputBufferSize();
- private static final int SMALL = defaultHttp.getOutputBufferSize() / 4;
- private static final int TINY = GzipHandler.DEFAULT_MIN_GZIP_SIZE / 2;
- private static final boolean EXPECT_COMPRESSED = true;
+ enum GzipMode
+ {
+ INTERNAL, EXTERNAL
+ }
public static Stream scenarios()
{
- List ret = new ArrayList<>();
+ // The list of servlets that implement various content sending behaviors
+ // some behaviors are more sane then others, but they are all real world scenarios
+ // that we have seen or had issues reported against Jetty.
+ List> servlets = new ArrayList<>();
- ret.add(new Scenario(0, "empty.txt", !EXPECT_COMPRESSED));
- ret.add(new Scenario(TINY, "file-tiny.txt", !EXPECT_COMPRESSED));
- ret.add(new Scenario(SMALL, "file-small.txt", EXPECT_COMPRESSED));
- ret.add(new Scenario(SMALL, "file-small.mp3", !EXPECT_COMPRESSED));
- ret.add(new Scenario(MEDIUM, "file-med.txt", EXPECT_COMPRESSED));
- ret.add(new Scenario(MEDIUM, "file-medium.mp3", !EXPECT_COMPRESSED));
- ret.add(new Scenario(LARGE, "file-large.txt", EXPECT_COMPRESSED));
- ret.add(new Scenario(LARGE, "file-large.mp3", !EXPECT_COMPRESSED));
+ // AsyncContext create -> timeout -> onTimeout -> write-response -> complete
+ servlets.add(AsyncTimeoutCompleteWrite.Default.class);
+ servlets.add(AsyncTimeoutCompleteWrite.Passed.class);
+ // AsyncContext create -> timeout -> onTimeout -> dispatch -> write-response
+ servlets.add(AsyncTimeoutDispatchWrite.Default.class);
+ servlets.add(AsyncTimeoutDispatchWrite.Passed.class);
+ // AsyncContext create -> no-timeout -> scheduler.schedule -> dispatch -> write-response
+ servlets.add(AsyncScheduledDispatchWrite.Default.class);
+ servlets.add(AsyncScheduledDispatchWrite.Passed.class);
- return ret.stream().map(Arguments::of);
- }
+ // HttpOutput usage scenario from http://bugs.eclipse.org/450873
+ // 1. getOutputStream()
+ // 2. setHeader(content-type)
+ // 3. setHeader(content-length)
+ // 4. (unwrapped) HttpOutput.write(ByteBuffer)
+ servlets.add(HttpOutputWriteFileContentServlet.class);
- private void testWithGzip(Scenario scenario, Class extends TestDirContentServlet> contentServlet) throws Exception
- {
- GzipTester tester = new GzipTester(workDir.getPath(), GzipHandler.GZIP);
+ // The following blocking scenarios are from http://bugs.eclipse.org/354014
+ // Blocking
+ // 1. setHeader(content-length)
+ // 2. getOutputStream()
+ // 3. setHeader(content-type)
+ // 4. outputStream.write()
+ servlets.add(BlockingServletLengthStreamTypeWrite.class);
+ // Blocking
+ // 1. setHeader(content-length)
+ // 2. setHeader(content-type)
+ // 3. getOutputStream()
+ // 4. outputStream.write()
+ servlets.add(BlockingServletLengthTypeStreamWrite.class);
+ // Blocking
+ // 1. getOutputStream()
+ // 2. setHeader(content-length)
+ // 3. setHeader(content-type)
+ // 4. outputStream.write()
+ servlets.add(BlockingServletStreamLengthTypeWrite.class);
+ // Blocking
+ // 1. getOutputStream()
+ // 2. setHeader(content-length)
+ // 3. setHeader(content-type)
+ // 4. outputStream.write() (with frequent response flush)
+ servlets.add(BlockingServletStreamLengthTypeWriteWithFlush.class);
+ // Blocking
+ // 1. getOutputStream()
+ // 2. setHeader(content-type)
+ // 3. setHeader(content-length)
+ // 4. outputStream.write()
+ servlets.add(BlockingServletStreamTypeLengthWrite.class);
+ // Blocking
+ // 1. setHeader(content-type)
+ // 2. setHeader(content-length)
+ // 3. getOutputStream()
+ // 4. outputStream.write()
+ servlets.add(BlockingServletTypeLengthStreamWrite.class);
+ // Blocking
+ // 1. setHeader(content-type)
+ // 2. getOutputStream()
+ // 3. setHeader(content-length)
+ // 4. outputStream.write()
+ servlets.add(BlockingServletTypeStreamLengthWrite.class);
- // Add AsyncGzip Configuration
- tester.getGzipHandler().setIncludedMimeTypes("text/plain");
- tester.getGzipHandler().setIncludedPaths("*.txt", "*.mp3");
+ List scenarios = new ArrayList<>();
- // Add content servlet
- tester.setContentServlet(contentServlet);
-
- try
+ for (Class extends Servlet> servlet : servlets)
{
- String testFilename = String.format("%s-%s", contentServlet.getSimpleName(), scenario.fileName);
- File testFile = tester.prepareServerFile(testFilename, scenario.fileSize);
-
- tester.start();
-
- HttpTester.Response response = tester.executeRequest("GET", "/context/" + testFile.getName(), 5, TimeUnit.SECONDS);
-
- if (response.getStatus() != 200)
- System.err.println("DANG!!!! " + response);
-
- assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
-
- if (scenario.expectCompressed)
+ for (GzipMode gzipMode : GzipMode.values())
{
- // Must be gzip compressed
- assertThat("Content-Encoding", response.get("Content-Encoding"), containsString(GzipHandler.GZIP));
+ // Not compressible (not large enough)
+ scenarios.add(Arguments.of(gzipMode, servlet, 0, "empty.txt", false));
+ scenarios.add(Arguments.of(gzipMode, servlet, GzipHandler.DEFAULT_MIN_GZIP_SIZE / 2, "file-tiny.txt", false));
+
+ // Compressible.
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE / 2, "file-small.txt", true));
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE, "file-medium.txt", true));
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE * 4, "file-large.txt", true));
+
+ // Not compressible (not a matching Content-Type)
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE / 2, "file-small.mp3", false));
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE, "file-medium.mp3", false));
+ scenarios.add(Arguments.of(gzipMode, servlet, DEFAULT_OUTPUT_BUFFER_SIZE * 4, "file-large.mp3", false));
}
- else
- {
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(GzipHandler.GZIP)));
- }
-
- // Uncompressed content Size
- ContentMetadata content = tester.getResponseMetadata(response);
- assertThat("(Uncompressed) Content Length", content.size, is((long)scenario.fileSize));
}
- finally
+
+ return scenarios.stream();
+ }
+
+ private Server server;
+
+ @AfterEach
+ public void stopServer()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @ParameterizedTest
+ @MethodSource("scenarios")
+ public void executeScenario(GzipMode gzipMode, Class extends Servlet> contentServlet, int fileSize, String fileName, boolean compressible) throws Exception
+ {
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ servletContextHandler.addServlet(contentServlet, "/*");
+ GzipHandler gzipHandler = new GzipHandler();
+
+ switch (gzipMode)
{
- tester.stop();
+ case INTERNAL:
+ servletContextHandler.insertHandler(gzipHandler);
+ server.setHandler(servletContextHandler);
+ break;
+ case EXTERNAL:
+ gzipHandler.setHandler(servletContextHandler);
+ server.setHandler(gzipHandler);
+ break;
}
- }
- /**
- * Test with content servlet that does:
- * AsyncContext create -> timeout -> onTimeout -> write-response -> complete
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncTimeoutCompleteWriteDefault(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncTimeoutCompleteWrite.Default.class);
- }
+ Path file = createFile(contextDir, fileName, fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
- /**
- * Test with content servlet that does:
- * AsyncContext create -> timeout -> onTimeout -> write-response -> complete
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncTimeoutCompleteWritePassed(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncTimeoutCompleteWrite.Passed.class);
- }
+ server.start();
- /**
- * Test with content servlet that does:
- * AsyncContext create -> timeout -> onTimeout -> dispatch -> write-response
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncTimeoutDispatchWriteDefault(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncTimeoutDispatchWrite.Default.class);
- }
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/" + file.getFileName().toString());
- /**
- * Test with content servlet that does:
- * AsyncContext create -> timeout -> onTimeout -> dispatch -> write-response
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncTimeoutDispatchWritePassed(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncTimeoutDispatchWrite.Passed.class);
- }
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
- /**
- * Test with content servlet that does:
- * AsyncContext create -> no-timeout -> scheduler.schedule -> dispatch -> write-response
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncScheduledDispatchWriteDefault(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncScheduledDispatchWrite.Default.class);
- }
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
- /**
- * Test with content servlet that does:
- * AsyncContext create -> no-timeout -> scheduler.schedule -> dispatch -> write-response
- *
- * @throws Exception on test failure
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testAsyncScheduledDispatchWritePassed(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, AsyncScheduledDispatchWrite.Passed.class);
- }
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
- /**
- * Test with content servlet that does:
- * 1) setHeader(content-length)
- * 2) getOutputStream()
- * 3) setHeader(content-type)
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletLengthStreamTypeWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletLengthStreamTypeWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) setHeader(content-length)
- * 2) setHeader(content-type)
- * 3) getOutputStream()
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletLengthTypeStreamWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletLengthTypeStreamWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) getOutputStream()
- * 2) setHeader(content-length)
- * 3) setHeader(content-type)
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletStreamLengthTypeWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletStreamLengthTypeWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) getOutputStream()
- * 2) setHeader(content-length)
- * 3) setHeader(content-type)
- * 4) outputStream.write() (with frequent response flush)
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletStreamLengthTypeWriteWithFlush(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletStreamLengthTypeWriteWithFlush.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) getOutputStream()
- * 2) setHeader(content-type)
- * 3) setHeader(content-length)
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletStreamTypeLengthWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletStreamTypeLengthWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) setHeader(content-type)
- * 2) setHeader(content-length)
- * 3) getOutputStream()
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletTypeLengthStreamWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletTypeLengthStreamWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 1) setHeader(content-type)
- * 2) getOutputStream()
- * 3) setHeader(content-length)
- * 4) outputStream.write()
- *
- * @throws Exception on test failure
- * @see http://bugs.eclipse.org/354014
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testServletTypeStreamLengthWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletTypeStreamLengthWrite.class);
- }
-
- /**
- * Test with content servlet that does:
- * 2) getOutputStream()
- * 1) setHeader(content-type)
- * 3) setHeader(content-length)
- * 4) (unwrapped) HttpOutput.write(ByteBuffer)
- *
- * This is done to demonstrate a bug with using HttpOutput.write()
- * while also using GzipFilter
- *
- * @throws Exception on test failure
- * @see Eclipse Bug 450873
- */
- @ParameterizedTest
- @MethodSource("scenarios")
- public void testHttpOutputWrite(Scenario scenario) throws Exception
- {
- testWithGzip(scenario, TestServletBufferTypeLengthWrite.class);
- }
-
- public static class Scenario
- {
- final int fileSize;
- final String fileName;
- final boolean expectCompressed;
-
- public Scenario(int fileSize, String fileName, boolean expectCompressed)
+ // Response Content-Encoding check
+ Matcher contentEncodingMatcher = containsString(GzipHandler.GZIP);
+ if (!compressible)
{
- this.fileSize = fileSize;
- this.fileName = fileName;
- this.expectCompressed = expectCompressed;
+ contentEncodingMatcher = not(contentEncodingMatcher);
}
+ assertThat("Content-Encoding", response.get("Content-Encoding"), contentEncodingMatcher);
- @Override
- public String toString()
- {
- return String.format("%s [%,d bytes, compressed=%b]", fileName, fileSize, expectCompressed);
- }
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
}
}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultNoRecompressTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultNoRecompressTest.java
deleted file mode 100644
index 28226f3d69a..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultNoRecompressTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.stream.Stream;
-
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.toolchain.test.IO;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-/**
- * Tests {@link GzipHandler} in combination with {@link DefaultServlet} for ability to configure {@link GzipHandler} to
- * ignore recompress situations from upstream.
- */
-@ExtendWith(WorkDirExtension.class)
-public class GzipDefaultNoRecompressTest
-{
- public static Stream data()
- {
- return Arrays.asList(new Object[][]
- {
- // Some already compressed files
- {"test_quotes.gz", "application/gzip", GzipHandler.GZIP},
- {"test_quotes.br", "application/brotli", GzipHandler.GZIP},
- {"test_quotes.bz2", "application/bzip2", GzipHandler.GZIP},
- {"test_quotes.zip", "application/zip", GzipHandler.GZIP},
- {"test_quotes.rar", "application/x-rar-compressed", GzipHandler.GZIP},
- // Some images (common first)
- {"jetty_logo.png", "image/png", GzipHandler.GZIP},
- {"jetty_logo.gif", "image/gif", GzipHandler.GZIP},
- {"jetty_logo.jpeg", "image/jpeg", GzipHandler.GZIP},
- {"jetty_logo.jpg", "image/jpeg", GzipHandler.GZIP},
- // Lesser encountered images (usually found being requested from non-browser clients)
- {"jetty_logo.bmp", "image/bmp", GzipHandler.GZIP},
- {"jetty_logo.tif", "image/tiff", GzipHandler.GZIP},
- {"jetty_logo.tiff", "image/tiff", GzipHandler.GZIP},
- {"jetty_logo.xcf", "image/xcf", GzipHandler.GZIP},
- {"jetty_logo.jp2", "image/jpeg2000", GzipHandler.GZIP},
- //qvalue disables compression
- {"test_quotes.txt", "text/plain", GzipHandler.GZIP + ";q=0"},
- {"test_quotes.txt", "text/plain", GzipHandler.GZIP + "; q = 0 "}
- }).stream().map(Arguments::of);
- }
-
- public WorkDir testingdir;
-
- @ParameterizedTest
- @MethodSource("data")
- public void testNotGzipHandleredDefaultAlreadyCompressed(String alreadyCompressedFilename, String expectedContentType, String compressionType) throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- copyTestFileToServer(alreadyCompressedFilename);
-
- tester.setContentServlet(TestStaticMimeTypeServlet.class);
-
- try
- {
- tester.start();
- tester.assertIsResponseNotGziped(alreadyCompressedFilename, alreadyCompressedFilename + ".sha1", expectedContentType);
- }
- finally
- {
- tester.stop();
- }
- }
-
- private void copyTestFileToServer(String testFilename) throws IOException
- {
- File testFile = MavenTestingUtils.getTestResourceFile(testFilename);
- File outFile = testingdir.getPathFile(testFilename).toFile();
- IO.copy(testFile, outFile);
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletDeferredContentTypeTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletDeferredContentTypeTest.java
new file mode 100644
index 00000000000..ad151c0654e
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletDeferredContentTypeTest.java
@@ -0,0 +1,138 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.Sha1Sum;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.resource.PathResource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.emptyOrNullString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+/**
+ * GzipHandler setting of headers when reset and/or not compressed.
+ *
+ * The GzipHandler now sets deferred headers (content-length and etag) when it decides not to commit.
+ * Also does not allow a reset after a decision to commit
+ *
+ * Originally from http://bugs.eclipse.org/408909
+ */
+public class GzipDefaultServletDeferredContentTypeTest extends AbstractGzipTest
+{
+ private Server server;
+
+ @AfterEach
+ public void stopServer()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testIsNotGzipCompressedByDeferredContentType() throws Exception
+ {
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", new DefaultServlet()
+ {
+ @Override
+ public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
+ {
+ String uri = req.getRequestURI();
+ if (uri.endsWith(".deferred"))
+ {
+ // System.err.println("type for "+uri.substring(0,uri.length()-9)+" is "+getServletContext().getMimeType(uri.substring(0,uri.length()-9)));
+ resp.setContentType(getServletContext().getMimeType(uri.substring(0, uri.length() - 9)));
+ }
+
+ doGet(req, resp);
+ }
+ });
+ servletContextHandler.addServlet(holder, "/");
+
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setHandler(servletContextHandler);
+ server.setHandler(gzipHandler);
+
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+
+ Path file = createFile(contextDir, "file.mp3.deferred", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.mp3.deferred");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+
+ // Response Vary check
+ assertThat("Response[Vary]", response.get("Vary"), is(emptyOrNullString()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java
new file mode 100644
index 00000000000..958058db75c
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java
@@ -0,0 +1,1182 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.CompressedContentFormat;
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.Sha1Sum;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.resource.PathResource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.startsWith;
+
+/**
+ * Test the GzipHandler support when working with the {@link DefaultServlet}.
+ */
+public class GzipDefaultServletTest extends AbstractGzipTest
+{
+ private Server server;
+
+ @AfterEach
+ public void stopServer()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"POST", "WIBBLE", "GET", "HEAD"})
+ public void testIsGzipByMethod(String method) throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setIncludedMethods("POST", "WIBBLE", "GET", "HEAD");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", WibbleDefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 8;
+
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod(method); // The point of this test
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[ETag]", response.get("ETag"), startsWith("W/"));
+ assertThat("Response[ETag]", response.get("ETag"), containsString(CompressedContentFormat.GZIP._etag));
+ // A HEAD request should have similar headers, but no body
+ if (method.equals("HEAD"))
+ {
+ assertThat("Response[Content-Length]", response.get("Content-Length"), is(not(nullValue())));
+ }
+ else
+ {
+ assertThat("Response[Content-Length]", response.get("Content-Length"), is(nullValue()));
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+ }
+
+ public static class WibbleDefaultServlet extends DefaultServlet
+ {
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ switch (req.getMethod())
+ {
+ case "WIBBLE":
+ // Disregard the method given, use GET instead.
+ doGet(req, resp);
+ return;
+ default:
+ super.service(req, resp);
+ }
+ }
+ }
+
+ @Test
+ public void testIsGzipCompressedEmpty() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = 0;
+ createFile(contextDir, "file.txt", fileSize);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(0));
+ }
+
+ public static Stream compressibleSizes()
+ {
+ return Stream.of(
+ DEFAULT_OUTPUT_BUFFER_SIZE / 4,
+ DEFAULT_OUTPUT_BUFFER_SIZE,
+ DEFAULT_OUTPUT_BUFFER_SIZE * 4);
+ }
+
+ @ParameterizedTest
+ @MethodSource("compressibleSizes")
+ public void testIsGzipCompressed(int fileSize) throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @ParameterizedTest
+ @MethodSource("compressibleSizes")
+ public void testIsGzipCompressedIfModifiedSince(int fileSize) throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ long fourSecondsAgo = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - 4000;
+ request.setHeader("If-Modified-Since", DateGenerator.formatDate(fourSecondsAgo));
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[ETag]", response.get("ETag"), startsWith("W/"));
+ assertThat("Response[ETag]", response.get("ETag"), containsString(CompressedContentFormat.GZIP._etag));
+ assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testGzippedIfSVG() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("image/svg+xml");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path testResource = MavenTestingUtils.getTestResourcePath("test.svg");
+ Path file = contextDir.resolve("test.svg");
+ IO.copy(testResource.toFile(), file.toFile());
+ String expectedSha1Sum = Sha1Sum.calculate(testResource);
+ int fileSize = (int)Files.size(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/test.svg");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testNotGzipedIfNotModified() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ createFile(contextDir, "file.txt", fileSize);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ long fiveMinutesLater = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
+ request.setHeader("If-Modified-Since", DateGenerator.formatDate(fiveMinutesLater));
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.NOT_MODIFIED_304));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[ETag]", response.get("ETag"), startsWith("W/"));
+ assertThat("Response[ETag]", response.get("ETag"), not(containsString(CompressedContentFormat.GZIP._etag)));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(0));
+ }
+
+ /**
+ * Gzip incorrectly gzips when {@code Accept-Encoding: gzip; q=0}.
+ *
+ *
+ * A quality of 0 results in no compression.
+ *
+ *
+ * See: http://bugs.eclipse.org/388072
+ */
+ @Test
+ public void testIsNotGzipCompressedWithZeroQ() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE / 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip; q=0"); // TESTING THIS
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testIsGzipCompressedWithQ() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE / 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "something; q=0.1, gzip; q=0.5"); // TESTING THIS
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testIsNotGzipCompressedByContentType() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.mp3", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.mp3");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testIsNotGzipCompressedByExcludedContentType() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addExcludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testIsNotGzipCompressedByExcludedContentTypeWithCharset() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addExcludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ servletContextHandler.getMimeTypes().addMimeMapping("txt", "text/plain;charset=UTF-8");
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ holder.setInitParameter("etags", "true");
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "test_quotes.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/test_quotes.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testUserAgentExclusionNoUAProvided() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+ gzipHandler.setExcludedAgentPatterns("bar", "foo");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ // INTENTIONALLY NOT SET - request.setHeader("User-Agent", "foo");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testUserAgentExclusionUAMatch() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+ gzipHandler.setExcludedAgentPatterns("bar", "foo");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setHeader("User-Agent", "foo");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testUserAgentExclusionDefault() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setHeader("User-Agent", "Some MSIE 6.0 user-agent");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testUserAgentExclusionByExcludedAgentPatterns() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+ gzipHandler.setExcludedAgentPatterns("bar", "fo.*");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setHeader("User-Agent", "foo");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testExcludePaths() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+ gzipHandler.setExcludedPaths("*.txt");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4;
+ Path file = createFile(contextDir, "file.txt", fileSize);
+ String expectedSha1Sum = Sha1Sum.calculate(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+
+ @Test
+ public void testIncludedPaths() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setExcludedPaths("/bad.txt");
+ gzipHandler.setIncludedPaths("*.txt");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path fileGood = createFile(contextDir, "file.txt", DEFAULT_OUTPUT_BUFFER_SIZE * 4);
+ Path fileBad = createFile(contextDir, "bad.txt", DEFAULT_OUTPUT_BUFFER_SIZE * 2);
+ String expectedGoodSha1Sum = Sha1Sum.calculate(fileGood);
+ String expectedBadSha1Sum = Sha1Sum.calculate(fileBad);
+
+ server.start();
+
+ // Test Request 1
+ {
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/file.txt");
+
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent"));
+
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is((int)Files.size(fileGood)));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedGoodSha1Sum));
+ }
+
+ // Test Request 2
+ {
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/bad.txt");
+
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ UncompressedMetadata metadata = parseResponseContent(response);
+ int fileSize = (int)Files.size(fileBad);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedBadSha1Sum));
+ }
+ }
+
+ @Test
+ public void testIsNotGzipCompressedSVGZ() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ ServletHolder holder = new ServletHolder("default", DefaultServlet.class);
+ servletContextHandler.addServlet(holder, "/");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path testResource = MavenTestingUtils.getTestResourcePath("test.svgz");
+ Path file = contextDir.resolve("test.svgz");
+ IO.copy(testResource.toFile(), file.toFile());
+ int fileSize = (int)Files.size(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/test.svgz");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Header checks
+ assertThat("Response[Content-Type]", response.get("Content-Type"), containsString("image/svg+xml"));
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java
deleted file mode 100644
index 15443159286..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java
+++ /dev/null
@@ -1,765 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.TimeUnit;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletOutputStream;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.CompressedContentFormat;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.tools.HttpTester;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.toolchain.test.IO;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.emptyOrNullString;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.startsWith;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-
-/**
- * Test the GzipHandler support built into the {@link DefaultServlet}
- */
-@ExtendWith(WorkDirExtension.class)
-public class GzipDefaultTest
-{
- private String compressionType;
-
- public GzipDefaultTest()
- {
- this.compressionType = GzipHandler.GZIP;
- }
-
- @SuppressWarnings("serial")
- public static class HttpStatusServlet extends HttpServlet
- {
- private int _status = 204;
-
- public HttpStatusServlet()
- {
- super();
- }
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- resp.setStatus(_status);
- resp.setHeader("ETag", "W/\"204\"");
- }
- }
-
- @SuppressWarnings("serial")
- public static class HttpErrorServlet extends HttpServlet
- {
- private int _status = 400;
-
- public HttpErrorServlet()
- {
- super();
- }
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- resp.getOutputStream().write("error message".getBytes());
- resp.setStatus(_status);
- }
- }
-
- @SuppressWarnings("serial")
- public static class HttpContentTypeWithEncoding extends HttpServlet
- {
- public static final String COMPRESSED_CONTENT = "COMPRESSABLE CONTENT
" +
- "This content must be longer than the default min gzip length, which is 256 bytes. " +
- "The moon is blue to a fish in love. How now brown cow. The quick brown fox jumped over the lazy dog. A woman needs a man like a fish needs a bicycle!" +
- "";
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- resp.setContentType("text/plain;charset=UTF8");
- resp.setStatus(200);
- ServletOutputStream out = resp.getOutputStream();
- out.print(COMPRESSED_CONTENT);
- }
- }
-
- public WorkDir testingdir;
-
- @Test
- public void testIsGzipByMethod() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().setIncludedMethods("POST", "WIBBLE", "HEAD");
-
- // Prepare Server File
- int filesize = tester.getOutputBufferSize() * 2;
- tester.prepareServerFile("file.txt", filesize);
-
- // Content Servlet
- tester.setContentServlet(GetServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response response;
-
- //These methods have content bodies of the compressed response
- tester.assertIsResponseGzipCompressed("POST", "file.txt");
- tester.assertIsResponseGzipCompressed("WIBBLE", "file.txt");
-
- //A HEAD request should have similar headers, but no body
- response = tester.executeRequest("HEAD", "/context/file.txt", 5, TimeUnit.SECONDS);
- assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
- assertThat("ETag", response.get("ETag"), containsString(CompressedContentFormat.GZIP._etag));
- assertThat("Content encoding", response.get("Content-Encoding"), containsString("gzip"));
- assertNull(response.get("Content-Length"), "Content length");
-
- response = tester.executeRequest("GET", "/context/file.txt", 5, TimeUnit.SECONDS);
-
- assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(compressionType)));
-
- String content = tester.readResponse(response);
- assertThat("Response content size", content.length(), is(filesize));
- String expectedContent = IO.readToString(testingdir.getPathFile("file.txt").toFile());
- assertThat("Response content", content, is(expectedContent));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @SuppressWarnings("serial")
- public static class GetServlet extends DefaultServlet
- {
- public GetServlet()
- {
- super();
- }
-
- @Override
- public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
- {
- String uri = req.getRequestURI();
- if (uri.endsWith(".deferred"))
- {
- // System.err.println("type for "+uri.substring(0,uri.length()-9)+" is "+getServletContext().getMimeType(uri.substring(0,uri.length()-9)));
- resp.setContentType(getServletContext().getMimeType(uri.substring(0, uri.length() - 9)));
- }
-
- doGet(req, resp);
- }
- }
-
- @Test
- public void testIsGzipCompressedEmpty() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
-
- // Prepare server file
- tester.prepareServerFile("empty.txt", 0);
-
- // Set content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
-
- HttpTester.Response response;
-
- response = tester.executeRequest("GET", "/context/empty.txt", 5, TimeUnit.SECONDS);
-
- assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(compressionType)));
-
- String content = tester.readResponse(response);
- assertThat("Response content size", content.length(), is(0));
- String expectedContent = IO.readToString(testingdir.getPathFile("empty.txt").toFile());
- assertThat("Response content", content, is(expectedContent));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsGzipCompressedTiny() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- int filesize = tester.getOutputBufferSize() / 4;
- tester.prepareServerFile("file.txt", filesize);
-
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET", "file.txt");
- assertEquals("Accept-Encoding, User-Agent", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsGzipCompressedLarge() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
- tester.getGzipHandler().setExcludedAgentPatterns();
-
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET", "file.txt");
- assertEquals("Accept-Encoding", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testGzipedIfModified() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET", "file.txt", TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - 4000);
- assertEquals("Accept-Encoding, User-Agent", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testGzippedIfSVG() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
- tester.copyTestServerFile("test.svg");
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-
- tester.getGzipHandler().addIncludedMimeTypes("image/svg+xml");
-
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET", "test.svg", TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - 4000);
- assertEquals("Accept-Encoding, User-Agent", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testNotGzipedIfNotModified() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-
- try
- {
- tester.start();
- tester.assertIsResponseNotModified("GET", "file.txt", System.currentTimeMillis() + 4000);
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedWithZeroQ() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType + "; q=0");
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() / 4;
- tester.prepareServerFile("file.txt", filesize);
-
- // Add content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = assertIsResponseNotGzipCompressed(tester, "GET", "file.txt", filesize, HttpStatus.OK_200);
- assertThat("Response[Vary]", http.get("Vary"), containsString("Accept-Encoding"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsGzipCompressedWithQ() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType, "something;q=0.1," + compressionType + ";q=0.5");
-
- int filesize = tester.getOutputBufferSize() / 4;
- tester.prepareServerFile("file.txt", filesize);
-
- tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
- tester.getGzipHandler().setExcludedAgentPatterns();
-
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET", "file.txt");
- assertEquals("Accept-Encoding", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedByContentType() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.mp3", filesize);
-
- // Add content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = assertIsResponseNotGzipCompressed(tester, "GET", "file.mp3", filesize, HttpStatus.OK_200);
- assertNull(http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedByExcludedContentType() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addExcludedMimeTypes("text/plain");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("test_quotes.txt", filesize);
-
- // Add content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = assertIsResponseNotGzipCompressed(tester, "GET", "test_quotes.txt", filesize, HttpStatus.OK_200);
- assertNull(http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedByExcludedContentTypeWithCharset() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addExcludedMimeTypes("text/plain");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("test_quotes.txt", filesize);
- tester.addMimeType("txt", "text/plain;charset=UTF-8");
-
- // Add content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response http = assertIsResponseNotGzipCompressed(tester, "GET", "test_quotes.txt", filesize, HttpStatus.OK_200);
- assertNull(http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testGzipCompressedByContentTypeWithEncoding() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
- tester.setContentServlet(HttpContentTypeWithEncoding.class);
- tester.getGzipHandler().setMinGzipSize(16);
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
- tester.getGzipHandler().setExcludedAgentPatterns();
- try
- {
- tester.start();
- HttpTester.Response http = tester.assertNonStaticContentIsResponseGzipCompressed("GET", "xxx", HttpContentTypeWithEncoding.COMPRESSED_CONTENT);
- assertEquals("Accept-Encoding", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedByDeferredContentType() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.mp3.deferred", filesize);
-
- // Add content servlet
- tester.setContentServlet(GetServlet.class);
-
- try
- {
- tester.start();
- HttpTester.Response response = assertIsResponseNotGzipCompressed(tester, "GET", "file.mp3.deferred", filesize, HttpStatus.OK_200);
- assertThat("Response[Vary]", response.get("Vary"), is(emptyOrNullString()));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedHttpStatus() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
-
- // Test error code 204
- tester.setContentServlet(HttpStatusServlet.class);
-
- try
- {
- tester.start();
-
- HttpTester.Response response = tester.executeRequest("GET", "/context/", 5, TimeUnit.SECONDS);
-
- assertThat("Response status", response.getStatus(), is(HttpStatus.NO_CONTENT_204));
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(compressionType)));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIsNotGzipCompressedHttpBadRequestStatus() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
-
- // Test error code 400
- tester.setContentServlet(HttpErrorServlet.class);
-
- try
- {
- tester.start();
-
- HttpTester.Response response = tester.executeRequest("GET", "/context/", 5, TimeUnit.SECONDS);
-
- assertThat("Response status", response.getStatus(), is(HttpStatus.BAD_REQUEST_400));
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(compressionType)));
-
- String content = tester.readResponse(response);
- assertThat("Response content", content, is("error message"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testUserAgentExclusion() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
- tester.setUserAgent("foo");
-
- // Configure Gzip Handler
- tester.getGzipHandler().addIncludedMimeTypes("text/plain");
- tester.getGzipHandler().setExcludedAgentPatterns("bar", "foo");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- // Add content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- assertIsResponseNotGzipCompressed(tester, "GET", "file.txt", filesize, HttpStatus.OK_200);
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testUserAgentExclusionDefault() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
- tester.setContentServlet(DefaultServlet.class);
- tester.setUserAgent("Some MSIE 6.0 user-agent");
-
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- try
- {
- tester.start();
- HttpTester.Response http = assertIsResponseNotGzipCompressed(tester, "GET", "file.txt", filesize, HttpStatus.OK_200);
- assertEquals("Accept-Encoding, User-Agent", http.get("Vary"));
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testUserAgentExclusionByExcludedAgentPatterns() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
- tester.setUserAgent("foo");
-
- // Configure Gzip Handler
- tester.getGzipHandler().setExcludedAgentPatterns("bar", "fo.*");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- // Set content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- assertIsResponseNotGzipCompressed(tester, "GET", "file.txt", filesize, HttpStatus.OK_200);
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testExcludePaths() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().setExcludedPaths("*.txt");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
-
- // Set content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- assertIsResponseNotGzipCompressed(tester, "GET", "file.txt", filesize, HttpStatus.OK_200);
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testIncludedPaths() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- // Configure Gzip Handler
- tester.getGzipHandler().setExcludedPaths(tester.getContextPath() + "/bad.txt");
- tester.getGzipHandler().setIncludedPaths("*.txt");
-
- // Prepare server file
- int filesize = tester.getOutputBufferSize() * 4;
- tester.prepareServerFile("file.txt", filesize);
- tester.prepareServerFile("bad.txt", filesize);
-
- // Set content servlet
- tester.setContentServlet(DefaultServlet.class);
-
- try
- {
- tester.start();
- tester.assertIsResponseGzipCompressed("GET", "file.txt");
- }
- finally
- {
- tester.stop();
- }
-
- try
- {
- tester.start();
- assertIsResponseNotGzipCompressed(tester, "GET", "bad.txt", filesize, HttpStatus.OK_200);
- }
- finally
- {
- tester.stop();
- }
- }
-
- public HttpTester.Response assertIsResponseNotGzipCompressed(GzipTester tester, String method, String filename, int expectedFilesize, int status)
- throws Exception
- {
- HttpTester.Response response = tester.executeRequest(method, "/context/" + filename, 5, TimeUnit.SECONDS);
-
- assertThat("Response status", response.getStatus(), is(status));
- assertThat("Content-Encoding", response.get("Content-Encoding"), not(containsString(compressionType)));
-
- assertResponseContent(tester, response, status, filename, expectedFilesize);
-
- return response;
- }
-
- private void assertResponseContent(GzipTester tester, HttpTester.Response response, int status, String filename, int expectedFilesize) throws IOException,
- UnsupportedEncodingException
- {
- if (expectedFilesize >= 0)
- {
- assertThat("filename", filename, notNullValue());
- assertThat("Response contentBytes.length", response.getContentBytes().length, is(expectedFilesize));
- String contentLength = response.get("Content-Length");
- if (StringUtil.isNotBlank(contentLength))
- {
- assertThat("Content-Length", response.get("Content-Length"), is(Integer.toString(expectedFilesize)));
- }
-
- if (status >= 200 && status < 300)
- {
- assertThat("ETag", response.get("ETAG"), startsWith("W/"));
- }
-
- File serverFile = testingdir.getPathFile(filename).toFile();
- String expectedResponse = IO.readToString(serverFile);
-
- String actual = tester.readResponse(response);
- assertEquals(expectedResponse, actual, "Expected response equals actual response");
- }
- }
-
- @Test
- public void testIsNotGzipCompressedSVGZ() throws Exception
- {
- GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType);
-
- tester.setContentServlet(DefaultServlet.class);
- tester.copyTestServerFile("test.svgz");
-
- try
- {
- tester.start();
- tester.assertIsResponseNotGzipFiltered("test.svgz", "test.svgz.sha1", "image/svg+xml", "gzip");
- }
- finally
- {
- tester.stop();
- }
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerNoReCompressTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerNoReCompressTest.java
new file mode 100644
index 00000000000..bfb00cc6a60
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerNoReCompressTest.java
@@ -0,0 +1,147 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.Sha1Sum;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.resource.PathResource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * Tests {@link GzipHandler} in combination with {@link DefaultServlet} for ability to configure {@link GzipHandler} to
+ * ignore recompress situations from upstream.
+ */
+public class GzipHandlerNoReCompressTest extends AbstractGzipTest
+{
+ public static Stream scenarios()
+ {
+ return Stream.of(
+ Arguments.of("test_quotes.gz", "application/gzip"),
+ Arguments.of("test_quotes.br", "application/brotli"),
+ Arguments.of("test_quotes.bz2", "application/bzip2"),
+ Arguments.of("test_quotes.zip", "application/zip"),
+ Arguments.of("test_quotes.rar", "application/x-rar-compressed"),
+ // Some images (common first)
+ Arguments.of("jetty_logo.png", "image/png"),
+ Arguments.of("jetty_logo.gif", "image/gif"),
+ Arguments.of("jetty_logo.jpeg", "image/jpeg"),
+ Arguments.of("jetty_logo.jpg", "image/jpeg"),
+ // Lesser encountered images (usually found being requested from non-browser clients)
+ Arguments.of("jetty_logo.bmp", "image/bmp"),
+ Arguments.of("jetty_logo.tif", "image/tiff"),
+ Arguments.of("jetty_logo.tiff", "image/tiff"),
+ Arguments.of("jetty_logo.xcf", "image/xcf"),
+ Arguments.of("jetty_logo.jp2", "image/jpeg2000")
+ );
+ }
+
+ private Server server;
+
+ @AfterEach
+ public void stopServer()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @ParameterizedTest
+ @MethodSource("scenarios")
+ public void testNotGzipAlreadyCompressed(String fileName, String expectedContentType) throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ Path contextDir = workDir.resolve("context");
+ FS.ensureDirExists(contextDir);
+
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath("/context");
+ servletContextHandler.setBaseResource(new PathResource(contextDir));
+ servletContextHandler.addServlet(TestStaticMimeTypeServlet.class, "/*");
+ servletContextHandler.insertHandler(gzipHandler);
+
+ server.setHandler(servletContextHandler);
+
+ // Prepare Server File
+ Path testResource = MavenTestingUtils.getTestResourcePath(fileName);
+ Path file = contextDir.resolve(fileName);
+ IO.copy(testResource.toFile(), file.toFile());
+ String expectedSha1Sum = Sha1Sum.loadSha1(MavenTestingUtils.getTestResourceFile(fileName + ".sha1")).toUpperCase(Locale.ENGLISH);
+ int fileSize = (int)Files.size(file);
+
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/" + fileName);
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Headers check
+ assertThat("Response[Content-Type]", response.get("Content-Type"), is(expectedContentType));
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response[Vary]", response.get("Vary"), is(nullValue()));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response Content Length", metadata.contentLength, is(fileSize));
+ assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize));
+ assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum));
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java
new file mode 100644
index 00000000000..980b254f672
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java
@@ -0,0 +1,226 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+public class GzipHandlerTest extends AbstractGzipTest
+{
+ private Server server;
+
+ @AfterEach
+ public void stopServer()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testGzipCompressedByContentTypeWithEncoding() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setMinGzipSize(32);
+ gzipHandler.addIncludedMimeTypes("text/plain");
+ gzipHandler.setExcludedAgentPatterns();
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ ServletContextHandler contextHandler = new ServletContextHandler();
+ contextHandler.setContextPath("/context");
+ contextHandler.addServlet(HttpContentTypeWithEncodingServlet.class, "/*");
+
+ gzipHandler.setHandler(contextHandler);
+
+ server.setHandler(gzipHandler);
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/xxx");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip"));
+ assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding"));
+
+ // Response Content checks
+ UncompressedMetadata metadata = parseResponseContent(response);
+ assertThat("Response[Content] raw length vs uncompressed length", metadata.contentLength, not(is(metadata.uncompressedSize)));
+ assertThat("(Uncompressed) Content", metadata.getContentUTF8(), is(HttpContentTypeWithEncodingServlet.CONTENT));
+ }
+
+ public static class HttpContentTypeWithEncodingServlet extends HttpServlet
+ {
+ public static final String CONTENT = "COMPRESSIBLE CONTENT
" +
+ "" +
+ "This content must be longer than the default min gzip length, which is " + GzipHandler.DEFAULT_MIN_GZIP_SIZE + " bytes. " +
+ "The moon is blue to a fish in love.
" +
+ "How now brown cow.
" +
+ "The quick brown fox jumped over the lazy dog.
" +
+ "A woman needs a man like a fish needs a bicycle!" +
+ "
" +
+ "";
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setContentType("text/plain;charset=UTF8");
+ resp.setStatus(200);
+ ServletOutputStream out = resp.getOutputStream();
+ out.print(CONTENT);
+ }
+ }
+
+ @Test
+ public void testIsNotGzipCompressedHttpStatus() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ ServletContextHandler contextHandler = new ServletContextHandler();
+ contextHandler.setContextPath("/context");
+ contextHandler.addServlet(HttpStatusServlet.class, "/*");
+
+ gzipHandler.setHandler(contextHandler);
+
+ server.setHandler(gzipHandler);
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/xxx");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.NO_CONTENT_204));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ }
+
+ public static class HttpStatusServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ resp.setHeader("ETag", "W/\"204\"");
+ }
+ }
+
+ @Test
+ public void testIsNotGzipCompressedHttpBadRequestStatus() throws Exception
+ {
+ GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.addIncludedMimeTypes("text/plain");
+
+ server = new Server();
+ LocalConnector localConnector = new LocalConnector(server);
+ server.addConnector(localConnector);
+
+ ServletContextHandler contextHandler = new ServletContextHandler();
+ contextHandler.setContextPath("/context");
+ contextHandler.addServlet(HttpErrorServlet.class, "/*");
+
+ gzipHandler.setHandler(contextHandler);
+
+ server.setHandler(gzipHandler);
+ server.start();
+
+ // Setup request
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "tester");
+ request.setHeader("Connection", "close");
+ request.setHeader("Accept-Encoding", "gzip");
+ request.setURI("/context/xxx");
+
+ // Issue request
+ ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS);
+
+ // Parse response
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status", response.getStatus(), is(HttpStatus.BAD_REQUEST_400));
+
+ // Response Content-Encoding check
+ assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip")));
+ assertThat("Response Content", response.getContent(), is("error message"));
+ }
+
+ public static class HttpErrorServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.getOutputStream().write("error message".getBytes());
+ resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java
deleted file mode 100644
index b3769c26cee..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java
+++ /dev/null
@@ -1,635 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.file.Path;
-import java.security.DigestOutputStream;
-import java.security.MessageDigest;
-import java.util.EnumSet;
-import java.util.Enumeration;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.Inflater;
-import java.util.zip.InflaterInputStream;
-
-import jakarta.servlet.DispatcherType;
-import jakarta.servlet.Servlet;
-import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.DateGenerator;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.tools.HttpTester;
-import org.eclipse.jetty.server.HttpConnectionFactory;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlet.ServletTester;
-import org.eclipse.jetty.toolchain.test.FS;
-import org.eclipse.jetty.toolchain.test.IO;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.Sha1Sum;
-import org.hamcrest.Matchers;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.emptyOrNullString;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class GzipTester
-{
- private static final Logger LOG = LoggerFactory.getLogger(GzipTester.class);
-
- public static class ContentMetadata
- {
- public final long size;
- public final String sha1;
-
- public ContentMetadata(long size, String sha1checksum)
- {
- this.size = size;
- this.sha1 = sha1checksum;
- }
- }
-
- private String encoding = "ISO8859_1";
- private String userAgent = null;
- private final GzipHandler gzipHandler = new GzipHandler();
- private final ServletTester tester = new ServletTester("/context");
- private Path testdir;
- private String accept;
- private String compressionType;
-
- public GzipTester(Path testingdir, String compressionType)
- {
- this(testingdir, compressionType, compressionType);
- }
-
- public GzipTester(Path testingdir, String compressionType, String accept)
- {
- this.testdir = testingdir;
- this.compressionType = compressionType;
- this.accept = accept;
- this.tester.getServer().insertHandler(gzipHandler);
- }
-
- public String getContextPath()
- {
- return tester.getContextPath();
- }
-
- public GzipHandler getGzipHandler()
- {
- return gzipHandler;
- }
-
- public int getOutputBufferSize()
- {
- return tester.getConnector().getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize();
- }
-
- public ContentMetadata getResponseMetadata(HttpTester.Response response) throws Exception
- {
- long size = response.getContentBytes().length;
-
- String contentEncoding = response.get("Content-Encoding");
-
- ByteArrayInputStream bais = null;
- InputStream in = null;
- DigestOutputStream digester = null;
- ByteArrayOutputStream uncompressedStream = null;
- try
- {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- bais = new ByteArrayInputStream(response.getContentBytes());
-
- if (contentEncoding == null)
- {
- LOG.debug("No response content-encoding");
- in = new PassThruInputStream(bais);
- }
- else if (contentEncoding.contains(GzipHandler.GZIP))
- {
- in = new GZIPInputStream(bais);
- }
- else if (contentEncoding.contains(GzipHandler.DEFLATE))
- {
- in = new InflaterInputStream(bais, new Inflater(true));
- }
- else
- {
- assertThat("Unexpected response content-encoding", contentEncoding, is(emptyOrNullString()));
- }
-
- uncompressedStream = new ByteArrayOutputStream((int)size);
-
- digester = new DigestOutputStream(uncompressedStream, digest);
- IO.copy(in, digester);
-
- byte[] output = uncompressedStream.toByteArray();
- String actualSha1Sum = Hex.asHex(digest.digest());
- return new ContentMetadata(output.length, actualSha1Sum);
- }
- finally
- {
- IO.close(digester);
- IO.close(in);
- IO.close(bais);
- IO.close(uncompressedStream);
- }
- }
-
- public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception
- {
- return assertIsResponseGzipCompressed(method, filename, filename, -1);
- }
-
- public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename, long ifmodifiedsince) throws Exception
- {
- return assertIsResponseGzipCompressed(method, filename, filename, ifmodifiedsince);
- }
-
- public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename) throws Exception
- {
- return assertIsResponseGzipCompressed(method, requestedFilename, serverFilename, -1);
- }
-
- public HttpTester.Response assertNonStaticContentIsResponseGzipCompressed(String method, String path, String expected) throws Exception
- {
- HttpTester.Request request = HttpTester.newRequest();
- HttpTester.Response response;
-
- request.setMethod(method);
- request.setVersion("HTTP/1.0");
- request.setHeader("Host", "tester");
- request.setHeader("Accept-Encoding", accept);
-
- if (this.userAgent != null)
- request.setHeader("User-Agent", this.userAgent);
- request.setURI("/context/" + path);
-
- // Issue the request
- response = HttpTester.parseResponse(tester.getResponses(request.generate()));
-
- int qindex = compressionType.indexOf(";");
- if (qindex < 0)
- assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"), containsString(compressionType));
- else
- assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"), containsString(compressionType.substring(0, qindex)));
-
- ByteArrayInputStream bais = null;
- InputStream in = null;
- ByteArrayOutputStream out = null;
- String actual = null;
-
- try
- {
- bais = new ByteArrayInputStream(response.getContentBytes());
- if (compressionType.startsWith(GzipHandler.GZIP))
- {
- in = new GZIPInputStream(bais);
- }
- else if (compressionType.startsWith(GzipHandler.DEFLATE))
- {
- in = new InflaterInputStream(bais, new Inflater(true));
- }
- out = new ByteArrayOutputStream();
- IO.copy(in, out);
-
- actual = out.toString(encoding);
- assertThat("Uncompressed contents", actual, equalTo(expected));
- }
- finally
- {
- IO.close(out);
- IO.close(in);
- IO.close(bais);
- }
-
- return response;
- }
-
- public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince)
- throws Exception
- {
- HttpTester.Request request = HttpTester.newRequest();
- HttpTester.Response response;
-
- request.setMethod(method);
- request.setVersion("HTTP/1.0");
- request.setHeader("Host", "tester");
- request.setHeader("Accept-Encoding", compressionType);
- if (ifmodifiedsince > 0)
- request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(), DateGenerator.formatDate(ifmodifiedsince));
- if (this.userAgent != null)
- request.setHeader("User-Agent", this.userAgent);
- request.setURI("/context/" + requestedFilename);
-
- // Issue the request
- response = HttpTester.parseResponse(tester.getResponses(request.generate()));
-
- // Assert the response headers
- // assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
-
- // Response headers should have either a Transfer-Encoding indicating chunked OR a Content-Length
- /*
- * TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests String
- * contentLength = response.get("Content-Length"); String transferEncoding = response.get("Transfer-Encoding");
- *
- * boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0); if(!chunked) {
- * assertThat("Response.header[Content-Length]",contentLength,notNullValue()); } else {
- * assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue()); }
- */
-
- int qindex = compressionType.indexOf(";");
- if (qindex < 0)
- assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"), containsString(compressionType));
- else
- assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"), containsString(compressionType.substring(0, qindex)));
-
- assertThat(response.get("ETag"), Matchers.startsWith("W/"));
-
- // Assert that the decompressed contents are what we expect.
- File serverFile = testdir.resolve(FS.separators(serverFilename)).toFile();
- String expected = IO.readToString(serverFile);
- String actual = null;
-
- ByteArrayInputStream bais = null;
- InputStream in = null;
- ByteArrayOutputStream out = null;
- try
- {
- bais = new ByteArrayInputStream(response.getContentBytes());
- if (compressionType.startsWith(GzipHandler.GZIP))
- {
- in = new GZIPInputStream(bais);
- }
- else if (compressionType.startsWith(GzipHandler.DEFLATE))
- {
- in = new InflaterInputStream(bais, new Inflater(true));
- }
- out = new ByteArrayOutputStream();
- IO.copy(in, out);
-
- actual = out.toString(encoding);
- assertThat("Uncompressed contents", actual, equalTo(expected));
- }
- finally
- {
- IO.close(out);
- IO.close(in);
- IO.close(bais);
- }
-
- return response;
- }
-
- public HttpTester.Response assertIsResponseNotModified(String method, String requestedFilename, long ifmodifiedsince) throws Exception
- {
- HttpTester.Request request = HttpTester.newRequest();
- HttpTester.Response response;
-
- request.setMethod(method);
- request.setVersion("HTTP/1.0");
- request.setHeader("Host", "tester");
- request.setHeader("Accept-Encoding", compressionType);
- if (ifmodifiedsince > 0)
- request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(), DateGenerator.formatDate(ifmodifiedsince));
- if (this.userAgent != null)
- request.setHeader("User-Agent", this.userAgent);
- request.setURI("/context/" + requestedFilename);
-
- // Issue the request
- response = HttpTester.parseResponse(tester.getResponses(request.generate()));
-
- assertThat(response.getStatus(), Matchers.equalTo(304));
- assertThat(response.get("ETag"), Matchers.startsWith("W/"));
-
- return response;
- }
-
- /**
- * Makes sure that the response contains an unfiltered file contents.
- *
- * This is used to test exclusions and passthroughs in the GzipHandler.
- *
- * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
- *
- * @param requestedFilename the filename used to on the GET request,.
- * @param testResourceSha1Sum the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
- * @param expectedContentType the expected content type
- * @throws Exception on test failure
- */
- public void assertIsResponseNotGziped(String requestedFilename, String testResourceSha1Sum, String expectedContentType) throws Exception
- {
- assertIsResponseNotGzipFiltered(requestedFilename, testResourceSha1Sum, expectedContentType, null);
- }
-
- /**
- * Makes sure that the response contains an unfiltered file contents.
- *
- * This is used to test exclusions and passthroughs in the GzipHandler.
- *
- * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
- *
- * @param requestedFilename the filename used to on the GET request,.
- * @param testResourceSha1Sum the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
- * @param expectedContentType the expected content type
- * @param expectedContentEncoding can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files
- * @throws Exception on test failure
- */
- public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding)
- throws Exception
- {
- HttpTester.Request request = HttpTester.newRequest();
- HttpTester.Response response;
-
- request.setMethod("GET");
- request.setVersion("HTTP/1.0");
- request.setHeader("Host", "tester");
- request.setHeader("Accept-Encoding", compressionType);
- if (this.userAgent != null)
- request.setHeader("User-Agent", this.userAgent);
- request.setURI("/context/" + requestedFilename);
-
- // Issue the request
- response = HttpTester.parseResponse(tester.getResponses(request.generate()));
-
- dumpHeaders(requestedFilename + " / Response Headers", response);
-
- // Assert the response headers
- String prefix = requestedFilename + " / Response";
- assertThat(prefix + ".status", response.getStatus(), is(HttpServletResponse.SC_OK));
- assertThat(prefix + ".header[Content-Length]", response.get("Content-Length"), notNullValue());
- assertThat(prefix + ".header[Content-Encoding] (should not be recompressed by GzipHandler)", response.get("Content-Encoding"),
- expectedContentEncoding == null ? nullValue() : notNullValue());
- if (expectedContentEncoding != null)
- assertThat(prefix + ".header[Content-Encoding]", response.get("Content-Encoding"), is(expectedContentEncoding));
- assertThat(prefix + ".header[Content-Type] (should have a Content-Type associated with it)", response.get("Content-Type"), notNullValue());
- assertThat(prefix + ".header[Content-Type]", response.get("Content-Type"), is(expectedContentType));
-
- assertThat(response.get("ETAG"), Matchers.startsWith("W/"));
-
- ByteArrayInputStream bais = null;
- DigestOutputStream digester = null;
- try
- {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- bais = new ByteArrayInputStream(response.getContentBytes());
- digester = new DigestOutputStream(new NoOpOutputStream(), digest);
- IO.copy(bais, digester);
-
- String actualSha1Sum = Hex.asHex(digest.digest());
- File sha1File = MavenTestingUtils.getTestResourceFile(testResourceSha1Sum);
- String expectedSha1Sum = Sha1Sum.loadSha1(sha1File);
- assertEquals(expectedSha1Sum, actualSha1Sum, requestedFilename + " / SHA1Sum of content");
- }
- finally
- {
- IO.close(digester);
- IO.close(bais);
- }
- }
-
- private void dumpHeaders(String prefix, HttpTester.Message message)
- {
- LOG.debug("dumpHeaders: {}", prefix);
- Enumeration names = message.getFieldNames();
- while (names.hasMoreElements())
- {
- String name = names.nextElement();
- String value = message.get(name);
- LOG.debug("dumpHeaders: {} = {}", name, value);
- }
- }
-
- public HttpTester.Response executeRequest(String method, String path, int idleFor, TimeUnit idleUnit) throws Exception
- {
- HttpTester.Request request = HttpTester.newRequest();
-
- request.setMethod(method);
- request.setVersion("HTTP/1.1");
- request.setHeader("Host", "tester");
- request.setHeader("Accept-Encoding", accept);
- request.setHeader("Connection", "close");
-
- if (this.userAgent != null)
- {
- request.setHeader("User-Agent", this.userAgent);
- }
-
- request.setURI(path);
-
- // Issue the request
- return HttpTester.parseResponse(tester.getResponses(request.generate(), idleFor, idleUnit));
- }
-
- public String readResponse(HttpTester.Response response) throws IOException, UnsupportedEncodingException
- {
- String actual = null;
- InputStream in = null;
- ByteArrayOutputStream out = null;
- try
- {
- byte[] content = response.getContentBytes();
- if (content != null)
- actual = new String(response.getContentBytes(), encoding);
- else
- actual = "";
- }
- finally
- {
- IO.close(out);
- IO.close(in);
- }
- return actual;
- }
-
- /**
- * Generate string content of arbitrary length.
- *
- * @param length the length of the string to generate.
- * @return the string content.
- */
- public String generateContent(int length)
- {
- StringBuilder builder = new StringBuilder();
- do
- {
- builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc.\n");
- builder.append("Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque\n");
- builder.append("habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n");
- builder.append("Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam\n");
- builder.append("at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate\n");
- builder.append("velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum.\n");
- builder.append("Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum\n");
- builder.append("eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa\n");
- builder.append("sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam\n");
- builder.append("consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque.\n");
- builder.append("Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse\n");
- builder.append("et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.\n");
- }
- while (builder.length() < length);
-
- // Make sure we are exactly at requested length. (truncate the extra)
- if (builder.length() > length)
- {
- builder.setLength(length);
- }
-
- return builder.toString();
- }
-
- public String getEncoding()
- {
- return encoding;
- }
-
- /**
- * Create a file on the server resource path of a specified filename and size.
- *
- * @param filename the filename to create
- * @param filesize the file size to create (Note: this isn't suitable for creating large multi-megabyte files)
- * @return the prepared file
- * @throws IOException if unable to create file
- */
- public File prepareServerFile(String filename, int filesize) throws IOException
- {
- File dir = testdir.toFile();
- File testFile = new File(dir, filename);
- // Make sure we have a uniq filename (to work around windows File.delete bug)
- int i = 0;
- while (testFile.exists())
- {
- testFile = new File(dir, (i++) + "-" + filename);
- }
-
- FileOutputStream fos = null;
- ByteArrayInputStream in = null;
- try
- {
- fos = new FileOutputStream(testFile, false);
- in = new ByteArrayInputStream(generateContent(filesize).getBytes(encoding));
- IO.copy(in, fos);
- return testFile;
- }
- finally
- {
- IO.close(in);
- IO.close(fos);
- }
- }
-
- /**
- * Copy a src/test/resource file into the server tree for eventual serving.
- *
- * @param filename the filename to look for in src/test/resources
- * @throws IOException if unable to copy file
- */
- public void copyTestServerFile(String filename) throws IOException
- {
- File srcFile = MavenTestingUtils.getTestResourceFile(filename);
- File testFile = testdir.resolve(FS.separators(filename)).toFile();
-
- IO.copy(srcFile, testFile);
- }
-
- /**
- * Set the servlet that provides content for the GzipHandler in being tested.
- *
- * @param servletClass the servlet that will provide content.
- * @throws IOException if unable to set content servlet
- */
- public void setContentServlet(Class extends Servlet> servletClass) throws IOException
- {
- String resourceBase = testdir.toString();
- tester.setContextPath("/context");
- tester.setResourceBase(resourceBase);
- ServletHolder servletHolder = tester.addServlet(servletClass, "/");
- servletHolder.setInitParameter("baseDir", resourceBase);
- servletHolder.setInitParameter("etags", "true");
- }
-
- public void setEncoding(String encoding)
- {
- this.encoding = encoding;
- }
-
- public void setUserAgent(String ua)
- {
- this.userAgent = ua;
- }
-
- public void addMimeType(String extension, String mimetype)
- {
- this.tester.getContext().getMimeTypes().addMimeMapping(extension, mimetype);
- }
-
- /**
- * Add an arbitrary filter to the test case.
- *
- * @param holder the filter to add
- * @param pathSpec the path spec for this filter
- * @param dispatches the set of {@link DispatcherType} to associate with this filter
- * @throws IOException if unable to add filter
- */
- public void addFilter(FilterHolder holder, String pathSpec, EnumSet dispatches) throws IOException
- {
- tester.addFilter(holder, pathSpec, dispatches);
- }
-
- public void start() throws Exception
- {
- assertThat("No servlet defined yet. Did you use #setContentServlet()?", tester, notNullValue());
-
- if (LOG.isDebugEnabled())
- {
- tester.dumpStdErr();
- }
- tester.start();
- }
-
- public void stop()
- {
- // NOTE: Do not cleanup the workDir. Failures can't be diagnosed if you do that.
- // IO.delete(workDir.getDir()):
- try
- {
- tester.stop();
- }
- catch (Exception e)
- {
- // Don't toss this out into Junit as this would be the last exception
- // that junit will report as being the cause of the test failure.
- // when in reality, the earlier setup issue is the real cause.
- e.printStackTrace(System.err);
- }
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/Hex.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/Hex.java
deleted file mode 100644
index 1b491baae23..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/Hex.java
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-public final class Hex
-{
- private static final char[] hexcodes = "0123456789abcdef".toCharArray();
-
- public static byte[] asByteArray(String id, int size)
- {
- if ((id.length() < 0) || (id.length() > (size * 2)))
- {
- throw new IllegalArgumentException(String.format("Invalid ID length of <%d> expected range of <0> to <%d>", id.length(), (size * 2)));
- }
-
- byte[] buf = new byte[size];
- byte hex;
- int len = id.length();
-
- int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
- int i = 0;
- if ((len % 2) != 0)
- { // deal with odd numbered chars
- i -= 1;
- }
-
- for (; i < len; i++)
- {
- hex = 0;
- if (i >= 0)
- {
- hex = (byte)(Character.digit(id.charAt(i), 16) << 4);
- }
- i++;
- hex += (byte)(Character.digit(id.charAt(i), 16));
-
- buf[idx] = hex;
- idx++;
- }
-
- return buf;
- }
-
- public static String asHex(byte[] buf)
- {
- int len = buf.length;
- char[] out = new char[len * 2];
- for (int i = 0; i < len; i++)
- {
- out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4];
- out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)];
- }
- return String.valueOf(out);
- }
-
- private Hex()
- {
- /* prevent instantiation */
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletBufferTypeLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HttpOutputWriteFileContentServlet.java
similarity index 94%
rename from jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletBufferTypeLengthWrite.java
rename to jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HttpOutputWriteFileContentServlet.java
index 5d43a0772c9..4e4c0755851 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestServletBufferTypeLengthWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HttpOutputWriteFileContentServlet.java
@@ -44,12 +44,12 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
* @see http://bugs.eclipse.org/354014
*/
@SuppressWarnings("serial")
-public class TestServletBufferTypeLengthWrite extends TestDirContentServlet
+public class HttpOutputWriteFileContentServlet extends AbstractFileContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
ServletOutputStream out = response.getOutputStream();
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipMinSizeTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipMinSizeTest.java
deleted file mode 100644
index 5c76b6701bd..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipMinSizeTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import jakarta.servlet.Servlet;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-/**
- * Perform specific tests on the IncludableGzipHandler's ability to manage
- * minGzipSize initialization parameter.
- *
- * @see http://bugs.eclipse.org/366106
- */
-@ExtendWith(WorkDirExtension.class)
-public class IncludedGzipMinSizeTest
-{
- public IncludedGzipMinSizeTest()
- {
- this.compressionType = GzipHandler.GZIP;
- }
-
- public WorkDir testdir;
-
- private String compressionType;
- private Class extends Servlet> testServlet = TestMinGzipSizeServlet.class;
-
- @Test
- public void testUnderMinSize() throws Exception
- {
- GzipTester tester = new GzipTester(testdir.getEmptyPathDir(), compressionType);
-
- tester.setContentServlet(testServlet);
- // A valid mime type that we will never use in this test.
- // configured here to prevent mimeType==null logic
- tester.getGzipHandler().addIncludedMimeTypes("application/soap+xml");
- tester.getGzipHandler().setMinGzipSize(2048);
-
- tester.copyTestServerFile("small_script.js");
-
- try
- {
- tester.start();
- tester.assertIsResponseNotGziped("small_script.js",
- "small_script.js.sha1",
- "text/javascript; charset=utf-8");
- }
- finally
- {
- tester.stop();
- }
- }
-
- @Test
- public void testOverMinSize() throws Exception
- {
- GzipTester tester = new GzipTester(testdir.getEmptyPathDir(), compressionType);
-
- tester.setContentServlet(testServlet);
- tester.getGzipHandler().addIncludedMimeTypes("application/soap+xml", "text/javascript", "application/javascript");
- tester.getGzipHandler().setMinGzipSize(2048);
-
- tester.copyTestServerFile("big_script.js");
-
- try
- {
- tester.start();
- tester.assertIsResponseGzipCompressed("GET", "big_script.js");
- }
- finally
- {
- tester.stop();
- }
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java
deleted file mode 100644
index 81b49e5661d..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.Inflater;
-import java.util.zip.InflaterInputStream;
-
-import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.tools.HttpTester;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlet.ServletTester;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.IO;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-@ExtendWith(WorkDirExtension.class)
-public class IncludedGzipTest
-{
- public WorkDir testdir;
-
- private static String __content =
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. " +
- "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque " +
- "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " +
- "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam " +
- "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate " +
- "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. " +
- "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum " +
- "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa " +
- "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam " +
- "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. " +
- "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse " +
- "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-
- private ServletTester tester;
- private String compressionType;
-
- public IncludedGzipTest()
- {
- this.compressionType = GzipHandler.GZIP;
- }
-
- @BeforeEach
- public void setUp() throws Exception
- {
- testdir.ensureEmpty();
-
- File testFile = testdir.getPathFile("file.txt").toFile();
- try (OutputStream testOut = new BufferedOutputStream(new FileOutputStream(testFile)))
- {
- ByteArrayInputStream testIn = new ByteArrayInputStream(__content.getBytes("ISO8859_1"));
- IO.copy(testIn, testOut);
- }
-
- tester = new ServletTester("/context");
- tester.getContext().setResourceBase(testdir.getPath().toString());
- tester.getContext().addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
-
- GzipHandler gzipHandler = new GzipHandler();
- gzipHandler.setMinGzipSize(16);
- tester.getContext().insertHandler(gzipHandler);
- tester.start();
- }
-
- @AfterEach
- public void tearDown() throws Exception
- {
- tester.stop();
- }
-
- @Test
- public void testGzip() throws Exception
- {
- // generated and parsed test
-
- ByteBuffer request = BufferUtil.toBuffer(
- "GET /context/file.txt HTTP/1.0\r\n" +
- "Host: tester\r\n" +
- "Accept-Encoding: " + compressionType + "\r\n" +
- "\r\n");
-
- HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request));
-
- assertEquals(HttpServletResponse.SC_OK, response.getStatus());
- assertEquals(compressionType, response.get("Content-Encoding"));
-
- InputStream testIn = null;
- ByteArrayInputStream compressedResponseStream = new ByteArrayInputStream(response.getContentBytes());
- if (compressionType.equals(GzipHandler.GZIP))
- {
- testIn = new GZIPInputStream(compressedResponseStream);
- }
- else if (compressionType.equals(GzipHandler.DEFLATE))
- {
- testIn = new InflaterInputStream(compressedResponseStream, new Inflater(true));
- }
- ByteArrayOutputStream testOut = new ByteArrayOutputStream();
- IO.copy(testIn, testOut);
-
- assertEquals(__content, testOut.toString("ISO8859_1"));
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestDirContentServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestDirContentServlet.java
deleted file mode 100644
index eb47d4594b5..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestDirContentServlet.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.0 which is available at
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-
-import jakarta.servlet.ServletConfig;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServlet;
-import org.eclipse.jetty.toolchain.test.PathAssert;
-import org.eclipse.jetty.util.IO;
-
-@SuppressWarnings("serial")
-public class TestDirContentServlet extends HttpServlet
-{
- private File basedir;
-
- @Override
- public void init(ServletConfig config) throws ServletException
- {
- basedir = new File(config.getInitParameter("baseDir"));
- }
-
- public File getTestFile(String filename)
- {
- File testfile = new File(basedir, filename);
- PathAssert.assertFileExists("Content File should exist", testfile);
- return testfile;
- }
-
- protected byte[] loadContentFileBytes(final String fileName) throws IOException
- {
- String relPath = fileName;
- relPath = relPath.replaceFirst("^/context/", "");
- relPath = relPath.replaceFirst("^/", "");
-
- File contentFile = getTestFile(relPath);
-
- FileInputStream in = null;
- ByteArrayOutputStream out = null;
- try
- {
- in = new FileInputStream(contentFile);
- out = new ByteArrayOutputStream();
- IO.copy(in, out);
- return out.toByteArray();
- }
- finally
- {
- IO.close(out);
- IO.close(in);
- }
- }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestMinGzipSizeServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestMinGzipSizeServlet.java
index 4eeac262bbb..082c2b89661 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestMinGzipSizeServlet.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestMinGzipSizeServlet.java
@@ -31,7 +31,7 @@ import org.eclipse.jetty.http.MimeTypes;
* Test servlet for testing against unusual minGzip configurable.
*/
@SuppressWarnings("serial")
-public class TestMinGzipSizeServlet extends TestDirContentServlet
+public class TestMinGzipSizeServlet extends AbstractFileContentServlet
{
private MimeTypes mimeTypes;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestStaticMimeTypeServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestStaticMimeTypeServlet.java
index b56d9fb3b10..9e26a9f5128 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestStaticMimeTypeServlet.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TestStaticMimeTypeServlet.java
@@ -31,7 +31,7 @@ import org.eclipse.jetty.http.MimeTypes;
* Test servlet for testing against unusual MimeTypes and Content-Types.
*/
@SuppressWarnings("serial")
-public class TestStaticMimeTypeServlet extends TestDirContentServlet
+public class TestStaticMimeTypeServlet extends AbstractFileContentServlet
{
private MimeTypes mimeTypes;
@@ -63,7 +63,7 @@ public class TestStaticMimeTypeServlet extends TestDirContentServlet
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- String fileName = request.getServletPath();
+ String fileName = request.getPathInfo();
byte[] dataBytes = loadContentFileBytes(fileName);
response.setContentLength(dataBytes.length);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java
index bc851f4e1c1..0e13aa15e0e 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java
@@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.io.ChannelEndPoint;
+
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.logging.StacklessLogging;
@@ -106,7 +106,7 @@ public class ThreadStarvationTest
ServerConnector connector = new ServerConnector(_server, 0, 1)
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@@ -258,7 +258,7 @@ public class ThreadStarvationTest
ServerConnector connector = new ServerConnector(_server, acceptors, selectors)
{
@Override
- protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key)
{
return new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
diff --git a/jetty-servlets/src/test/resources/test.svg.sha1 b/jetty-servlets/src/test/resources/test.svg.sha1
deleted file mode 100644
index 3b170f0b098..00000000000
--- a/jetty-servlets/src/test/resources/test.svg.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1ccb7a0b85585d0e9bdc3863ad093d4e53a9ea68 test.svg
diff --git a/jetty-unixsocket/jetty-unixsocket-client/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java b/jetty-unixsocket/jetty-unixsocket-client/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
index b4b20bd61cc..dad2b625e37 100644
--- a/jetty-unixsocket/jetty-unixsocket-client/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
+++ b/jetty-unixsocket/jetty-unixsocket-client/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
@@ -47,6 +47,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.unixsocket.common.UnixSocketEndPoint;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -128,7 +129,7 @@ public class HttpClientTransportOverUnixSockets extends AbstractConnectorHttpCli
}
catch (Throwable x)
{
- safeClose(channel);
+ IO.close(channel);
connectFailed(x, context);
}
}
diff --git a/jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java b/jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
index b9060dae6de..a5327eb2463 100644
--- a/jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
+++ b/jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
@@ -40,6 +40,7 @@ import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.slf4j.Logger;
@@ -146,6 +147,7 @@ public class UnixSocketTest
assertThat(contentResponse.getContentAsString(), containsString("Hello World"));
}
+ @Tag("external")
@Test
public void testNotLocal() throws Exception
{
diff --git a/jetty-unixsocket/jetty-unixsocket-common/src/main/java/org/eclipse/jetty/unixsocket/common/UnixSocketEndPoint.java b/jetty-unixsocket/jetty-unixsocket-common/src/main/java/org/eclipse/jetty/unixsocket/common/UnixSocketEndPoint.java
index 29c3c11d5e9..ed4e4248605 100644
--- a/jetty-unixsocket/jetty-unixsocket-common/src/main/java/org/eclipse/jetty/unixsocket/common/UnixSocketEndPoint.java
+++ b/jetty-unixsocket/jetty-unixsocket-common/src/main/java/org/eclipse/jetty/unixsocket/common/UnixSocketEndPoint.java
@@ -23,22 +23,25 @@ import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import jnr.unixsocket.UnixSocketChannel;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class UnixSocketEndPoint extends ChannelEndPoint
+public class UnixSocketEndPoint extends SocketChannelEndPoint
{
private static final Logger LOG = LoggerFactory.getLogger(UnixSocketEndPoint.class);
- private final UnixSocketChannel _channel;
-
public UnixSocketEndPoint(UnixSocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
{
super(channel, selector, key, scheduler);
- _channel = channel;
+ }
+
+ @Override
+ public UnixSocketChannel getChannel()
+ {
+ return (UnixSocketChannel)super.getChannel();
}
@Override
@@ -56,11 +59,9 @@ public class UnixSocketEndPoint extends ChannelEndPoint
@Override
protected void doShutdownOutput()
{
- if (LOG.isDebugEnabled())
- LOG.debug("oshut {}", this);
try
{
- _channel.shutdownOutput();
+ getChannel().shutdownOutput();
super.doShutdownOutput();
}
catch (IOException e)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
index b8b8386b3a7..8fc304278e6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -141,9 +141,9 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
private final Set _includeProtocols = new LinkedHashSet<>();
private final Set _excludeCipherSuites = new LinkedHashSet<>();
private final List _includeCipherSuites = new ArrayList<>();
- protected final Map _aliasX509 = new HashMap<>();
- protected final Map _certHosts = new HashMap<>();
- protected final Map _certWilds = new HashMap<>();
+ private final Map _aliasX509 = new HashMap<>();
+ private final Map _certHosts = new HashMap<>();
+ private final Map _certWilds = new HashMap<>();
private String[] _selectedProtocols;
private boolean _useCipherSuitesOrder = true;
private Comparator _cipherComparator;
@@ -453,6 +453,21 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
_certWilds.clear();
}
+ Map aliasCerts()
+ {
+ return _aliasX509;
+ }
+
+ Map hostCerts()
+ {
+ return _certHosts;
+ }
+
+ Map wildCerts()
+ {
+ return _certWilds;
+ }
+
@ManagedAttribute(value = "The selected TLS protocol versions", readonly = true)
public String[] getSelectedProtocols()
{
@@ -2157,7 +2172,7 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
boolean hasSniX509ExtendedKeyManager = false;
// Is SNI needed to select a certificate?
- if (!_certWilds.isEmpty() || _certHosts.size() > 1 || (_certHosts.size() == 1 && _aliasX509.size() > 1))
+ if (isSniRequired() || !wildCerts().isEmpty() || hostCerts().size() > 1 || (hostCerts().size() == 1 && aliasCerts().size() > 1))
{
for (int idx = 0; idx < managers.length; idx++)
{
@@ -2201,7 +2216,7 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
if (sniHost == null)
{
// No SNI, so reject or delegate.
- return _sniRequired ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
+ return isSniRequired() ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
}
else
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java
index 349ef807744..73f59a8cd3f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java
@@ -80,8 +80,7 @@ public class X509
String cn = list.get(1).toString();
if (LOG.isDebugEnabled())
LOG.debug("Certificate SAN alias={} CN={} in {}", alias, cn, this);
- if (cn != null)
- addName(cn);
+ addName(cn);
}
}
}
@@ -95,19 +94,21 @@ public class X509
String cn = rdn.getValue().toString();
if (LOG.isDebugEnabled())
LOG.debug("Certificate CN alias={} CN={} in {}", alias, cn, this);
- if (cn != null && cn.contains(".") && !cn.contains(" "))
- addName(cn);
+ addName(cn);
}
}
}
protected void addName(String cn)
{
- cn = StringUtil.asciiToLowerCase(cn);
- if (cn.startsWith("*."))
- _wilds.add(cn.substring(2));
- else
- _hosts.add(cn);
+ if (cn != null)
+ {
+ cn = StringUtil.asciiToLowerCase(cn);
+ if (cn.startsWith("*."))
+ _wilds.add(cn.substring(2));
+ else
+ _hosts.add(cn);
+ }
}
public String getAlias()
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
index f6270c1eace..ed937fe7915 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
@@ -282,7 +282,7 @@ public class SslContextFactoryTest
assertTrue(cf.getX509("other").matches("www.example.com"));
assertFalse(cf.getX509("other").matches("eclipse.org"));
- assertThat(cf.getX509("san").getHosts(), containsInAnyOrder("www.san.com", "m.san.com"));
+ assertThat(cf.getX509("san").getHosts(), containsInAnyOrder("san example", "www.san.com", "m.san.com"));
assertTrue(cf.getX509("san").getWilds().isEmpty());
assertTrue(cf.getX509("san").matches("www.san.com"));
assertTrue(cf.getX509("san").matches("m.san.com"));
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
index ac9e0ce1b9c..15aecd2a4a0 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
@@ -74,6 +74,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FuturePromise;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -397,6 +398,7 @@ public class AsyncIOServletTest extends AbstractTest consumer) throws Exception
+ {
+ server = new Server();
+ connector = consumer.apply(server);
+ server.addConnector(connector);
+ server.start();
+ }
+
+ @AfterEach
+ public void dispose() throws Exception
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testSelectFailureBetweenReads() throws Exception
+ {
+ // There will be 3 calls to select(): one at start(),
+ // one to accept, and one to set read interest.
+ CountDownLatch selectLatch = new CountDownLatch(3);
+ CountDownLatch failureLatch = new CountDownLatch(1);
+ AtomicBoolean fail = new AtomicBoolean();
+ start(server -> new ServerConnector(server, 1, 1)
+ {
+ @Override
+ protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ return new ServerConnectorManager(executor, scheduler, selectors)
+ {
+ @Override
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(this, id)
+ {
+ @Override
+ protected int nioSelect(Selector selector, boolean now) throws IOException
+ {
+ selectLatch.countDown();
+ if (fail.getAndSet(false))
+ throw new IOException("explicit select() failure");
+ return super.nioSelect(selector, now);
+ }
+
+ @Override
+ protected void handleSelectFailure(Selector selector, Throwable failure) throws IOException
+ {
+ super.handleSelectFailure(selector, failure);
+ failureLatch.countDown();
+ }
+ };
+ }
+ };
+ }
+ });
+
+ try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())))
+ {
+ assertTrue(selectLatch.await(5, TimeUnit.SECONDS));
+
+ String request = "GET / HTTP/1.0\r\n\r\n";
+ int split = request.length() / 2;
+ ByteBuffer chunk1 = StandardCharsets.UTF_8.encode(request.substring(0, split));
+ ByteBuffer chunk2 = StandardCharsets.UTF_8.encode(request.substring(split));
+
+ // Wake up the selector and fail it.
+ fail.set(true);
+ client.write(chunk1);
+
+ // Wait for the failure handling to be completed.
+ assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+ // Write the rest of the request, the
+ // server should be able to continue.
+ client.write(chunk2);
+
+ HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
+ }
+ }
+
+ @Test
+ public void testAcceptDuringSelectFailure() throws Exception
+ {
+ // There will be 3 calls to select(): one at start(),
+ // one to accept, and one to set read interest.
+ CountDownLatch selectLatch = new CountDownLatch(3);
+ CountDownLatch failureLatch = new CountDownLatch(1);
+ AtomicBoolean fail = new AtomicBoolean();
+ AtomicReference socketRef = new AtomicReference<>();
+ start(server -> new ServerConnector(server, 1, 1)
+ {
+ @Override
+ protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ return new ServerConnectorManager(executor, scheduler, selectors)
+ {
+ @Override
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(this, id)
+ {
+ @Override
+ protected int nioSelect(Selector selector, boolean now) throws IOException
+ {
+ selectLatch.countDown();
+ if (fail.getAndSet(false))
+ throw new IOException("explicit select() failure");
+ return super.nioSelect(selector, now);
+ }
+
+ @Override
+ protected void handleSelectFailure(Selector selector, Throwable failure) throws IOException
+ {
+ // Before handling the failure, connect with another socket.
+ SocketChannel socket = SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort()));
+ socketRef.set(socket);
+ super.handleSelectFailure(selector, failure);
+ failureLatch.countDown();
+ }
+ };
+ }
+ };
+ }
+ });
+
+ try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())))
+ {
+ assertTrue(selectLatch.await(5, TimeUnit.SECONDS));
+
+ String request = "GET / HTTP/1.0\r\n\r\n";
+ ByteBuffer buffer = StandardCharsets.UTF_8.encode(request);
+
+ // Wake up the selector and fail it.
+ fail.set(true);
+ client.write(buffer);
+
+ // Wait for the failure handling to be completed.
+ assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+ HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
+
+ // Verify that the newly created socket works well.
+ SocketChannel socket = socketRef.get();
+ buffer.flip();
+ socket.write(buffer);
+ response = HttpTester.parseResponse(HttpTester.from(socket));
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
+ }
+ }
+
+ @Test
+ public void testSelectFailureDuringEndPointCreation() throws Exception
+ {
+ // There will be 2 calls to select(): one at start(), one to accept.
+ CountDownLatch selectLatch = new CountDownLatch(2);
+ CountDownLatch failureLatch = new CountDownLatch(1);
+ AtomicBoolean fail = new AtomicBoolean();
+ CountDownLatch endPointLatch1 = new CountDownLatch(1);
+ CountDownLatch endPointLatch2 = new CountDownLatch(1);
+ start(server -> new ServerConnector(server, 1, 1)
+ {
+ @Override
+ protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ return new ServerConnectorManager(executor, scheduler, selectors)
+ {
+ @Override
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(this, id)
+ {
+ @Override
+ protected int nioSelect(Selector selector, boolean now) throws IOException
+ {
+ selectLatch.countDown();
+ if (fail.getAndSet(false))
+ throw new IOException("explicit select() failure");
+ return super.nioSelect(selector, now);
+ }
+
+ @Override
+ protected void handleSelectFailure(Selector selector, Throwable failure) throws IOException
+ {
+ super.handleSelectFailure(selector, failure);
+ failureLatch.countDown();
+ }
+ };
+ }
+
+ @Override
+ protected SocketChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
+ {
+ try
+ {
+ SocketChannelEndPoint endPoint = super.newEndPoint(channel, selector, selectionKey);
+ endPointLatch1.countDown();
+ assertTrue(endPointLatch2.await(5, TimeUnit.SECONDS));
+ return endPoint;
+ }
+ catch (InterruptedException x)
+ {
+ throw new InterruptedIOException();
+ }
+ }
+ };
+ }
+ });
+
+ try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())))
+ {
+ assertTrue(selectLatch.await(5, TimeUnit.SECONDS));
+
+ // Wait until the server EndPoint instance is created.
+ assertTrue(endPointLatch1.await(5, TimeUnit.SECONDS));
+
+ // Wake up the selector and fail it.
+ fail.set(true);
+ SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())).close();
+
+ // Wait until the selector is replaced.
+ assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+ // Continue the EndPoint creation.
+ endPointLatch2.countDown();
+
+ String request = "GET / HTTP/1.0\r\n\r\n";
+ ByteBuffer buffer = StandardCharsets.UTF_8.encode(request);
+ client.write(buffer);
+
+ HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
+ }
+ }
+
+ @Test
+ public void testSelectFailureDuringEndPointCreatedThenClosed() throws Exception
+ {
+ // There will be 2 calls to select(): one at start(), one to accept.
+ CountDownLatch selectLatch = new CountDownLatch(2);
+ CountDownLatch failureLatch = new CountDownLatch(1);
+ AtomicBoolean fail = new AtomicBoolean();
+ CountDownLatch connectionLatch1 = new CountDownLatch(1);
+ CountDownLatch connectionLatch2 = new CountDownLatch(1);
+ start(server -> new ServerConnector(server, 1, 1)
+ {
+ @Override
+ protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ return new ServerConnectorManager(executor, scheduler, selectors)
+ {
+ @Override
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(this, id)
+ {
+ @Override
+ protected int nioSelect(Selector selector, boolean now) throws IOException
+ {
+ selectLatch.countDown();
+ if (fail.getAndSet(false))
+ throw new IOException("explicit select() failure");
+ return super.nioSelect(selector, now);
+ }
+
+ @Override
+ protected void handleSelectFailure(Selector selector, Throwable failure) throws IOException
+ {
+ super.handleSelectFailure(selector, failure);
+ failureLatch.countDown();
+ }
+ };
+ }
+
+ @Override
+ public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
+ {
+ try
+ {
+ Connection connection = super.newConnection(channel, endPoint, attachment);
+ endPoint.close();
+ connectionLatch1.countDown();
+ assertTrue(connectionLatch2.await(5, TimeUnit.SECONDS));
+ return connection;
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException();
+ }
+ }
+ };
+ }
+ });
+
+ try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())))
+ {
+ assertTrue(selectLatch.await(5, TimeUnit.SECONDS));
+
+ // Wait until the server EndPoint is closed.
+ assertTrue(connectionLatch1.await(5, TimeUnit.SECONDS));
+
+ // Wake up the selector and fail it.
+ fail.set(true);
+ SocketChannel.open(new InetSocketAddress("localhost", connector.getLocalPort())).close();
+
+ // Wait until the selector is replaced.
+ assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+ // Continue the server processing.
+ connectionLatch2.countDown();
+
+ // The channel has been closed on the server.
+ int read = client.read(ByteBuffer.allocate(1));
+ assertTrue(read < 0);
+ }
+ }
+}
diff --git a/tests/test-integration/src/test/resources/jetty-logging.properties b/tests/test-integration/src/test/resources/jetty-logging.properties
index c3b261ea39e..ba4789f545f 100644
--- a/tests/test-integration/src/test/resources/jetty-logging.properties
+++ b/tests/test-integration/src/test/resources/jetty-logging.properties
@@ -1,5 +1,3 @@
# Jetty Logging using jetty-slf4j-impl
-## Jetty Logging using jetty-slf4j-impl
-org.eclipse.jetty.LEVEL=WARN
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.websocket.LEVEL=DEBUG