entry : hopHeaders.entrySet())
- {
- request.header(entry.getKey(), entry.getValue());
- }
+ hopHeaders.forEach((key, value) -> request.headers(headers -> headers.add(key, value)));
ContentResponse response = request
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -1283,7 +1280,7 @@ public class ProxyServletTest
CountDownLatch contentLatch = new CountDownLatch(1);
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
.body(new BytesRequestContent(content))
.onRequestContent((request, buffer) -> contentLatch.countDown())
.send(new BufferingResponseListener()
@@ -1340,7 +1337,7 @@ public class ProxyServletTest
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
.body(requestContent)
.send(new BufferingResponseListener()
{
@@ -1400,7 +1397,7 @@ public class ProxyServletTest
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
.body(requestContent)
.send(result ->
{
@@ -1448,7 +1445,7 @@ public class ProxyServletTest
CountDownLatch contentLatch = new CountDownLatch(1);
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
.body(new BytesRequestContent(content))
.onRequestContent((request, buffer) -> contentLatch.countDown())
.send(result ->
diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml
index f37980beea8..b9900ccb068 100644
--- a/jetty-server/pom.xml
+++ b/jetty-server/pom.xml
@@ -37,6 +37,14 @@
org.eclipse.jetty.server.*
+
+ maven-surefire-plugin
+
+
+ @{argLine} ${jetty.surefire.argLine} --add-opens org.eclipse.jetty.server/org.eclipse.jetty.server=ALL-UNNAMED
+
+
+
diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml
index 17aa2453587..ac0985b14cd 100644
--- a/jetty-server/src/main/config/etc/jetty-http.xml
+++ b/jetty-server/src/main/config/etc/jetty-http.xml
@@ -38,6 +38,10 @@
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml
index e53ce12c81b..54ab9d81b20 100644
--- a/jetty-server/src/main/config/etc/jetty-ssl.xml
+++ b/jetty-server/src/main/config/etc/jetty-ssl.xml
@@ -31,6 +31,10 @@
+
+
+
+
diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod
index a0b26e12663..acc0c7be6f4 100644
--- a/jetty-server/src/main/config/modules/http.mod
+++ b/jetty-server/src/main/config/modules/http.mod
@@ -39,5 +39,20 @@ etc/jetty-http.xml
## Thread priority delta to give to acceptor threads
# jetty.http.acceptorPriorityDelta=0
+## The requested maximum length of the queue of incoming connections.
+# jetty.http.acceptQueueSize=0
+
+## Enable/disable the SO_REUSEADDR socket option.
+# jetty.http.reuseAddress=true
+
+## Enable/disable TCP_NODELAY on accepted sockets.
+# jetty.http.acceptedTcpNoDelay=true
+
+## The SO_RCVBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
+# jetty.http.acceptedReceiveBufferSize=-1
+
+## The SO_SNDBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
+# jetty.http.acceptedSendBufferSize=-1
+
## Connect Timeout in milliseconds
# jetty.http.connectTimeout=15000
diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod
index bd64f9d7a7a..3395c7eda91 100644
--- a/jetty-server/src/main/config/modules/ssl.mod
+++ b/jetty-server/src/main/config/modules/ssl.mod
@@ -40,6 +40,21 @@ etc/jetty-ssl-context.xml
## Thread priority delta to give to acceptor threads
# jetty.ssl.acceptorPriorityDelta=0
+## The requested maximum length of the queue of incoming connections.
+# jetty.ssl.acceptQueueSize=0
+
+## Enable/disable the SO_REUSEADDR socket option.
+# jetty.ssl.reuseAddress=true
+
+## Enable/disable TCP_NODELAY on accepted sockets.
+# jetty.ssl.acceptedTcpNoDelay=true
+
+## The SO_RCVBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
+# jetty.ssl.acceptedReceiveBufferSize=-1
+
+## The SO_SNDBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
+# jetty.ssl.acceptedSendBufferSize=-1
+
## Connect Timeout in milliseconds
# jetty.ssl.connectTimeout=15000
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/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
index ddba526aab3..3ffb77571c8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
@@ -641,6 +641,10 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
if (LOG.isDebugEnabled())
LOG.debug("Proxy v2 {} {}", getEndPoint(), proxyEndPoint.toString());
}
+ else
+ {
+ _buffer.position(_buffer.position() + _length);
+ }
if (LOG.isDebugEnabled())
LOG.debug("Proxy v2 parsing dynamic packet part is now done, upgrading to {}", _nextProtocol);
@@ -777,7 +781,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
{
return _tlvs != null ? _tlvs.get(type) : null;
}
-
+
@Override
public void close(Throwable cause)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyCustomizer.java
new file mode 100644
index 00000000000..43fa5583f07
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyCustomizer.java
@@ -0,0 +1,108 @@
+//
+// ========================================================================
+// 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.server;
+
+import java.net.InetSocketAddress;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Attributes;
+
+/**
+ * Customizer that extracts the real local and remote address:port pairs from a {@link ProxyConnectionFactory}
+ * and sets them on the request with {@link ServletRequest#setAttribute(String, Object)}.
+ */
+public class ProxyCustomizer implements HttpConfiguration.Customizer
+{
+ /**
+ * The remote address attribute name.
+ */
+ public static final String REMOTE_ADDRESS_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.remote.address";
+
+ /**
+ * The remote port attribute name.
+ */
+ public static final String REMOTE_PORT_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.remote.port";
+
+ /**
+ * The local address attribute name.
+ */
+ public static final String LOCAL_ADDRESS_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.local.address";
+
+ /**
+ * The local port attribute name.
+ */
+ public static final String LOCAL_PORT_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.local.port";
+
+ @Override
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+ {
+ EndPoint endPoint = request.getHttpChannel().getEndPoint();
+ if (endPoint instanceof ProxyConnectionFactory.ProxyEndPoint)
+ {
+ EndPoint underlyingEndpoint = ((ProxyConnectionFactory.ProxyEndPoint)endPoint).unwrap();
+ request.setAttributes(new ProxyAttributes(underlyingEndpoint.getRemoteAddress(), underlyingEndpoint.getLocalAddress(), request.getAttributes()));
+ }
+ }
+
+ private static class ProxyAttributes extends Attributes.Wrapper
+ {
+ private final InetSocketAddress remoteAddress;
+ private final InetSocketAddress localAddress;
+
+ private ProxyAttributes(InetSocketAddress remoteAddress, InetSocketAddress localAddress, Attributes attributes)
+ {
+ super(attributes);
+ this.remoteAddress = remoteAddress;
+ this.localAddress = localAddress;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ switch (name)
+ {
+ case REMOTE_ADDRESS_ATTRIBUTE_NAME:
+ return remoteAddress.getAddress().getHostAddress();
+ case REMOTE_PORT_ATTRIBUTE_NAME:
+ return remoteAddress.getPort();
+ case LOCAL_ADDRESS_ATTRIBUTE_NAME:
+ return localAddress.getAddress().getHostAddress();
+ case LOCAL_PORT_ATTRIBUTE_NAME:
+ return localAddress.getPort();
+ default:
+ return super.getAttribute(name);
+ }
+ }
+
+ @Override
+ public Set getAttributeNameSet()
+ {
+ Set names = new HashSet<>(_attributes.getAttributeNameSet());
+ names.add(REMOTE_ADDRESS_ATTRIBUTE_NAME);
+ names.add(REMOTE_PORT_ATTRIBUTE_NAME);
+ names.add(LOCAL_ADDRESS_ATTRIBUTE_NAME);
+ names.add(LOCAL_PORT_ATTRIBUTE_NAME);
+ return names;
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
index ab745646d88..61095d646c0 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.server;
+import java.util.EnumSet;
+import java.util.Objects;
import java.util.Set;
import javax.servlet.http.PushBuilder;
@@ -27,6 +29,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,7 +41,14 @@ public class PushBuilderImpl implements PushBuilder
{
private static final Logger LOG = LoggerFactory.getLogger(PushBuilderImpl.class);
- private static final HttpField JettyPush = new HttpField("x-http2-push", "PushBuilder");
+ private static final HttpField JETTY_PUSH = new HttpField("x-http2-push", "PushBuilder");
+ private static EnumSet UNSAFE_METHODS = EnumSet.of(
+ HttpMethod.POST,
+ HttpMethod.PUT,
+ HttpMethod.DELETE,
+ HttpMethod.CONNECT,
+ HttpMethod.OPTIONS,
+ HttpMethod.TRACE);
private final Request _request;
private final HttpFields.Mutable _fields;
@@ -56,7 +66,7 @@ public class PushBuilderImpl implements PushBuilder
_method = method;
_queryString = queryString;
_sessionId = sessionId;
- _fields.add(JettyPush);
+ _fields.add(JETTY_PUSH);
if (LOG.isDebugEnabled())
LOG.debug("PushBuilder({} {}?{} s={} c={})", _method, _request.getRequestURI(), _queryString, _sessionId);
}
@@ -70,6 +80,10 @@ public class PushBuilderImpl implements PushBuilder
@Override
public PushBuilder method(String method)
{
+ Objects.requireNonNull(method);
+
+ if (StringUtil.isBlank(method) || UNSAFE_METHODS.contains(HttpMethod.fromString(method)))
+ throw new IllegalArgumentException("Method not allowed for push: " + method);
_method = method;
return this;
}
@@ -149,9 +163,6 @@ public class PushBuilderImpl implements PushBuilder
@Override
public void push()
{
- if (HttpMethod.POST.is(_method) || HttpMethod.PUT.is(_method))
- throw new IllegalStateException("Bad Method " + _method);
-
if (_path == null || _path.length() == 0)
throw new IllegalStateException("Bad Path " + _path);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index d5426fbfc14..745290bb93a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -71,6 +71,7 @@ import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@@ -393,6 +394,11 @@ public class Request implements HttpServletRequest
HttpFields.Mutable fields = HttpFields.build(getHttpFields(), NOT_PUSHED_HEADERS);
+ HttpField authField = getHttpFields().getField(HttpHeader.AUTHORIZATION);
+ //TODO check what to do for digest etc etc
+ if (getUserPrincipal() != null && authField.getValue().startsWith("Basic"))
+ fields.add(authField);
+
String id;
try
{
@@ -410,12 +416,47 @@ public class Request implements HttpServletRequest
id = getRequestedSessionId();
}
+ Map cookies = new HashMap<>();
+ Cookie[] existingCookies = getCookies();
+ if (existingCookies != null)
+ {
+ for (Cookie c: getCookies())
+ {
+ cookies.put(c.getName(), c.getValue());
+ }
+ }
+
+ //Any Set-Cookies that were set on the response must be set as Cookies on the
+ //PushBuilder, unless the max-age of the cookie is <= 0
+ HttpFields responseFields = getResponse().getHttpFields();
+ for (HttpField field : responseFields)
+ {
+ HttpHeader header = field.getHeader();
+ if (header == HttpHeader.SET_COOKIE)
+ {
+ HttpCookie cookie = ((SetCookieHttpField)field).getHttpCookie();
+ if (cookie.getMaxAge() > 0)
+ cookies.put(cookie.getName(), cookie.getValue());
+ else
+ cookies.remove(cookie.getName());
+ }
+ }
+
+ if (!cookies.isEmpty())
+ {
+ StringBuilder buff = new StringBuilder();
+ for (Map.Entry entry : cookies.entrySet())
+ {
+ if (buff.length() > 0)
+ buff.append("; ");
+ buff.append(entry.getKey()).append('=').append(entry.getValue());
+ }
+ fields.add(new HttpField(HttpHeader.COOKIE, buff.toString()));
+ }
+
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), getQueryString(), id);
builder.addHeader("referer", getRequestURL().toString());
- // TODO process any set cookies
- // TODO process any user_identity
-
return builder;
}
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..d3d4d265044 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;
@@ -83,6 +82,9 @@ public class ServerConnector extends AbstractNetworkConnector
private volatile int _localPort = -1;
private volatile int _acceptQueueSize = 0;
private volatile boolean _reuseAddress = true;
+ private volatile boolean _acceptedTcpNoDelay = true;
+ private volatile int _acceptedReceiveBufferSize = -1;
+ private volatile int _acceptedSendBufferSize = -1;
/**
* Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.
@@ -397,7 +399,11 @@ public class ServerConnector extends AbstractNetworkConnector
{
try
{
- socket.setTcpNoDelay(true);
+ socket.setTcpNoDelay(_acceptedTcpNoDelay);
+ if (_acceptedReceiveBufferSize > -1)
+ socket.setReceiveBufferSize(_acceptedReceiveBufferSize);
+ if (_acceptedSendBufferSize > -1)
+ socket.setSendBufferSize(_acceptedSendBufferSize);
}
catch (SocketException e)
{
@@ -424,7 +430,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());
@@ -452,6 +458,7 @@ public class ServerConnector extends AbstractNetworkConnector
* @return whether the server socket reuses addresses
* @see ServerSocket#getReuseAddress()
*/
+ @ManagedAttribute("Server Socket SO_REUSEADDR")
public boolean getReuseAddress()
{
return _reuseAddress;
@@ -466,6 +473,67 @@ public class ServerConnector extends AbstractNetworkConnector
_reuseAddress = reuseAddress;
}
+ /**
+ * @return whether the accepted socket gets {@link java.net.SocketOptions#TCP_NODELAY TCP_NODELAY} enabled.
+ * @see Socket#getTcpNoDelay()
+ */
+ @ManagedAttribute("Accepted Socket TCP_NODELAY")
+ public boolean getAcceptedTcpNoDelay()
+ {
+ return _acceptedTcpNoDelay;
+ }
+
+ /**
+ * @param tcpNoDelay whether {@link java.net.SocketOptions#TCP_NODELAY TCP_NODELAY} gets enabled on the the accepted socket.
+ * @see Socket#setTcpNoDelay(boolean)
+ */
+ public void setAcceptedTcpNoDelay(boolean tcpNoDelay)
+ {
+ this._acceptedTcpNoDelay = tcpNoDelay;
+ }
+
+ /**
+ * @return the {@link java.net.SocketOptions#SO_RCVBUF SO_RCVBUF} size to set onto the accepted socket.
+ * A value of -1 indicates that it is left to its default value.
+ * @see Socket#getReceiveBufferSize()
+ */
+ @ManagedAttribute("Accepted Socket SO_RCVBUF")
+ public int getAcceptedReceiveBufferSize()
+ {
+ return _acceptedReceiveBufferSize;
+ }
+
+ /**
+ * @param receiveBufferSize the {@link java.net.SocketOptions#SO_RCVBUF SO_RCVBUF} size to set onto the accepted socket.
+ * A value of -1 indicates that it is left to its default value.
+ * @see Socket#setReceiveBufferSize(int)
+ */
+ public void setAcceptedReceiveBufferSize(int receiveBufferSize)
+ {
+ this._acceptedReceiveBufferSize = receiveBufferSize;
+ }
+
+ /**
+ * @return the {@link java.net.SocketOptions#SO_SNDBUF SO_SNDBUF} size to set onto the accepted socket.
+ * A value of -1 indicates that it is left to its default value.
+ * @see Socket#getSendBufferSize()
+ */
+ @ManagedAttribute("Accepted Socket SO_SNDBUF")
+ public int getAcceptedSendBufferSize()
+ {
+ return _acceptedSendBufferSize;
+ }
+
+ /**
+ * @param sendBufferSize the {@link java.net.SocketOptions#SO_SNDBUF SO_SNDBUF} size to set onto the accepted socket.
+ * A value of -1 indicates that it is left to its default value.
+ * @see Socket#setSendBufferSize(int)
+ */
+ public void setAcceptedSendBufferSize(int sendBufferSize)
+ {
+ this._acceptedSendBufferSize = sendBufferSize;
+ }
+
@Override
public void setAccepting(boolean accepting)
{
@@ -511,9 +579,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 60291739302..7cde76cdf40 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 javax.servlet.http.HttpServletRequest;
import javax.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 9afea19e7b5..d96a48b9dda 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 javax.servlet.http.HttpServletRequest;
import javax.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/ProxyConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyConnectionTest.java
index 93bf04a133b..c5ba725069a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyConnectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyConnectionTest.java
@@ -151,6 +151,39 @@ public class ProxyConnectionTest
assertThat(response, Matchers.containsString("remote=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:12345"));
}
+ @ParameterizedTest
+ @MethodSource("requestProcessors")
+ public void testLocalV2(RequestProcessor p) throws Exception
+ {
+ String proxy =
+ // Preamble
+ "0D0A0D0A000D0A515549540A" +
+
+ // V2, LOCAL
+ "20" +
+
+ // 0x1 : AF_INET 0x1 : STREAM.
+ "11" +
+
+ // Address length is 16.
+ "0010" +
+
+ // gibberish
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ ;
+ String http = "GET /path HTTP/1.1\n" +
+ "Host: server:80\n" +
+ "Connection: close\n" +
+ "\n";
+
+ String response = p.sendRequestWaitingForResponse(TypeUtil.fromHexString(proxy), http.getBytes(StandardCharsets.US_ASCII));
+
+ assertThat(response, Matchers.containsString("HTTP/1.1 200"));
+ assertThat(response, Matchers.containsString("pathInfo=/path"));
+ assertThat(response, Matchers.containsString("local=0.0.0.0:0"));
+ assertThat(response, Matchers.containsString("remote=0.0.0.0:0"));
+ }
+
@ParameterizedTest
@MethodSource("requestProcessors")
public void testMissingField(RequestProcessor p) throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyCustomizerTest.java
new file mode 100644
index 00000000000..c5423a568c8
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyCustomizerTest.java
@@ -0,0 +1,178 @@
+//
+// ========================================================================
+// 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.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ProxyCustomizerTest
+{
+ private Server server;
+
+ private ProxyResponse sendProxyRequest(String proxyAsHexString, String rawHttp) throws IOException
+ {
+ try (Socket socket = new Socket(server.getURI().getHost(), server.getURI().getPort()))
+ {
+ OutputStream output = socket.getOutputStream();
+ output.write(TypeUtil.fromHexString(proxyAsHexString));
+ output.write(rawHttp.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+ socket.shutdownOutput();
+
+ StringBuilder sb = new StringBuilder();
+
+ InputStream input = socket.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ while (true)
+ {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+ sb.append(line).append("\r\n");
+ }
+
+ return new ProxyResponse((InetSocketAddress)socket.getLocalSocketAddress(), (InetSocketAddress)socket.getRemoteSocketAddress(), sb.toString());
+ }
+ }
+
+ private static class ProxyResponse
+ {
+ private final InetSocketAddress localSocketAddress;
+ private final InetSocketAddress remoteSocketAddress;
+ private final String httpResponse;
+
+ public ProxyResponse(InetSocketAddress localSocketAddress, InetSocketAddress remoteSocketAddress, String httpResponse)
+ {
+ this.localSocketAddress = localSocketAddress;
+ this.remoteSocketAddress = remoteSocketAddress;
+ this.httpResponse = httpResponse;
+ }
+ }
+
+ @BeforeEach
+ void setUp() throws Exception
+ {
+ Handler handler = new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ response.addHeader("preexisting.attribute", request.getAttribute("some.attribute").toString());
+ ArrayList attributeNames = Collections.list(request.getAttributeNames());
+ Collections.sort(attributeNames);
+ response.addHeader("attributeNames", String.join(",", attributeNames));
+
+ response.addHeader("localAddress", request.getLocalAddr() + ":" + request.getLocalPort());
+ response.addHeader("remoteAddress", request.getRemoteAddr() + ":" + request.getRemotePort());
+ Object localAddress = request.getAttribute(ProxyCustomizer.LOCAL_ADDRESS_ATTRIBUTE_NAME);
+ if (localAddress != null)
+ response.addHeader("proxyLocalAddress", localAddress.toString() + ":" + request.getAttribute(ProxyCustomizer.LOCAL_PORT_ATTRIBUTE_NAME));
+ Object remoteAddress = request.getAttribute(ProxyCustomizer.REMOTE_ADDRESS_ATTRIBUTE_NAME);
+ if (remoteAddress != null)
+ response.addHeader("proxyRemoteAddress", remoteAddress.toString() + ":" + request.getAttribute(ProxyCustomizer.REMOTE_PORT_ATTRIBUTE_NAME));
+
+ baseRequest.setHandled(true);
+ }
+ };
+
+ server = new Server();
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ httpConfiguration.addCustomizer((connector, channelConfig, request) -> request.setAttribute("some.attribute", "some value"));
+ httpConfiguration.addCustomizer(new ProxyCustomizer());
+ ServerConnector connector = new ServerConnector(server, new ProxyConnectionFactory(), new HttpConnectionFactory(httpConfiguration));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception
+ {
+ server.stop();
+ server = null;
+ }
+
+ @Test
+ void testProxyCustomizerWithProxyData() throws Exception
+ {
+ String proxy =
+ // Preamble
+ "0D0A0D0A000D0A515549540A" +
+ // V2, PROXY
+ "21" +
+ // 0x1 : AF_INET 0x1 : STREAM. Address length is 2*4 + 2*2 = 12 bytes.
+ "11" +
+ // length of remaining header (4+4+2+2 = 12)
+ "000C" +
+ // uint32_t src_addr; uint32_t dst_addr; uint16_t src_port; uint16_t dst_port;
+ "01010001" +
+ "010100FE" +
+ "3039" +
+ "1F90";
+ String http = "GET /1 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+
+ ProxyResponse response = sendProxyRequest(proxy, http);
+
+ assertThat(response.httpResponse, Matchers.containsString("localAddress: 1.1.0.254:8080"));
+ assertThat(response.httpResponse, Matchers.containsString("remoteAddress: 1.1.0.1:12345"));
+ assertThat(response.httpResponse, Matchers.containsString("proxyLocalAddress: " + response.remoteSocketAddress.getAddress().getHostAddress() + ":" + response.remoteSocketAddress.getPort()));
+ assertThat(response.httpResponse, Matchers.containsString("proxyRemoteAddress: " + response.localSocketAddress.getAddress().getHostAddress() + ":" + response.localSocketAddress.getPort()));
+ assertThat(response.httpResponse, Matchers.containsString("preexisting.attribute: some value"));
+ assertThat(response.httpResponse, Matchers.containsString("attributeNames: org.eclipse.jetty.proxy.local.address,org.eclipse.jetty.proxy.local.port,org.eclipse.jetty.proxy.remote.address,org.eclipse.jetty.proxy.remote.port,some.attribute"));
+ }
+
+ @Test
+ void testProxyCustomizerWithoutProxyData() throws Exception
+ {
+ String proxy = "";
+ String http = "GET /1 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+
+ ProxyResponse response = sendProxyRequest(proxy, http);
+
+ assertThat(response.httpResponse, Matchers.containsString("localAddress: " + response.remoteSocketAddress.getAddress().getHostAddress() + ":" + response.remoteSocketAddress.getPort()));
+ assertThat(response.httpResponse, Matchers.containsString("remoteAddress: " + response.localSocketAddress.getAddress().getHostAddress() + ":" + response.localSocketAddress.getPort()));
+ assertThat(response.httpResponse, Matchers.not(Matchers.containsString("proxyLocalAddress: ")));
+ assertThat(response.httpResponse, Matchers.not(Matchers.containsString("proxyRemoteAddress: ")));
+ assertThat(response.httpResponse, Matchers.containsString("preexisting.attribute: some value"));
+ assertThat(response.httpResponse, Matchers.containsString("attributeNames: some.attribute"));
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyProtocolTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyProtocolTest.java
index 45a536c7d19..25be0bd9631 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyProtocolTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ProxyProtocolTest.java
@@ -216,5 +216,81 @@ public class ProxyProtocolTest
}
}
}
-
+
+ @Test
+ public void testProxyProtocolV2Local() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ }
+ });
+
+ try (Socket socket = new Socket("localhost", connector.getLocalPort()))
+ {
+ String proxy =
+ // Preamble
+ "0D0A0D0A000D0A515549540A" +
+
+ // V2, LOCAL
+ "20" +
+
+ // 0x1 : AF_INET 0x1 : STREAM. Address length is 2*4 + 2*2 = 12 bytes.
+ "11" +
+
+ // length of remaining header (4+4+2+2+6+3 = 21)
+ "0015" +
+
+ // uint32_t src_addr; uint32_t dst_addr; uint16_t src_port; uint16_t dst_port;
+ "C0A80001" +
+ "7f000001" +
+ "3039" +
+ "1F90" +
+
+ // NOOP value 0
+ "040000" +
+
+ // NOOP value ABCDEF
+ "040003ABCDEF";
+
+ String request1 =
+ "GET /1 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+ OutputStream output = socket.getOutputStream();
+ output.write(TypeUtil.fromHexString(proxy));
+ output.write(request1.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ InputStream input = socket.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ String response1 = reader.readLine();
+ assertTrue(response1.startsWith("HTTP/1.1 200 "));
+ while (true)
+ {
+ if (reader.readLine().isEmpty())
+ break;
+ }
+
+ // Send a second request to verify that the proxied IP is retained.
+ String request2 =
+ "GET /2 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
+ output.write(request2.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ String response2 = reader.readLine();
+ assertTrue(response2.startsWith("HTTP/1.1 200 "));
+ while (true)
+ {
+ if (reader.readLine() == null)
+ break;
+ }
+ }
+ }
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index e6c0a0ce660..a23c4c8aef4 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -27,6 +27,7 @@ import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
+import java.security.Principal;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -45,10 +46,19 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
+import javax.servlet.http.PushBuilder;
import org.eclipse.jetty.http.BadMessageException;
+import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.http.tools.HttpTester;
@@ -57,6 +67,9 @@ import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.session.Session;
+import org.eclipse.jetty.server.session.SessionData;
+import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
@@ -1860,11 +1873,128 @@ public class RequestTest
assertNotNull(request.getParameterMap());
assertEquals(0, request.getParameterMap().size());
}
+
+ @Test
+ public void testPushBuilder() throws Exception
+ {
+ String uri = "/foo/something";
+ Request request = new TestRequest(null, null);
+ request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("good","thumbsup", 100), CookieCompliance.RFC6265));
+ request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("bonza","bewdy", 1), CookieCompliance.RFC6265));
+ request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("bad", "thumbsdown", 0), CookieCompliance.RFC6265));
+ HttpFields.Mutable fields = HttpFields.build();
+ fields.add(HttpHeader.AUTHORIZATION, "Basic foo");
+ request.setMetaData(new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_1_0, fields));
+ assertTrue(request.isPushSupported());
+ PushBuilder builder = request.newPushBuilder();
+ assertNotNull(builder);
+ assertEquals("GET", builder.getMethod());
+ assertThrows(NullPointerException.class, () ->
+ {
+ builder.method(null);
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method(" ");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("POST");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("PUT");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("DELETE");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("CONNECT");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("OPTIONS");
+ });
+ assertThrows(IllegalArgumentException.class, () ->
+ {
+ builder.method("TRACE");
+ });
+ assertEquals(TestRequest.TEST_SESSION_ID, builder.getSessionId());
+ builder.path("/foo/something-else.txt");
+ assertEquals("/foo/something-else.txt", builder.getPath());
+ assertEquals("Basic foo", builder.getHeader("Authorization"));
+ assertThat(builder.getHeader("Cookie"), containsString("bonza"));
+ assertThat(builder.getHeader("Cookie"), containsString("good"));
+ assertThat(builder.getHeader("Cookie"), containsString("maxpos"));
+ assertThat(builder.getHeader("Cookie"), not(containsString("bad")));
+ }
interface RequestTester
{
boolean check(HttpServletRequest request, HttpServletResponse response) throws IOException;
}
+
+ private class TestRequest extends Request
+ {
+ public static final String TEST_SESSION_ID = "abc123";
+ Response _response = new Response(null, null);
+ Cookie c1;
+ Cookie c2;
+
+ public TestRequest(HttpChannel channel, HttpInput input)
+ {
+ super(channel, input);
+ c1 = new Cookie("maxpos", "xxx");
+ c1.setMaxAge(1);
+ c2 = new Cookie("maxneg", "yyy");
+ c2.setMaxAge(-1);
+ }
+
+ @Override
+ public boolean isPushSupported()
+ {
+ return true;
+ }
+
+ @Override
+ public HttpSession getSession()
+ {
+ return new Session(new SessionHandler(), new SessionData(TEST_SESSION_ID, "", "0.0.0.0", 0, 0, 0, 300));
+ }
+
+ @Override
+ public Principal getUserPrincipal()
+ {
+ return new Principal()
+ {
+
+ @Override
+ public String getName()
+ {
+ return "user";
+ }
+
+ };
+ }
+
+ @Override
+ public Response getResponse()
+ {
+ return _response;
+ }
+
+ @Override
+ public Cookie[] getCookies()
+ {
+ return new Cookie[] {c1,c2};
+ }
+ }
private class RequestHandler extends AbstractHandler
{
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 3fe424c2072..2f8bf9b9546 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 668494a0566..6eb21fd2bbc 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/FormTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java
index f6789252dbc..820b5d62dac 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java
@@ -136,7 +136,7 @@ public class FormTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
- .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
+ .headers(headers -> headers.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString()))
.body(content)
.onRequestBegin(request ->
{
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java
index 2f6609016a5..2739bbe77d7 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
@@ -85,7 +86,7 @@ public class GzipHandlerBreakEvenSizeTest
{
URI uri = server.getURI().resolve("/content?size=" + size);
ContentResponse response = client.newRequest(uri)
- .header(HttpHeader.ACCEPT_ENCODING, "gzip")
+ .headers(headers -> headers.put(HttpHeader.ACCEPT_ENCODING, HttpHeaderValue.GZIP))
.send();
assertThat("Status Code", response.getStatus(), is(200));
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 7732af42f47..869f24e94a5 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
@@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
-public abstract class AsyncScheduledDispatchWrite extends TestDirContentServlet
+public abstract class AsyncScheduledDispatchWrite extends AbstractFileContentServlet
{
public static class Default extends AsyncScheduledDispatchWrite
{
@@ -102,7 +102,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 f2e1ebd3006..ab25a4c4e44 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
@@ -43,7 +43,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
{
@@ -86,7 +86,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 8ad05d75ead..567f86e259c 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
@@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.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
{
@@ -77,7 +77,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 618a8a3dba6..ab37d9d6afe 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 65c0184b133..9ff632ed7a9 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 b83d56a3e67..063cb6a9a82 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 45901e5fe42..3fa9b757615 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 2ccc85c2d7d..7c793b8f25a 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 f3ee50fe588..0af3dc38eac 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 21ec41b19fc..0c622d54667 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 a30150100c7..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 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.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 61bb4fab8be..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 javax.servlet.DispatcherType;
-import javax.servlet.Servlet;
-import javax.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 1d462fa8e6e..654dcdf78a2 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 b040a97e321..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipMinSizeTest.java
+++ /dev/null
@@ -1,95 +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 javax.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 50d7526bf67..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 javax.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 7c5f1a00178..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 javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.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 8b24aaece49..87e60a377fb 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 d45ea8037bd..7d378f6f271 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 dd5db390cea..b97d52c9814 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,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.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 +105,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 +257,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 8add2ca82b3..a8f10b39001 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/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
index 5bbf1e1118b..c38fa02a63b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
@@ -48,6 +48,7 @@ public class ShutdownThread extends Thread
*/
private ShutdownThread()
{
+ super("JettyShutdownThread");
}
private synchronized void hook()
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/jetty-websocket/pom.xml b/jetty-websocket/pom.xml
index b2952f150a8..3d74b9c9e91 100644
--- a/jetty-websocket/pom.xml
+++ b/jetty-websocket/pom.xml
@@ -15,8 +15,8 @@
websocket-core
- websocket-servlet
websocket-util
+ websocket-util-server
websocket-jetty-api
websocket-jetty-common
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java
index 0e21f6104a2..54048a41692 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java
@@ -54,16 +54,17 @@ public class HttpUpgraderOverHTTP implements HttpUpgrader
public void prepare(HttpRequest request)
{
request.method(HttpMethod.GET).version(HttpVersion.HTTP_1_1)
- .add(WS_VERSION_FIELD)
- .add(WS_UPGRADE_FIELD)
- .add(WS_CONNECTION_FIELD)
- .header(HttpHeader.SEC_WEBSOCKET_KEY, generateRandomKey())
- // Per the hybi list: Add no-cache headers to avoid compatibility issue.
- // There are some proxies that rewrite "Connection: upgrade" to
- // "Connection: close" in the response if a request doesn't contain
- // these headers.
- .add(PRAGMA_NO_CACHE_FIELD)
- .add(CACHE_CONTROL_NO_CACHE_FIELD);
+ .headers(headers -> headers
+ .put(WS_VERSION_FIELD)
+ .put(WS_UPGRADE_FIELD)
+ .put(WS_CONNECTION_FIELD)
+ .put(HttpHeader.SEC_WEBSOCKET_KEY, generateRandomKey())
+ // Per the hybi list: Add no-cache headers to avoid compatibility issue.
+ // There are some proxies that rewrite "Connection: upgrade" to
+ // "Connection: close" in the response if a request doesn't contain
+ // these headers.
+ .put(PRAGMA_NO_CACHE_FIELD)
+ .put(CACHE_CONTROL_NO_CACHE_FIELD));
// Notify the UpgradeListeners now the headers are set.
clientUpgradeRequest.requestComplete();
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java
index e1904a0b419..422e4a14187 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java
@@ -41,9 +41,9 @@ public class HttpUpgraderOverHTTP2 implements HttpUpgrader
@Override
public void prepare(HttpRequest request)
{
- request.method(HttpMethod.CONNECT);
- request.upgradeProtocol("websocket");
- request.add(WS_VERSION_FIELD);
+ request.upgradeProtocol("websocket")
+ .method(HttpMethod.CONNECT)
+ .headers(headers -> headers.put(WS_VERSION_FIELD));
// Notify the UpgradeListeners now the headers are set.
clientUpgradeRequest.requestComplete();
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java
index 4cee6345c71..ae25d7bbe94 100644
--- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java
@@ -387,7 +387,7 @@ public class WebSocketNegotiationTest extends WebSocketTester
@Override
public void onHandshakeRequest(HttpRequest request)
{
- request.header(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, "permessage-deflate");
+ request.headers(headers -> headers.put(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, "permessage-deflate"));
}
@Override
diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java
index bdf93ba6dcd..8202b43a44a 100644
--- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java
+++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java
@@ -40,11 +40,14 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
+import org.eclipse.jetty.websocket.core.exception.UpgradeException;
+import org.eclipse.jetty.websocket.core.exception.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.javax.common.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketExtensionConfig;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandler;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
/**
* Container for Client use of the javax.websocket API.
@@ -131,7 +134,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
{
if (error != null)
{
- futureSession.completeExceptionally(error);
+ futureSession.completeExceptionally(convertCause(error));
return;
}
@@ -147,6 +150,18 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
return futureSession;
}
+ public static Throwable convertCause(Throwable error)
+ {
+ if (error instanceof UpgradeException ||
+ error instanceof WebSocketTimeoutException)
+ return new IOException(error);
+
+ if (error instanceof InvalidWebSocketException)
+ return new DeploymentException(error.getMessage(), error);
+
+ return error;
+ }
+
private Session connect(ConfiguredEndpoint configuredEndpoint, URI destURI) throws IOException
{
Objects.requireNonNull(configuredEndpoint, "WebSocket configured endpoint cannot be null");
diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketAsyncRemote.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketAsyncRemote.java
index ab86f54b4bd..185c89715c8 100644
--- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketAsyncRemote.java
+++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketAsyncRemote.java
@@ -31,7 +31,7 @@ import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
-import org.eclipse.jetty.websocket.util.TextUtil;
+import org.eclipse.jetty.websocket.util.TextUtils;
import org.eclipse.jetty.websocket.util.messages.MessageOutputStream;
import org.eclipse.jetty.websocket.util.messages.MessageWriter;
import org.slf4j.Logger;
@@ -184,7 +184,7 @@ public class JavaxWebSocketAsyncRemote extends JavaxWebSocketRemoteEndpoint impl
assertMessageNotNull(text);
if (LOG.isDebugEnabled())
{
- LOG.debug("sendText({})", TextUtil.hint(text));
+ LOG.debug("sendText({})", TextUtils.hint(text));
}
FutureCallback future = new FutureCallback();
sendFrame(new Frame(OpCode.TEXT).setPayload(text), future, batch);
@@ -198,7 +198,7 @@ public class JavaxWebSocketAsyncRemote extends JavaxWebSocketRemoteEndpoint impl
assertSendHandlerNotNull(handler);
if (LOG.isDebugEnabled())
{
- LOG.debug("sendText({},{})", TextUtil.hint(text), handler);
+ LOG.debug("sendText({},{})", TextUtils.hint(text), handler);
}
sendFrame(new Frame(OpCode.TEXT).setPayload(text), new SendHandlerCallback(handler), batch);
}
diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketBasicRemote.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketBasicRemote.java
index a119ebf1f9c..2519e4be72f 100644
--- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketBasicRemote.java
+++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketBasicRemote.java
@@ -31,7 +31,7 @@ import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
-import org.eclipse.jetty.websocket.util.TextUtil;
+import org.eclipse.jetty.websocket.util.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -118,7 +118,7 @@ public class JavaxWebSocketBasicRemote extends JavaxWebSocketRemoteEndpoint impl
assertMessageNotNull(text);
if (LOG.isDebugEnabled())
{
- LOG.debug("sendText({})", TextUtil.hint(text));
+ LOG.debug("sendText({})", TextUtils.hint(text));
}
FutureCallback b = new FutureCallback();
@@ -132,7 +132,7 @@ public class JavaxWebSocketBasicRemote extends JavaxWebSocketRemoteEndpoint impl
assertMessageNotNull(partialMessage);
if (LOG.isDebugEnabled())
{
- LOG.debug("sendText({},{})", TextUtil.hint(partialMessage), isLast);
+ LOG.debug("sendText({},{})", TextUtils.hint(partialMessage), isLast);
}
Frame frame;
diff --git a/jetty-websocket/websocket-javax-server/pom.xml b/jetty-websocket/websocket-javax-server/pom.xml
index f33263ba474..d629de14878 100644
--- a/jetty-websocket/websocket-javax-server/pom.xml
+++ b/jetty-websocket/websocket-javax-server/pom.xml
@@ -22,7 +22,7 @@
org.eclipse.jetty.websocket
- websocket-servlet
+ websocket-util-server
${project.version}
diff --git a/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod b/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod
index 2f0ae30c75b..d46554bdfaf 100644
--- a/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod
+++ b/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod
@@ -12,8 +12,8 @@ annotations
[lib]
lib/websocket/websocket-core-${jetty.version}.jar
-lib/websocket/websocket-servlet-${jetty.version}.jar
lib/websocket/websocket-util-${jetty.version}.jar
+lib/websocket/websocket-util-server-${jetty.version}.jar
lib/websocket/jetty-javax-websocket-api-1.1.2.jar
lib/websocket/websocket-javax-client-${jetty.version}.jar
lib/websocket/websocket-javax-common-${jetty.version}.jar
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/module-info.java b/jetty-websocket/websocket-javax-server/src/main/java/module-info.java
index 6d52f89a355..af5c88bbdd4 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/module-info.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/module-info.java
@@ -31,7 +31,7 @@ module org.eclipse.jetty.websocket.javax.server
requires transitive org.eclipse.jetty.webapp;
requires transitive org.eclipse.jetty.websocket.javax.client;
requires org.eclipse.jetty.websocket.javax.common;
- requires org.eclipse.jetty.websocket.servlet;
+ requires org.eclipse.jetty.websocket.util.server;
requires org.slf4j;
provides ServletContainerInitializer with JavaxWebSocketServletContainerInitializer;
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketConfiguration.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketConfiguration.java
index 4c66e39abed..6075eeb66e3 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketConfiguration.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketConfiguration.java
@@ -37,7 +37,7 @@ public class JavaxWebSocketConfiguration extends AbstractConfiguration
{
addDependencies(WebXmlConfiguration.class, MetaInfConfiguration.class, WebInfConfiguration.class, FragmentConfiguration.class);
addDependents("org.eclipse.jetty.annotations.AnnotationConfiguration", WebAppConfiguration.class.getName());
- protectAndExpose("org.eclipse.jetty.websocket.servlet."); // For WebSocketUpgradeFilter
+ protectAndExpose("org.eclipse.jetty.websocket.util.server."); // For WebSocketUpgradeFilter
protectAndExpose("org.eclipse.jetty.websocket.javax.server.config.");
protectAndExpose("org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainerProvider");
hide("org.eclipse.jetty.websocket.javax.server.internal");
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java
index 797c9c1f1a8..87739c6a434 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java
@@ -38,8 +38,8 @@ import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer;
-import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
-import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java
index 69624723ec8..b38d3779863 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java
@@ -22,7 +22,7 @@ import java.net.URI;
import java.security.Principal;
import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
public class JavaxServerUpgradeRequest implements UpgradeRequest
{
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java
index de98982caac..e66f98c19ed 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java
@@ -37,9 +37,9 @@ import org.eclipse.jetty.websocket.javax.common.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketExtension;
import org.eclipse.jetty.websocket.javax.common.ServerEndpointConfigWrapper;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
index 5f7bd46f996..35a5ba4a86c 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
@@ -39,7 +39,7 @@ import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java
index a31ea1741e6..bb81bbc3704 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java
@@ -27,9 +27,9 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClientFrameHandlerFactory implements FrameHandlerFactory
{
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java
index be31158e1f2..dbb8e1f1fb3 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java
@@ -25,7 +25,7 @@ import java.util.Map;
import javax.websocket.server.HandshakeRequest;
import org.eclipse.jetty.http.pathmap.PathSpec;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
public class JsrHandshakeRequest implements HandshakeRequest
{
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java
index 8e25272dc36..80ec6a7502b 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java
@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Map;
import javax.websocket.HandshakeResponse;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
public class JsrHandshakeResponse implements HandshakeResponse
{
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/PathParamIdentifier.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/PathParamIdentifier.java
index 3b26056b2cc..44410741311 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/PathParamIdentifier.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/PathParamIdentifier.java
@@ -22,6 +22,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javax.websocket.server.PathParam;
+import org.eclipse.jetty.websocket.util.InvalidSignatureException;
import org.eclipse.jetty.websocket.util.InvokerUtils;
/**
@@ -40,6 +41,7 @@ public class PathParamIdentifier implements InvokerUtils.ParamIdentifier
{
if (anno.annotationType().equals(PathParam.class))
{
+ validateType(paramType);
PathParam pathParam = (PathParam)anno;
return new InvokerUtils.Arg(paramType, pathParam.value());
}
@@ -47,4 +49,22 @@ public class PathParamIdentifier implements InvokerUtils.ParamIdentifier
}
return new InvokerUtils.Arg(paramType);
}
+
+ /**
+ * The JSR356 rules for @PathParam only support
+ * String, Primitive Types (and their Boxed version)
+ */
+ public static void validateType(Class> type)
+ {
+ if (!String.class.isAssignableFrom(type) &&
+ !Integer.TYPE.isAssignableFrom(type) &&
+ !Long.TYPE.isAssignableFrom(type) &&
+ !Short.TYPE.isAssignableFrom(type) &&
+ !Float.TYPE.isAssignableFrom(type) &&
+ !Double.TYPE.isAssignableFrom(type) &&
+ !Boolean.TYPE.isAssignableFrom(type) &&
+ !Character.TYPE.isAssignableFrom(type) &&
+ !Byte.TYPE.isAssignableFrom(type))
+ throw new InvalidSignatureException("Unsupported PathParam Type: " + type);
+ }
}
diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java
index 01170fd2a8b..bc80b1a81ea 100644
--- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java
+++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java
@@ -27,7 +27,6 @@ import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.Handler;
@@ -50,11 +49,6 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSessionListener;
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer;
-import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerFrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
-import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -259,28 +253,6 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi
servletContextHandler.addServlet(holder, urlPattern);
}
- public void registerWebSocket(String urlPattern, WebSocketCreator creator)
- {
- ServletHolder holder = new ServletHolder(new WebSocketServlet()
- {
- JavaxWebSocketServerFrameHandlerFactory factory = new JavaxWebSocketServerFrameHandlerFactory(JavaxWebSocketServerContainer.ensureContainer(getServletContext()));
-
- @Override
- public void configure(WebSocketServletFactory factory)
- {
- PathSpec pathSpec = factory.parsePathSpec("/");
- factory.addMapping(pathSpec, creator);
- }
-
- @Override
- protected FrameHandlerFactory getFactory()
- {
- return factory;
- }
- });
- servletContextHandler.addServlet(holder, urlPattern);
- }
-
public JavaxWebSocketServerContainer getServerContainer()
{
if (!servletContextHandler.isRunning())
diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java
index 92a9fa0913b..a5682b72772 100644
--- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java
+++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java
@@ -50,112 +50,121 @@ import static org.hamcrest.Matchers.notNullValue;
public class WSServer extends LocalServer implements LocalFuzzer.Provider
{
private static final Logger LOG = LoggerFactory.getLogger(WSServer.class);
- private final Path contextDir;
- private final String contextPath;
- private ContextHandlerCollection contexts;
- private Path webinf;
- private Path classesDir;
+ private final Path testDir;
+ private ContextHandlerCollection contexts = new ContextHandlerCollection();
- public WSServer(File testdir, String contextName)
+ public WSServer(Path testDir)
{
- this(testdir.toPath(), contextName);
+ this.testDir = testDir;
}
- public WSServer(Path testdir, String contextName)
+ public WebApp createWebApp(String contextName)
{
- this.contextDir = testdir.resolve(contextName);
- this.contextPath = "/" + contextName;
- FS.ensureEmpty(contextDir);
- }
-
- public void copyClass(Class> clazz) throws Exception
- {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- String endpointPath = TypeUtil.toClassReference(clazz);
- URL classUrl = cl.getResource(endpointPath);
- assertThat("Class URL for: " + clazz, classUrl, notNullValue());
- Path destFile = classesDir.resolve(endpointPath);
- FS.ensureDirExists(destFile.getParent());
- File srcFile = new File(classUrl.toURI());
- IO.copy(srcFile, destFile.toFile());
- }
-
- public void copyEndpoint(Class> endpointClass) throws Exception
- {
- copyClass(endpointClass);
- }
-
- public void copyLib(Class> clazz, String jarFileName) throws URISyntaxException, IOException
- {
- webinf = contextDir.resolve("WEB-INF");
- FS.ensureDirExists(webinf);
- Path libDir = webinf.resolve("lib");
- FS.ensureDirExists(libDir);
- Path jarFile = libDir.resolve(jarFileName);
-
- URL codeSourceURL = clazz.getProtectionDomain().getCodeSource().getLocation();
- assertThat("Class CodeSource URL is file scheme", codeSourceURL.getProtocol(), is("file"));
-
- File sourceCodeSourceFile = new File(codeSourceURL.toURI());
- if (sourceCodeSourceFile.isDirectory())
- {
- LOG.info("Creating " + jarFile + " from " + sourceCodeSourceFile);
- JAR.create(sourceCodeSourceFile, jarFile.toFile());
- }
- else
- {
- LOG.info("Copying " + sourceCodeSourceFile + " to " + jarFile);
- IO.copy(sourceCodeSourceFile, jarFile.toFile());
- }
- }
-
- public void copyWebInf(String testResourceName) throws IOException
- {
- webinf = contextDir.resolve("WEB-INF");
- FS.ensureDirExists(webinf);
- classesDir = webinf.resolve("classes");
- FS.ensureDirExists(classesDir);
- Path webxml = webinf.resolve("web.xml");
- File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
- IO.copy(testWebXml, webxml.toFile());
- }
-
- public WebAppContext createWebAppContext() throws IOException
- {
- WebAppContext context = new WebAppContext();
- context.setContextPath(this.contextPath);
- context.setBaseResource(new PathResource(this.contextDir));
- context.setAttribute("org.eclipse.jetty.websocket.javax", Boolean.TRUE);
- context.addConfiguration(new JavaxWebSocketConfiguration());
- return context;
- }
-
- public void createWebInf() throws IOException
- {
- copyWebInf("empty-web.xml");
- }
-
- public void deployWebapp(WebAppContext webapp) throws Exception
- {
- contexts.addHandler(webapp);
- contexts.manage(webapp);
- webapp.setThrowUnavailableOnStartupException(true);
- webapp.start();
- if (LOG.isDebugEnabled())
- {
- LOG.debug("{}", webapp.dump());
- }
- }
-
- public Path getWebAppDir()
- {
- return this.contextDir;
+ return new WebApp(contextName);
}
@Override
- protected Handler createRootHandler(Server server) throws Exception
+ protected Handler createRootHandler(Server server)
{
- contexts = new ContextHandlerCollection();
return contexts;
}
+
+ public class WebApp
+ {
+ private final WebAppContext context;
+ private final Path contextDir;
+ private final Path webInf;
+ private final Path classesDir;
+ private final Path libDir;
+
+ private WebApp(String contextName)
+ {
+ // Ensure context directory.
+ contextDir = testDir.resolve(contextName);
+ FS.ensureEmpty(contextDir);
+
+ // Ensure WEB-INF directories.
+ webInf = contextDir.resolve("WEB-INF");
+ FS.ensureDirExists(webInf);
+ classesDir = webInf.resolve("classes");
+ FS.ensureDirExists(classesDir);
+ libDir = webInf.resolve("lib");
+ FS.ensureDirExists(libDir);
+
+ // Configure the WebAppContext.
+ context = new WebAppContext();
+ context.setContextPath("/" + contextName);
+ context.setBaseResource(new PathResource(contextDir));
+ context.setAttribute("org.eclipse.jetty.websocket.javax", Boolean.TRUE);
+ context.addConfiguration(new JavaxWebSocketConfiguration());
+ }
+
+ public WebAppContext getWebAppContext()
+ {
+ return context;
+ }
+
+ public String getContextPath()
+ {
+ return context.getContextPath();
+ }
+
+ public Path getContextDir()
+ {
+ return contextDir;
+ }
+
+ public void createWebInf() throws IOException
+ {
+ copyWebInf("empty-web.xml");
+ }
+
+ public void copyWebInf(String testResourceName) throws IOException
+ {
+ File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
+ Path webXml = webInf.resolve("web.xml");
+ IO.copy(testWebXml, webXml.toFile());
+ }
+
+ public void copyClass(Class> clazz) throws Exception
+ {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ String endpointPath = TypeUtil.toClassReference(clazz);
+ URL classUrl = cl.getResource(endpointPath);
+ assertThat("Class URL for: " + clazz, classUrl, notNullValue());
+ Path destFile = classesDir.resolve(endpointPath);
+ FS.ensureDirExists(destFile.getParent());
+ File srcFile = new File(classUrl.toURI());
+ IO.copy(srcFile, destFile.toFile());
+ }
+
+ public void copyLib(Class> clazz, String jarFileName) throws URISyntaxException, IOException
+ {
+ Path jarFile = libDir.resolve(jarFileName);
+
+ URL codeSourceURL = clazz.getProtectionDomain().getCodeSource().getLocation();
+ assertThat("Class CodeSource URL is file scheme", codeSourceURL.getProtocol(), is("file"));
+
+ File sourceCodeSourceFile = new File(codeSourceURL.toURI());
+ if (sourceCodeSourceFile.isDirectory())
+ {
+ LOG.info("Creating " + jarFile + " from " + sourceCodeSourceFile);
+ JAR.create(sourceCodeSourceFile, jarFile.toFile());
+ }
+ else
+ {
+ LOG.info("Copying " + sourceCodeSourceFile + " to " + jarFile);
+ IO.copy(sourceCodeSourceFile, jarFile.toFile());
+ }
+ }
+
+ public void deploy()
+ {
+ contexts.addHandler(context);
+ contexts.manage(context);
+ context.setThrowUnavailableOnStartupException(true);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", context.dump());
+ }
+ }
}
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/MessageReceivingTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/MessageReceivingTest.java
index 5161bf2c05e..9699f2a3bc0 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/MessageReceivingTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/MessageReceivingTest.java
@@ -47,7 +47,7 @@ import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.server.Negotiation;
import org.eclipse.jetty.websocket.javax.tests.CoreServer;
import org.eclipse.jetty.websocket.javax.tests.DataUtils;
-import org.eclipse.jetty.websocket.util.TextUtil;
+import org.eclipse.jetty.websocket.util.TextUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -328,7 +328,7 @@ public class MessageReceivingTest
{
if (LOG.isDebugEnabled())
{
- LOG.debug("{}.onWholeText({})", EchoWholeMessageFrameHandler.class.getSimpleName(), TextUtil.hint(wholeMessage));
+ LOG.debug("{}.onWholeText({})", EchoWholeMessageFrameHandler.class.getSimpleName(), TextUtils.hint(wholeMessage));
}
sendText(wholeMessage, callback, false);
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AltFilterTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AltFilterTest.java
index e6c93a0ce17..31df6bdda45 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AltFilterTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AltFilterTest.java
@@ -24,13 +24,13 @@ import java.util.List;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CloseStatus;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.javax.tests.Fuzzer;
import org.eclipse.jetty.websocket.javax.tests.WSServer;
import org.eclipse.jetty.websocket.javax.tests.server.sockets.echo.BasicEchoSocket;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -39,7 +39,7 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
/**
- * Testing the use of an alternate {@link org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter}
+ * Testing the use of an alternate {@link WebSocketUpgradeFilter}
* defined in the WEB-INF/web.xml
*/
@ExtendWith(WorkDirExtension.class)
@@ -50,22 +50,21 @@ public class AltFilterTest
@Test
public void testEcho() throws Exception
{
- WSServer wsb = new WSServer(testdir.getPath(), "app");
- wsb.copyWebInf("alt-filter-web.xml");
+ WSServer wsb = new WSServer(testdir.getPath());
+ WSServer.WebApp app = wsb.createWebApp("app");
+ app.copyWebInf("alt-filter-web.xml");
// the endpoint (extends javax.websocket.Endpoint)
- wsb.copyClass(BasicEchoSocket.class);
+ app.copyClass(BasicEchoSocket.class);
+ app.deploy();
try
{
wsb.start();
- WebAppContext webapp = wsb.createWebAppContext();
- wsb.deployWebapp(webapp);
-
- FilterHolder filterWebXml = webapp.getServletHandler().getFilter("wsuf-test");
+ FilterHolder filterWebXml = app.getWebAppContext().getServletHandler().getFilter("wsuf-test");
assertThat("Filter[wsuf-test]", filterWebXml, notNullValue());
- FilterHolder filterSCI = webapp.getServletHandler().getFilter("Jetty_WebSocketUpgradeFilter");
+ FilterHolder filterSCI = app.getWebAppContext().getServletHandler().getFilter("Jetty_WebSocketUpgradeFilter");
assertThat("Filter[Jetty_WebSocketUpgradeFilter]", filterSCI, nullValue());
List send = new ArrayList<>();
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
index 573ccd47b3c..82f3fd3f76c 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
@@ -115,7 +115,7 @@ public class ConfiguratorTest
else
{
return "negotiatedExtensions=" + negotiatedExtensions.stream()
- .map((ext) -> ext.getName())
+ .map(Extension::getName)
.collect(Collectors.joining(",", "[", "]"));
}
}
@@ -198,7 +198,7 @@ public class ConfiguratorTest
public static class UniqueUserPropsConfigurator extends ServerEndpointConfig.Configurator
{
- private AtomicInteger upgradeCount = new AtomicInteger(0);
+ private final AtomicInteger upgradeCount = new AtomicInteger(0);
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
@@ -479,7 +479,7 @@ public class ConfiguratorTest
FrameHandlerTracker clientSocket = new FrameHandlerTracker();
ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
- upgradeRequest.header("X-Dummy", "Bogus");
+ upgradeRequest.headers(headers -> headers.put("X-Dummy", "Bogus"));
Future clientConnectFuture = client.connect(upgradeRequest);
CoreSession coreSession = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS);
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ContainerProviderServerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ContainerProviderServerTest.java
index 849d329c8d7..b11386ef4d5 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ContainerProviderServerTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ContainerProviderServerTest.java
@@ -28,7 +28,6 @@ import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.javax.tests.EventSocket;
import org.eclipse.jetty.websocket.javax.tests.WSServer;
import org.junit.jupiter.api.AfterEach;
@@ -61,12 +60,13 @@ public class ContainerProviderServerTest
public void startServer() throws Exception
{
Path testdir = MavenTestingUtils.getTargetTestingPath(ContainerProviderServerTest.class.getName());
- server = new WSServer(testdir, "app");
- server.createWebInf();
- server.copyEndpoint(MySocket.class);
+ server = new WSServer(testdir);
+ WSServer.WebApp app = server.createWebApp("app");
+ app.createWebInf();
+ app.copyClass(MySocket.class);
+ app.deploy();
+
server.start();
- WebAppContext webapp = server.createWebAppContext();
- server.deployWebapp(webapp);
}
@AfterEach
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentTest.java
new file mode 100644
index 00000000000..2a30ea45c1e
--- /dev/null
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentTest.java
@@ -0,0 +1,195 @@
+//
+// ========================================================================
+// 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.websocket.javax.tests.server;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.CloseReason;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.logging.StacklessLogging;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.javax.tests.EventSocket;
+import org.eclipse.jetty.websocket.javax.tests.WSServer;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnJre;
+import org.junit.jupiter.api.condition.JRE;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DeploymentTest
+{
+ private WSServer server;
+
+ @BeforeEach
+ public void startServer() throws Exception
+ {
+ Path testdir = MavenTestingUtils.getTargetTestingPath(DeploymentTest.class.getName());
+ server = new WSServer(testdir);
+ }
+
+ @AfterEach
+ public void stopServer() throws Exception
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testBadPathParamSignature() throws Exception
+ {
+ WSServer.WebApp app1 = server.createWebApp("test1");
+ app1.createWebInf();
+ app1.copyClass(BadPathParamEndpoint.class);
+ app1.copyClass(DecodedString.class);
+ app1.copyClass(DeploymentTest.class);
+ app1.deploy();
+ app1.getWebAppContext().setThrowUnavailableOnStartupException(false);
+
+ try (StacklessLogging ignore = new StacklessLogging(ServletContainerInitializersStarter.class, WebAppContext.class))
+ {
+ server.start();
+ }
+
+ WebSocketContainer client = ContainerProvider.getWebSocketContainer();
+ EventSocket clientSocket = new EventSocket();
+
+ Throwable error = assertThrows(Throwable.class, () ->
+ client.connectToServer(clientSocket, server.getWsUri().resolve(app1.getContextPath() + "/badonclose/a")));
+ assertThat(error, Matchers.instanceOf(IOException.class));
+ assertThat(error.getMessage(), Matchers.containsString("503 Service Unavailable"));
+ }
+
+ @Test
+ @DisabledOnJre(JRE.JAVA_14) // TODO: Waiting on JDK14 bug at https://bugs.openjdk.java.net/browse/JDK-8244090.
+ public void testDifferentWebAppsWithSameClassInSignature() throws Exception
+ {
+ WSServer.WebApp app1 = server.createWebApp("test1");
+ app1.createWebInf();
+ app1.copyClass(DecodedEndpoint.class);
+ app1.copyClass(StringDecoder.class);
+ app1.copyClass(DecodedString.class);
+ app1.copyClass(DeploymentTest.class);
+ app1.deploy();
+
+ WSServer.WebApp app2 = server.createWebApp("test2");
+ app2.createWebInf();
+ app2.copyClass(DecodedEndpoint.class);
+ app2.copyClass(StringDecoder.class);
+ app2.copyClass(DecodedString.class);
+ app2.copyClass(DeploymentTest.class);
+ app2.deploy();
+
+ server.start();
+ WebSocketContainer client = ContainerProvider.getWebSocketContainer();
+ EventSocket clientSocket = new EventSocket();
+
+ // Test echo and close to endpoint at /test1.
+ Session session = client.connectToServer(clientSocket, server.getWsUri().resolve("/test1"));
+ session.getAsyncRemote().sendText("hello world");
+ assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("hello world"));
+ session.close();
+ assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientSocket.closeReason.getCloseCode(), is(CloseReason.CloseCodes.NORMAL_CLOSURE));
+
+ // Test echo and close to endpoint at /test2.
+ session = client.connectToServer(clientSocket, server.getWsUri().resolve("/test2"));
+ session.getAsyncRemote().sendText("hello world");
+ assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("hello world"));
+ session.close();
+ assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientSocket.closeReason.getCloseCode(), is(CloseReason.CloseCodes.NORMAL_CLOSURE));
+ }
+
+ @ServerEndpoint("/badonopen/{arg}")
+ public static class BadPathParamEndpoint
+ {
+ @OnOpen
+ public void onOpen(Session session, @PathParam("arg") DecodedString arg)
+ {
+ }
+ }
+
+ @ServerEndpoint(value = "/", decoders = {StringDecoder.class})
+ public static class DecodedEndpoint
+ {
+ @OnMessage
+ public void onMessage(Session session, DecodedString message)
+ {
+ session.getAsyncRemote().sendText(message.getString());
+ }
+ }
+
+ public static class DecodedString
+ {
+ public String string = "";
+
+ public DecodedString(String hold)
+ {
+ string = hold;
+ }
+
+ public String getString()
+ {
+ return string;
+ }
+ }
+
+ public static class StringDecoder implements Decoder.Text
+ {
+ @Override
+ public DecodedString decode(String s) throws DecodeException
+ {
+ return new DecodedString(s);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/EndpointViaConfigTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/EndpointViaConfigTest.java
index 57d9f839f37..b7e76497d18 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/EndpointViaConfigTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/EndpointViaConfigTest.java
@@ -27,7 +27,6 @@ import com.acme.websocket.BasicEchoEndpointConfigContextListener;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -56,21 +55,20 @@ public class EndpointViaConfigTest
@Test
public void testEcho() throws Exception
{
- WSServer wsb = new WSServer(testdir.getPath(), "app");
- wsb.copyWebInf("basic-echo-endpoint-config-web.xml");
+ WSServer wsb = new WSServer(testdir.getPath());
+ WSServer.WebApp app = wsb.createWebApp("app");
+ app.copyWebInf("basic-echo-endpoint-config-web.xml");
// the endpoint (extends javax.websocket.Endpoint)
- wsb.copyClass(BasicEchoEndpoint.class);
+ app.copyClass(BasicEchoEndpoint.class);
// the configuration (adds the endpoint)
- wsb.copyClass(BasicEchoEndpointConfigContextListener.class);
+ app.copyClass(BasicEchoEndpointConfigContextListener.class);
+ app.deploy();
try
{
wsb.start();
URI uri = wsb.getWsUri();
- WebAppContext webapp = wsb.createWebAppContext();
- wsb.deployWebapp(webapp);
-
WebSocketCoreClient client = new WebSocketCoreClient();
try
{
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/IdleTimeoutTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/IdleTimeoutTest.java
index 4948dc66515..b4858f87b73 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/IdleTimeoutTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/IdleTimeoutTest.java
@@ -28,7 +28,6 @@ import com.acme.websocket.IdleTimeoutOnOpenEndpoint;
import com.acme.websocket.IdleTimeoutOnOpenSocket;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CloseStatus;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -51,19 +50,18 @@ public class IdleTimeoutTest
@BeforeAll
public static void setupServer() throws Exception
{
- server = new WSServer(MavenTestingUtils.getTargetTestingPath(IdleTimeoutTest.class.getName()), "app");
- server.copyWebInf("idle-timeout-config-web.xml");
+ server = new WSServer(MavenTestingUtils.getTargetTestingPath(IdleTimeoutTest.class.getName()));
+ WSServer.WebApp app = server.createWebApp("app");
+ app.copyWebInf("idle-timeout-config-web.xml");
// the endpoint (extends javax.websocket.Endpoint)
- server.copyClass(IdleTimeoutOnOpenEndpoint.class);
+ app.copyClass(IdleTimeoutOnOpenEndpoint.class);
// the configuration that adds the endpoint
- server.copyClass(IdleTimeoutContextListener.class);
+ app.copyClass(IdleTimeoutContextListener.class);
// the annotated socket
- server.copyClass(IdleTimeoutOnOpenSocket.class);
+ app.copyClass(IdleTimeoutOnOpenSocket.class);
+ app.deploy();
server.start();
-
- WebAppContext webapp = server.createWebAppContext();
- server.deployWebapp(webapp);
}
@AfterAll
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeAnnotatedTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeAnnotatedTest.java
index 66d21d6a25d..f76b4683d1e 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeAnnotatedTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeAnnotatedTest.java
@@ -29,7 +29,6 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -63,18 +62,17 @@ public class LargeAnnotatedTest
@Test
public void testEcho() throws Exception
{
- WSServer wsb = new WSServer(testdir.getPath(), "app");
- wsb.createWebInf();
- wsb.copyEndpoint(LargeEchoConfiguredSocket.class);
+ WSServer wsb = new WSServer(testdir.getPath());
+ WSServer.WebApp app = wsb.createWebApp("app");
+ app.createWebInf();
+ app.copyClass(LargeEchoConfiguredSocket.class);
+ app.deploy();
try
{
wsb.start();
URI uri = wsb.getWsUri();
- WebAppContext webapp = wsb.createWebAppContext();
- wsb.deployWebapp(webapp);
-
WebSocketCoreClient client = new WebSocketCoreClient();
try
{
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeContainerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeContainerTest.java
index 541cc93b648..5025742f79c 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeContainerTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/LargeContainerTest.java
@@ -28,7 +28,6 @@ import com.acme.websocket.LargeEchoDefaultSocket;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -53,18 +52,17 @@ public class LargeContainerTest
@Test
public void testEcho() throws Exception
{
- WSServer wsb = new WSServer(testdir.getPath(), "app");
- wsb.copyWebInf("large-echo-config-web.xml");
- wsb.copyEndpoint(LargeEchoDefaultSocket.class);
+ WSServer wsb = new WSServer(testdir.getPath());
+ WSServer.WebApp app = wsb.createWebApp("app");
+ app.copyWebInf("large-echo-config-web.xml");
+ app.copyClass(LargeEchoDefaultSocket.class);
+ app.deploy();
try
{
wsb.start();
URI uri = wsb.getWsUri();
- WebAppContext webapp = wsb.createWebAppContext();
- wsb.deployWebapp(webapp);
-
WebSocketCoreClient client = new WebSocketCoreClient();
try
{
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/OnMessageReturnTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/OnMessageReturnTest.java
index 074205796b0..8da784ac364 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/OnMessageReturnTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/OnMessageReturnTest.java
@@ -31,7 +31,6 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -87,18 +86,17 @@ public class OnMessageReturnTest
@Test
public void testEchoReturn() throws Exception
{
- WSServer wsb = new WSServer(testdir.getPath(), "app");
- wsb.copyWebInf("empty-web.xml");
- wsb.copyClass(EchoReturnEndpoint.class);
+ WSServer wsb = new WSServer(testdir.getPath());
+ WSServer.WebApp app = wsb.createWebApp("app");
+ app.copyWebInf("empty-web.xml");
+ app.copyClass(EchoReturnEndpoint.class);
+ app.deploy();
try
{
wsb.start();
URI uri = wsb.getWsUri();
- WebAppContext webapp = wsb.createWebAppContext();
- wsb.deployWebapp(webapp);
-
WebSocketCoreClient client = new WebSocketCoreClient();
try
{
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PingPongTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PingPongTest.java
index 4d548a2f05b..0e63b4a9314 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PingPongTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PingPongTest.java
@@ -30,7 +30,6 @@ import com.acme.websocket.PongMessageEndpoint;
import com.acme.websocket.PongSocket;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
@@ -55,17 +54,16 @@ public class PingPongTest
public static void startServer() throws Exception
{
Path testdir = MavenTestingUtils.getTargetTestingPath(PingPongTest.class.getName());
- server = new WSServer(testdir, "app");
- server.copyWebInf("pong-config-web.xml");
+ server = new WSServer(testdir);
- server.copyClass(PongContextListener.class);
- server.copyClass(PongMessageEndpoint.class);
- server.copyClass(PongSocket.class);
+ WSServer.WebApp app = server.createWebApp("app");
+ app.copyWebInf("pong-config-web.xml");
+ app.copyClass(PongContextListener.class);
+ app.copyClass(PongMessageEndpoint.class);
+ app.copyClass(PongSocket.class);
+ app.deploy();
server.start();
-
- WebAppContext webapp = server.createWebAppContext();
- server.deployWebapp(webapp);
}
@BeforeAll
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebAppClassLoaderTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebAppClassLoaderTest.java
index 9e40e26ab78..fc37d260962 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebAppClassLoaderTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebAppClassLoaderTest.java
@@ -93,12 +93,13 @@ public class WebAppClassLoaderTest
public void startServer() throws Exception
{
Path testdir = MavenTestingUtils.getTargetTestingPath(WebAppClassLoaderTest.class.getName());
- server = new WSServer(testdir, "app");
- server.createWebInf();
- server.copyEndpoint(MySocket.class);
+ server = new WSServer(testdir);
+ WSServer.WebApp app = server.createWebApp("app");
+ app.createWebInf();
+ app.copyClass(MySocket.class);
+ app.deploy();
+ webapp = app.getWebAppContext();
server.start();
- webapp = server.createWebAppContext();
- server.deployWebapp(webapp);
}
@AfterEach
diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml
index f1723581a3a..986106295e3 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml
+++ b/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml
@@ -13,7 +13,7 @@
wsuf-test
- org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter
+ org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter
diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-alt-config-via-listener.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-alt-config-via-listener.xml
index 1479493aff3..647d0947663 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-alt-config-via-listener.xml
+++ b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-alt-config-via-listener.xml
@@ -12,7 +12,7 @@
wsuf-alt
- org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter
+ org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter
configAttributeKey
alt.config
diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-listener.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-listener.xml
index d9811f6e0e5..595de89a035 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-listener.xml
+++ b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-listener.xml
@@ -16,7 +16,7 @@
wsuf
- org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter
+ org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter
diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-servlet-init.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-servlet-init.xml
index f8c8fbf61e9..56a333bfee1 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-servlet-init.xml
+++ b/jetty-websocket/websocket-javax-tests/src/test/resources/wsuf-config-via-servlet-init.xml
@@ -18,7 +18,7 @@
wsuf
- org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter
+ org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter
diff --git a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java
index 373e0dd7ccc..4ec9847e0af 100644
--- a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java
+++ b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java
@@ -38,7 +38,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.CloseStatus;
-import org.eclipse.jetty.websocket.util.TextUtil;
+import org.eclipse.jetty.websocket.util.TextUtils;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
@@ -62,7 +62,7 @@ public class EndPoints
@Override
public void onWebSocketClose(int statusCode, String reason)
{
- events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtil.quote(reason));
+ events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtils.quote(reason));
}
@Override
@@ -74,13 +74,13 @@ public class EndPoints
@Override
public void onWebSocketError(Throwable cause)
{
- events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtil.quote(cause.getMessage()));
+ events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtils.quote(cause.getMessage()));
}
@Override
public void onWebSocketText(String message)
{
- events.add("onWebSocketText(%s)", TextUtil.quote(message));
+ events.add("onWebSocketText(%s)", TextUtils.quote(message));
}
}
@@ -91,7 +91,7 @@ public class EndPoints
@Override
public void onWebSocketClose(int statusCode, String reason)
{
- events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtil.quote(reason));
+ events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtils.quote(reason));
}
@Override
@@ -103,7 +103,7 @@ public class EndPoints
@Override
public void onWebSocketError(Throwable cause)
{
- events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtil.quote(cause.getMessage()));
+ events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtils.quote(cause.getMessage()));
}
@Override
@@ -120,7 +120,7 @@ public class EndPoints
@Override
public void onWebSocketClose(int statusCode, String reason)
{
- events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtil.quote(reason));
+ events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtils.quote(reason));
}
@Override
@@ -132,13 +132,13 @@ public class EndPoints
@Override
public void onWebSocketError(Throwable cause)
{
- events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtil.quote(cause.getMessage()));
+ events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtils.quote(cause.getMessage()));
}
@Override
public void onWebSocketPartialText(String payload, boolean fin)
{
- events.add("onWebSocketPartialText(%s, %b)", TextUtil.quote(payload), fin);
+ events.add("onWebSocketPartialText(%s, %b)", TextUtils.quote(payload), fin);
}
@Override
@@ -155,7 +155,7 @@ public class EndPoints
@Override
public void onWebSocketClose(int statusCode, String reason)
{
- events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtil.quote(reason));
+ events.add("onWebSocketClose(%s, %s)", CloseStatus.codeString(statusCode), TextUtils.quote(reason));
}
@Override
@@ -167,7 +167,7 @@ public class EndPoints
@Override
public void onWebSocketError(Throwable cause)
{
- events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtil.quote(cause.getMessage()));
+ events.add("onWebSocketError((%s) %s)", cause.getClass().getSimpleName(), TextUtils.quote(cause.getMessage()));
}
@Override
@@ -228,7 +228,7 @@ public class EndPoints
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
- events.add("onClose(%d, %s)", statusCode, TextUtil.quote(reason));
+ events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason));
}
@OnWebSocketConnect
@@ -253,7 +253,7 @@ public class EndPoints
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
- events.add("onClose(%d, %s)", statusCode, TextUtil.quote(reason));
+ events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason));
}
@OnWebSocketConnect
@@ -271,7 +271,7 @@ public class EndPoints
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
- events.add("onClose(%d, %s)", statusCode, TextUtil.quote(reason));
+ events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason));
}
@OnWebSocketConnect
@@ -289,7 +289,7 @@ public class EndPoints
@OnWebSocketMessage
public void onText(String message)
{
- events.add("onText(%s)", TextUtil.quote(message));
+ events.add("onText(%s)", TextUtils.quote(message));
}
}
@@ -301,7 +301,7 @@ public class EndPoints
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
- events.add("onClose(%d, %s)", statusCode, TextUtil.quote(reason));
+ events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason));
}
@OnWebSocketConnect
diff --git a/jetty-websocket/websocket-jetty-server/pom.xml b/jetty-websocket/websocket-jetty-server/pom.xml
index 811fa6c6f89..ade9576c6e7 100644
--- a/jetty-websocket/websocket-jetty-server/pom.xml
+++ b/jetty-websocket/websocket-jetty-server/pom.xml
@@ -48,7 +48,7 @@
org.eclipse.jetty.websocket
- websocket-servlet
+ websocket-util-server
${project.version}
diff --git a/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod b/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod
index 1f3f6189a64..9a93574f2bf 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod
+++ b/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod
@@ -12,8 +12,8 @@ annotations
[lib]
lib/websocket/websocket-core-${jetty.version}.jar
-lib/websocket/websocket-servlet-${jetty.version}.jar
lib/websocket/websocket-util-${jetty.version}.jar
+lib/websocket/websocket-util-server-${jetty.version}.jar
lib/websocket/websocket-jetty-api-${jetty.version}.jar
lib/websocket/websocket-jetty-common-${jetty.version}.jar
lib/websocket/websocket-jetty-server-${jetty.version}.jar
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/module-info.java b/jetty-websocket/websocket-jetty-server/src/main/java/module-info.java
index 9686cfd3f61..6156e8d5a30 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/module-info.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/module-info.java
@@ -29,10 +29,10 @@ module org.eclipse.jetty.websocket.jetty.server
requires jetty.servlet.api;
requires org.eclipse.jetty.websocket.jetty.common;
+ requires org.eclipse.jetty.websocket.util.server;
+ requires org.slf4j;
requires transitive org.eclipse.jetty.webapp;
requires transitive org.eclipse.jetty.websocket.jetty.api;
- requires transitive org.eclipse.jetty.websocket.servlet;
- requires org.slf4j;
// Only required if using JMX.
requires static org.eclipse.jetty.jmx;
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java
index 052b458c95f..13e0b330d61 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java
@@ -36,7 +36,7 @@ import javax.servlet.http.HttpSession;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.websocket.core.server.Negotiation;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
public class JettyServerUpgradeRequest
{
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java
index dcd144613a0..01e015302ed 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
public class JettyServerUpgradeResponse
{
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java
index faf36ac1a02..06a614efec2 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java
@@ -41,8 +41,8 @@ import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
+import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java
index b2880acbb31..448694577c5 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java
@@ -18,23 +18,101 @@
package org.eclipse.jetty.websocket.server;
-import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.time.Duration;
+import java.util.Set;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
-public abstract class JettyWebSocketServlet extends WebSocketServlet
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.websocket.core.Configuration;
+import org.eclipse.jetty.websocket.core.WebSocketComponents;
+import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketCreator;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract Servlet used to bridge the Servlet API to the WebSocket API.
+ *
+ * To use this servlet, you will be required to register your websockets with the {@link WebSocketMapping} so that it can create your websockets under the
+ * appropriate conditions.
+ *
+ * The most basic implementation would be as follows:
+ *
+ * package my.example;
+ *
+ * import JettyWebSocketServlet;
+ * import JettyWebSocketServletFactory;
+ *
+ * public class MyEchoServlet extends JettyWebSocketServlet
+ * {
+ * @Override
+ * public void configure(JettyWebSocketServletFactory factory)
+ * {
+ * factory.setDefaultMaxFrameSize(4096);
+ * factory.addMapping(factory.parsePathSpec("/"), (req,res)->new EchoSocket());
+ * }
+ * }
+ *
+ *
+ * Only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketMapping} handling of creating
+ * WebSockets. All other requests are treated as normal servlet requests. The configuration defined by this servlet init parameters will
+ * be used as the customizer for any mappings created by {@link JettyWebSocketServletFactory#addMapping(String, JettyWebSocketCreator)} during
+ * {@link #configure(JettyWebSocketServletFactory)} calls. The request upgrade may be peformed by this servlet, or is may be performed by a
+ * {@link WebSocketUpgradeFilter} instance that will share the same {@link WebSocketMapping} instance. If the filter is used, then the
+ * filter configuraton is used as the default configuration prior to this servlets configuration being applied.
+ *
+ *
+ * Configuration / Init-Parameters:
+ *
+ *
+ * - idleTimeout
+ * - set the time in ms that a websocket may be idle before closing
+ * - maxTextMessageSize
+ * - set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing
+ * - maxBinaryMessageSize
+ * - set the size in bytes that a websocket may be accept as a Binary Message before closing
+ * - inputBufferSize
+ * - set the size in bytes of the buffer used to read raw bytes from the network layer
* - outputBufferSize
+ * - set the size in bytes of the buffer used to write bytes to the network layer
+ * - maxFrameSize
+ * - The maximum frame size sent or received.
+ * - autoFragment
+ * - If true, frames are automatically fragmented to respect the maximum frame size.
+ *
+ */
+public abstract class JettyWebSocketServlet extends HttpServlet
{
+ private static final Logger LOG = LoggerFactory.getLogger(JettyWebSocketServlet.class);
+ private final CustomizedWebSocketServletFactory customizer = new CustomizedWebSocketServletFactory();
+
+ private WebSocketMapping mapping;
+ private WebSocketComponents components;
+
+ /**
+ * Configure the JettyWebSocketServletFactory for this servlet instance by setting default
+ * configuration (which may be overriden by annotations) and mapping {@link JettyWebSocketCreator}s.
+ * This method assumes a single {@link FrameHandlerFactory} will be available as a bean on the
+ * {@link ContextHandler}, which in practise will mostly the the Jetty WebSocket API factory.
+ *
+ * @param factory the JettyWebSocketServletFactory
+ */
protected abstract void configure(JettyWebSocketServletFactory factory);
- @Override
- protected final void configure(WebSocketServletFactory factory)
- {
- configure(new JettyWebSocketServletFactory(factory));
- }
-
- @Override
- protected FrameHandlerFactory getFactory()
+ /**
+ * @return the instance of {@link FrameHandlerFactory} to be used to create the FrameHandler
+ */
+ private FrameHandlerFactory getFactory()
{
JettyServerFrameHandlerFactory frameHandlerFactory = JettyServerFrameHandlerFactory.getFactory(getServletContext());
@@ -43,4 +121,163 @@ public abstract class JettyWebSocketServlet extends WebSocketServlet
return frameHandlerFactory;
}
+
+ @Override
+ public void init() throws ServletException
+ {
+ try
+ {
+ ServletContext servletContext = getServletContext();
+
+ components = WebSocketComponents.ensureWebSocketComponents(servletContext);
+ mapping = new WebSocketMapping(components);
+
+ String max = getInitParameter("idleTimeout");
+ if (max == null)
+ {
+ max = getInitParameter("maxIdleTime");
+ if (max != null)
+ LOG.warn("'maxIdleTime' init param is deprecated, use 'idleTimeout' instead");
+ }
+ if (max != null)
+ customizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
+
+ max = getInitParameter("maxTextMessageSize");
+ if (max != null)
+ customizer.setMaxTextMessageSize(Long.parseLong(max));
+
+ max = getInitParameter("maxBinaryMessageSize");
+ if (max != null)
+ customizer.setMaxBinaryMessageSize(Long.parseLong(max));
+
+ max = getInitParameter("inputBufferSize");
+ if (max != null)
+ customizer.setInputBufferSize(Integer.parseInt(max));
+
+ max = getInitParameter("outputBufferSize");
+ if (max != null)
+ customizer.setOutputBufferSize(Integer.parseInt(max));
+
+ max = getInitParameter("maxFrameSize");
+ if (max == null)
+ max = getInitParameter("maxAllowedFrameSize");
+ if (max != null)
+ customizer.setMaxFrameSize(Long.parseLong(max));
+
+ String autoFragment = getInitParameter("autoFragment");
+ if (autoFragment != null)
+ customizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
+
+ configure(customizer); // Let user modify customizer prior after init params
+ }
+ catch (Throwable x)
+ {
+ throw new ServletException(x);
+ }
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ // provide a null default customizer the customizer will be on the negotiator in the mapping
+ if (mapping.upgrade(req, resp, null))
+ return;
+
+ // If we reach this point, it means we had an incoming request to upgrade
+ // but it was either not a proper websocket upgrade, or it was possibly rejected
+ // due to incoming request constraints (controlled by WebSocketCreator)
+ if (resp.isCommitted())
+ return;
+
+ // Handle normally
+ super.service(req, resp);
+ }
+
+ private class CustomizedWebSocketServletFactory extends Configuration.ConfigurationCustomizer implements JettyWebSocketServletFactory
+ {
+ @Override
+ public Set getAvailableExtensionNames()
+ {
+ return components.getExtensionRegistry().getAvailableExtensionNames();
+ }
+
+ @Override
+ public void addMapping(String pathSpec, JettyWebSocketCreator creator)
+ {
+ mapping.addMapping(WebSocketMapping.parsePathSpec(pathSpec), new WrappedJettyCreator(creator), getFactory(), this);
+ }
+
+ @Override
+ public void register(Class> endpointClass)
+ {
+ Constructor> constructor;
+ try
+ {
+ constructor = endpointClass.getDeclaredConstructor();
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ JettyWebSocketCreator creator = (req, resp) ->
+ {
+ try
+ {
+ return constructor.newInstance();
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ return null;
+ }
+ };
+
+ addMapping("/", creator);
+ }
+
+ @Override
+ public void setCreator(JettyWebSocketCreator creator)
+ {
+ addMapping("/", creator);
+ }
+
+ @Override
+ public JettyWebSocketCreator getMapping(String pathSpec)
+ {
+ WebSocketCreator creator = mapping.getMapping(WebSocketMapping.parsePathSpec(pathSpec));
+ if (creator instanceof WrappedJettyCreator)
+ return ((WrappedJettyCreator)creator).getJettyWebSocketCreator();
+
+ return null;
+ }
+
+ @Override
+ public boolean removeMapping(String pathSpec)
+ {
+ return mapping.removeMapping(WebSocketMapping.parsePathSpec(pathSpec));
+ }
+ }
+
+ private static class WrappedJettyCreator implements WebSocketCreator
+ {
+ private JettyWebSocketCreator creator;
+
+ private WrappedJettyCreator(JettyWebSocketCreator creator)
+ {
+ this.creator = creator;
+ }
+
+ private JettyWebSocketCreator getJettyWebSocketCreator()
+ {
+ return creator;
+ }
+
+ @Override
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+ {
+ return creator.createWebSocket(new JettyServerUpgradeRequest(req), new JettyServerUpgradeResponse(resp));
+ }
+ }
}
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java
index c27d5245bca..bad07937441 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java
@@ -18,123 +18,15 @@
package org.eclipse.jetty.websocket.server;
-import java.time.Duration;
import java.util.Set;
-import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
-public class JettyWebSocketServletFactory implements WebSocketPolicy
+public interface JettyWebSocketServletFactory extends WebSocketPolicy
{
- private WebSocketServletFactory factory;
-
- JettyWebSocketServletFactory(WebSocketServletFactory factory)
- {
- this.factory = factory;
- }
-
- public Set getAvailableExtensionNames()
- {
- return factory.getExtensionRegistry().getAvailableExtensionNames();
- }
-
- @Override
- public WebSocketBehavior getBehavior()
- {
- return WebSocketBehavior.SERVER;
- }
-
- @Override
- public boolean isAutoFragment()
- {
- return factory.isAutoFragment();
- }
-
- @Override
- public void setAutoFragment(boolean autoFragment)
- {
- factory.setAutoFragment(autoFragment);
- }
-
- @Override
- public long getMaxFrameSize()
- {
- return factory.getMaxFrameSize();
- }
-
- @Override
- public void setMaxFrameSize(long maxFrameSize)
- {
- factory.setMaxFrameSize(maxFrameSize);
- }
-
- @Override
- public Duration getIdleTimeout()
- {
- return factory.getIdleTimeout();
- }
-
- @Override
- public void setIdleTimeout(Duration duration)
- {
- factory.setIdleTimeout(duration);
- }
-
- @Override
- public int getInputBufferSize()
- {
- return factory.getInputBufferSize();
- }
-
- @Override
- public void setInputBufferSize(int bufferSize)
- {
- factory.setInputBufferSize(bufferSize);
- }
-
- @Override
- public long getMaxBinaryMessageSize()
- {
- return factory.getMaxBinaryMessageSize();
- }
-
- @Override
- public void setMaxBinaryMessageSize(long bufferSize)
- {
- factory.setMaxBinaryMessageSize(bufferSize);
- }
-
- @Override
- public long getMaxTextMessageSize()
- {
- return factory.getMaxTextMessageSize();
- }
-
- @Override
- public void setMaxTextMessageSize(long bufferSize)
- {
- factory.setMaxTextMessageSize(bufferSize);
- }
-
- @Override
- public int getOutputBufferSize()
- {
- return factory.getOutputBufferSize();
- }
-
- @Override
- public void setOutputBufferSize(int bufferSize)
- {
- factory.setOutputBufferSize(bufferSize);
- }
-
/**
- * add a WebSocket mapping to a provided {@link JettyWebSocketCreator}.
+ * Add a WebSocket mapping to a provided {@link JettyWebSocketCreator}.
*
* If mapping is added before this configuration is started, then it is persisted through
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
@@ -145,30 +37,21 @@ public class JettyWebSocketServletFactory implements WebSocketPolicy
* @param creator the WebSocketCreator to use
* @since 10.0
*/
- public void addMapping(String pathSpec, JettyWebSocketCreator creator)
- {
- factory.addMapping(pathSpec, new WrappedCreator(creator));
- }
+ void addMapping(String pathSpec, JettyWebSocketCreator creator);
/**
* Add a WebSocket mapping at PathSpec "/" for a creator which creates the endpointClass
*
* @param endpointClass the WebSocket class to use
*/
- public void register(Class> endpointClass)
- {
- factory.register(endpointClass);
- }
+ void register(Class> endpointClass);
/**
* Add a WebSocket mapping at PathSpec "/" for a creator
*
* @param creator the WebSocketCreator to use
*/
- public void setCreator(JettyWebSocketCreator creator)
- {
- factory.setCreator(new WrappedCreator(creator));
- }
+ void setCreator(JettyWebSocketCreator creator);
/**
* Returns the creator for the given path spec.
@@ -176,14 +59,7 @@ public class JettyWebSocketServletFactory implements WebSocketPolicy
* @param pathSpec the pathspec to respond on
* @return the websocket creator if path spec exists, or null
*/
- public JettyWebSocketCreator getMapping(String pathSpec)
- {
- WebSocketCreator creator = factory.getMapping(parsePathSpec(pathSpec));
- if (creator instanceof WrappedCreator)
- return ((WrappedCreator)creator).getCreator();
-
- return null;
- }
+ JettyWebSocketCreator getMapping(String pathSpec);
/**
* Removes the mapping based on the given path spec.
@@ -191,40 +67,17 @@ public class JettyWebSocketServletFactory implements WebSocketPolicy
* @param pathSpec the pathspec to respond on
* @return true if underlying mapping were altered, false otherwise
*/
- public boolean removeMapping(String pathSpec)
- {
- return factory.removeMapping(parsePathSpec(pathSpec));
- }
+ boolean removeMapping(String pathSpec);
/**
- * Parse a PathSpec string into a PathSpec instance.
- *
- * @param rawSpec the raw path spec as String to parse.
- * @return the {@link PathSpec} implementation for the rawSpec
+ * Get the names of all available WebSocket Extensions.
+ * @return a set the available extension names.
*/
- private PathSpec parsePathSpec(String rawSpec)
+ Set getAvailableExtensionNames();
+
+ @Override
+ default WebSocketBehavior getBehavior()
{
- return factory.parsePathSpec(rawSpec);
- }
-
- private static class WrappedCreator implements WebSocketCreator
- {
- private JettyWebSocketCreator creator;
-
- private WrappedCreator(JettyWebSocketCreator creator)
- {
- this.creator = creator;
- }
-
- public JettyWebSocketCreator getCreator()
- {
- return creator;
- }
-
- @Override
- public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
- {
- return creator.createWebSocket(new JettyServerUpgradeRequest(req), new JettyServerUpgradeResponse(resp));
- }
+ return WebSocketBehavior.SERVER;
}
}
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketConfiguration.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketConfiguration.java
index c0a54dddef8..a2caa2480b2 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketConfiguration.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketConfiguration.java
@@ -58,7 +58,8 @@ public class JettyWebSocketConfiguration extends AbstractConfiguration
protectAndExpose(
"org.eclipse.jetty.websocket.api.",
- "org.eclipse.jetty.websocket.server.");
+ "org.eclipse.jetty.websocket.server.",
+ "org.eclipse.jetty.websocket.util.server."); // For WebSocketUpgradeFilter
hide("org.eclipse.jetty.server.internal.",
"org.eclipse.jetty.server.config.");
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java
index 82c3f79383a..43b65eafd16 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java
@@ -27,8 +27,8 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.listener.ContainerInitializer;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
-import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
-import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java
index 6c3acd2ea91..ce4d9989c64 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java
@@ -27,9 +27,9 @@ import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
public class JettyServerFrameHandlerFactory
extends JettyWebSocketFrameHandlerFactory
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java
index fb72a261005..bddf712cf15 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest;
public class UpgradeRequestAdapter implements UpgradeRequest
{
diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java
index 4e9eefed389..586d78e91c1 100644
--- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java
+++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java
@@ -27,7 +27,7 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse;
public class UpgradeResponseAdapter implements UpgradeResponse
{
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java
index 0e077b8d7ff..86dc8240721 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java
@@ -39,7 +39,7 @@ import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
index aad53455047..744449a7741 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
@@ -64,7 +64,7 @@ import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.servlet.internal.UpgradeHttpServletRequest;
+import org.eclipse.jetty.websocket.util.server.internal.UpgradeHttpServletRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java
index bb6189b06d7..cfdef700f31 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java
@@ -45,11 +45,11 @@ import org.eclipse.jetty.websocket.api.util.WSURI;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint;
import org.eclipse.jetty.websocket.tests.EchoSocket;
import org.eclipse.jetty.websocket.tests.GetAuthHeaderEndpoint;
import org.eclipse.jetty.websocket.tests.SimpleStatusServlet;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java
index 6b10557e870..3725abbb25b 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java
@@ -44,13 +44,13 @@ import org.eclipse.jetty.websocket.api.util.WSURI;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.tests.AnnoMaxMessageEndpoint;
import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint;
import org.eclipse.jetty.websocket.tests.ConnectMessageEndpoint;
import org.eclipse.jetty.websocket.tests.EchoSocket;
import org.eclipse.jetty.websocket.tests.ParamsEndpoint;
import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback;
+import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java
index 01ee6ed0346..36bc27041e5 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java
@@ -47,7 +47,7 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint;
-import org.eclipse.jetty.websocket.util.TextUtil;
+import org.eclipse.jetty.websocket.util.TextUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -293,7 +293,7 @@ public class PartialListenerTest
@Override
public void onWebSocketPartialText(String payload, boolean fin)
{
- partialEvents.offer(String.format("TEXT[payload=%s, fin=%b]", TextUtil.maxStringLength(30, payload), fin));
+ partialEvents.offer(String.format("TEXT[payload=%s, fin=%b]", TextUtils.maxStringLength(30, payload), fin));
}
}
}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
deleted file mode 100644
index 6a3e00a84bf..00000000000
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
+++ /dev/null
@@ -1,263 +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.websocket.servlet;
-
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.time.Duration;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.pathmap.PathSpec;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.websocket.core.Configuration;
-import org.eclipse.jetty.websocket.core.WebSocketComponents;
-import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Abstract Servlet used to bridge the Servlet API to the WebSocket API.
- *
- * To use this servlet, you will be required to register your websockets with the {@link WebSocketMapping} so that it can create your websockets under the
- * appropriate conditions.
- *
- * The most basic implementation would be as follows:
- *
- * package my.example;
- *
- * import WebSocketServlet;
- * import WebSocketServletFactory;
- *
- * public class MyEchoServlet extends WebSocketServlet
- * {
- * @Override
- * public void configure(WebSocketServletFactory factory)
- * {
- * factory.setDefaultMaxFrameSize(4096);
- * factory.addMapping(factory.parsePathSpec("/"), (req,res)->new EchoSocket());
- * }
- * }
- *
- *
- * Only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketMapping} handling of creating
- * WebSockets. All other requests are treated as normal servlet requests. The configuration defined by this servlet init parameters will
- * be used as the customizer for any mappings created by {@link WebSocketServletFactory#addMapping(PathSpec, WebSocketCreator)} during
- * {@link #configure(WebSocketServletFactory)} calls. The request upgrade may be peformed by this servlet, or is may be performed by a
- * {@link WebSocketUpgradeFilter} instance that will share the same {@link WebSocketMapping} instance. If the filter is used, then the
- * filter configuraton is used as the default configuration prior to this servlets configuration being applied.
- *
- *
- * Configuration / Init-Parameters:
- *
- *
- * - idleTimeout
- * - set the time in ms that a websocket may be idle before closing
- * - maxTextMessageSize
- * - set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing
- * - maxBinaryMessageSize
- * - set the size in bytes that a websocket may be accept as a Binary Message before closing
- * - inputBufferSize
- * - set the size in bytes of the buffer used to read raw bytes from the network layer
* - outputBufferSize
- * - set the size in bytes of the buffer used to write bytes to the network layer
- * - maxFrameSize
- * - The maximum frame size sent or received.
- * - autoFragment
- * - If true, frames are automatically fragmented to respect the maximum frame size.
- *
- */
-@SuppressWarnings("serial")
-public abstract class WebSocketServlet extends HttpServlet
-{
- private static final Logger LOG = LoggerFactory.getLogger(WebSocketServlet.class);
- private final CustomizedWebSocketServletFactory customizer = new CustomizedWebSocketServletFactory();
-
- private WebSocketMapping mapping;
- private WebSocketComponents components;
-
- /**
- * Configure the WebSocketServletFactory for this servlet instance by setting default
- * configuration (which may be overriden by annotations) and mapping {@link WebSocketCreator}s.
- * This method assumes a single {@link FrameHandlerFactory} will be available as a bean on the
- * {@link ContextHandler}, which in practise will mostly the the Jetty WebSocket API factory.
- *
- * @param factory the WebSocketServletFactory
- */
- protected abstract void configure(WebSocketServletFactory factory);
-
- /**
- * @return the instance of {@link FrameHandlerFactory} to be used to create the FrameHandler
- */
- protected abstract FrameHandlerFactory getFactory();
-
- @Override
- public void init() throws ServletException
- {
- try
- {
- ServletContext servletContext = getServletContext();
-
- components = WebSocketComponents.ensureWebSocketComponents(servletContext);
- mapping = new WebSocketMapping(components);
-
- String max = getInitParameter("idleTimeout");
- if (max == null)
- {
- max = getInitParameter("maxIdleTime");
- if (max != null)
- LOG.warn("'maxIdleTime' init param is deprecated, use 'idleTimeout' instead");
- }
- if (max != null)
- customizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
-
- max = getInitParameter("maxTextMessageSize");
- if (max != null)
- customizer.setMaxTextMessageSize(Long.parseLong(max));
-
- max = getInitParameter("maxBinaryMessageSize");
- if (max != null)
- customizer.setMaxBinaryMessageSize(Long.parseLong(max));
-
- max = getInitParameter("inputBufferSize");
- if (max != null)
- customizer.setInputBufferSize(Integer.parseInt(max));
-
- max = getInitParameter("outputBufferSize");
- if (max != null)
- customizer.setOutputBufferSize(Integer.parseInt(max));
-
- max = getInitParameter("maxFrameSize");
- if (max == null)
- max = getInitParameter("maxAllowedFrameSize");
- if (max != null)
- customizer.setMaxFrameSize(Long.parseLong(max));
-
- String autoFragment = getInitParameter("autoFragment");
- if (autoFragment != null)
- customizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
-
- configure(customizer); // Let user modify customizer prior after init params
- }
- catch (Throwable x)
- {
- throw new ServletException(x);
- }
- }
-
- @Override
- protected void service(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- // provide a null default customizer the customizer will be on the negotiator in the mapping
- if (mapping.upgrade(req, resp, null))
- return;
-
- // If we reach this point, it means we had an incoming request to upgrade
- // but it was either not a proper websocket upgrade, or it was possibly rejected
- // due to incoming request constraints (controlled by WebSocketCreator)
- if (resp.isCommitted())
- return;
-
- // Handle normally
- super.service(req, resp);
- }
-
- private class CustomizedWebSocketServletFactory extends Configuration.ConfigurationCustomizer implements WebSocketServletFactory
- {
- @Override
- public WebSocketExtensionRegistry getExtensionRegistry()
- {
- return components.getExtensionRegistry();
- }
-
- @Override
- public void addMapping(String pathSpec, WebSocketCreator creator)
- {
- addMapping(WebSocketMapping.parsePathSpec(pathSpec), creator);
- }
-
- @Override
- public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
- {
- mapping.addMapping(pathSpec, creator, getFactory(), this);
- }
-
- @Override
- public void register(Class> endpointClass)
- {
- Constructor> constructor;
- try
- {
- constructor = endpointClass.getDeclaredConstructor();
- }
- catch (NoSuchMethodException e)
- {
- throw new RuntimeException(e);
- }
-
- WebSocketCreator creator = (req, resp) ->
- {
- try
- {
- return constructor.newInstance();
- }
- catch (Throwable t)
- {
- t.printStackTrace();
- return null;
- }
- };
-
- addMapping("/", creator);
- }
-
- @Override
- public void setCreator(WebSocketCreator creator)
- {
- addMapping("/", creator);
- }
-
- @Override
- public WebSocketCreator getMapping(PathSpec pathSpec)
- {
- return mapping.getMapping(pathSpec);
- }
-
- @Override
- public WebSocketCreator getMatch(String target)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean removeMapping(PathSpec pathSpec)
- {
- return mapping.removeMapping(pathSpec);
- }
-
- @Override
- public PathSpec parsePathSpec(String pathSpec)
- {
- return WebSocketMapping.parsePathSpec(pathSpec);
- }
- }
-}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
deleted file mode 100644
index f62a25056e5..00000000000
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
+++ /dev/null
@@ -1,101 +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.websocket.servlet;
-
-import org.eclipse.jetty.http.pathmap.PathSpec;
-import org.eclipse.jetty.websocket.core.Configuration;
-import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
-
-public interface WebSocketServletFactory extends Configuration
-{
- WebSocketExtensionRegistry getExtensionRegistry();
-
- void addMapping(String pathSpec, WebSocketCreator creator);
-
- /**
- * add a WebSocket mapping to a provided {@link WebSocketCreator}.
- *
- * If mapping is added before this configuration is started, then it is persisted through
- * stop/start of this configuration's lifecycle. Otherwise it will be removed when
- * this configuration is stopped.
- *
- *
- * @param pathSpec the pathspec to respond on
- * @param creator the WebSocketCreator to use
- * @since 10.0
- */
- void addMapping(PathSpec pathSpec, WebSocketCreator creator);
-
- /**
- * Add a WebSocket mapping at PathSpec "/" for a creator which creates the endpointClass
- *
- * @param endpointClass the WebSocket class to use
- */
- void register(Class> endpointClass);
-
- /**
- * Add a WebSocket mapping at PathSpec "/" for a creator
- *
- * @param creator the WebSocketCreator to use
- */
- void setCreator(WebSocketCreator creator);
-
- /**
- * Returns the creator for the given path spec.
- *
- * @param pathSpec the pathspec to respond on
- * @return the websocket creator if path spec exists, or null
- */
- WebSocketCreator getMapping(PathSpec pathSpec);
-
- /**
- * Get the MappedResource for the given target path.
- *
- * @param target the target path
- * @return the MappedResource if matched, or null if not matched.
- */
- WebSocketCreator getMatch(String target);
-
- /**
- * Parse a PathSpec string into a PathSpec instance.
- *
- * Recognized Path Spec syntaxes:
- *
- *
- * - {@code /path/to} or {@code /} or {@code *.ext} or {@code servlet|{spec}}
- * - Servlet Syntax
- * - {@code ^{spec}} or {@code regex|{spec}}
- * - Regex Syntax
- * - {@code uri-template|{spec}}
- * - URI Template (see JSR356 and RFC6570 level 1)
- *
- *
- * @param rawSpec the raw path spec as String to parse.
- * @return the {@link PathSpec} implementation for the rawSpec
- */
- PathSpec parsePathSpec(String rawSpec);
-
- /**
- * Removes the mapping based on the given path spec.
- *
- * @param pathSpec the pathspec to respond on
- * @return true if underlying mapping were altered, false otherwise
- */
- boolean removeMapping(PathSpec pathSpec);
-}
diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-util-server/pom.xml
similarity index 63%
rename from jetty-websocket/websocket-servlet/pom.xml
rename to jetty-websocket/websocket-util-server/pom.xml
index beeeab4b0fc..c8b50273799 100644
--- a/jetty-websocket/websocket-servlet/pom.xml
+++ b/jetty-websocket/websocket-util-server/pom.xml
@@ -7,80 +7,19 @@
4.0.0
- websocket-servlet
- Jetty :: Websocket :: Servlet
+ websocket-util-server
+ Jetty :: Websocket :: org.eclipse.jetty.websocket :: Util-Server
- ${project.groupId}.servlet
+ ${project.groupId}.util.server
-
-
- org.eclipse.jetty.websocket
- websocket-core
- ${project.version}
-
-
- org.eclipse.jetty
- jetty-servlet
- ${project.version}
-
-
- org.slf4j
- slf4j-api
-
-
- org.eclipse.jetty
- jetty-slf4j-impl
- test
-
-
- org.eclipse.jetty.toolchain
- jetty-test-helper
- test
-
-
- org.eclipse.jetty.websocket
- websocket-jetty-api
- ${project.version}
- test
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
-
-
- artifact-jars
-
- test-jar
-
-
-
-
org.apache.maven.plugins
maven-enforcer-plugin
-
- ban-ws-apis
-
- enforce
-
-
-
-
-
- org.eclipse.jetty.websocket:websocket-jetty-api
- javax.websocket
-
-
-
-
-
ban-java-servlet-api
@@ -109,15 +48,13 @@
true
+ generate-manifest
manifest
- javax.websocket.servlet Implementation
-
- org.eclipse.jetty.websocket.servlet.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"
-
+ *,org.eclipse.jetty.websocket.util.server.internal.*
@@ -125,4 +62,32 @@
+
+
+
+ org.eclipse.jetty.websocket
+ websocket-core
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ ${project.version}
+ provided
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ test
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ test
+
+
diff --git a/jetty-websocket/websocket-servlet/src/main/java/module-info.java b/jetty-websocket/websocket-util-server/src/main/java/module-info.java
similarity index 78%
rename from jetty-websocket/websocket-servlet/src/main/java/module-info.java
rename to jetty-websocket/websocket-util-server/src/main/java/module-info.java
index 93715204b9d..ae57cc58894 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/module-info.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/module-info.java
@@ -16,11 +16,12 @@
// ========================================================================
//
-module org.eclipse.jetty.websocket.servlet
+module org.eclipse.jetty.websocket.util.server
{
- exports org.eclipse.jetty.websocket.servlet;
-
+ exports org.eclipse.jetty.websocket.util.server;
+ exports org.eclipse.jetty.websocket.util.server.internal to org.eclipse.jetty.websocket.jetty.server, org.eclipse.jetty.websocket.javax.server;
+
+ requires org.slf4j;
requires transitive org.eclipse.jetty.servlet;
requires transitive org.eclipse.jetty.websocket.core;
- requires org.slf4j;
}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java
similarity index 96%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java
index 6829d6219a9..b0a9c030e90 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server;
import java.io.IOException;
import java.time.Duration;
@@ -35,11 +35,11 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
+import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -123,7 +123,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
return holder;
}
- public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.servlet.WebSocketMapping.key";
+ public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping.key";
private final Configuration.ConfigurationCustomizer defaultCustomizer = new Configuration.ConfigurationCustomizer();
private WebSocketMapping mapping;
@@ -159,12 +159,6 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
Dumpable.dumpObjects(out, indent, this, mapping);
}
- @ManagedAttribute(value = "factory", readonly = true)
- public WebSocketMapping getMapping()
- {
- return mapping;
- }
-
@Override
public void init(FilterConfig config) throws ServletException
{
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/FrameHandlerFactory.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java
similarity index 96%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/FrameHandlerFactory.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java
index a895d9db690..bebcf7fe203 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/FrameHandlerFactory.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server.internal;
import org.eclipse.jetty.websocket.core.FrameHandler;
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java
similarity index 98%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java
index f327a424a95..a4418a9e6e7 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server.internal;
import java.net.HttpCookie;
import java.net.InetSocketAddress;
@@ -42,7 +42,6 @@ import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.Negotiation;
-import org.eclipse.jetty.websocket.servlet.internal.UpgradeHttpServletRequest;
/**
* Holder of request data for a WebSocket upgrade request.
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java
similarity index 99%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java
index 04b5d490558..f78615f46c6 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server.internal;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/internal/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java
similarity index 99%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/internal/UpgradeHttpServletRequest.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java
index f2f56dd0be5..72eafaff707 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/internal/UpgradeHttpServletRequest.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet.internal;
+package org.eclipse.jetty.websocket.util.server.internal;
import java.io.BufferedReader;
import java.net.InetSocketAddress;
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java
similarity index 96%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java
index b4db96bd148..b991530193f 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server.internal;
/**
* Abstract WebSocket creator interface.
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketMapping.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java
similarity index 97%
rename from jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketMapping.java
rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java
index 30bf8098e2b..a96a55d315c 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketMapping.java
+++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.servlet;
+package org.eclipse.jetty.websocket.util.server.internal;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
@@ -45,8 +45,6 @@ import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
-
/**
* Mapping of pathSpec to a tupple of {@link WebSocketCreator}, {@link FrameHandlerFactory} and
* {@link Configuration.Customizer}.
@@ -136,7 +134,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]");
}
- public static final String DEFAULT_KEY = "org.eclipse.jetty.websocket.servlet.WebSocketMapping";
+ public static final String DEFAULT_KEY = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping";
private final PathMappings mappings = new PathMappings<>();
private final WebSocketComponents components;
@@ -281,7 +279,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
if (websocketPojo == null)
{
// no creation, sorry
- upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
+ upgradeResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
return null;
}
diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtil.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtils.java
similarity index 99%
rename from jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtil.java
rename to jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtils.java
index 21a2a138d1a..20563f8af40 100644
--- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtil.java
+++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/TextUtils.java
@@ -21,7 +21,7 @@ package org.eclipse.jetty.websocket.util;
/**
* Collection of utility methods for Text content
*/
-public final class TextUtil
+public final class TextUtils
{
/**
* Create a hint of what the text is like.
diff --git a/jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java b/jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java
similarity index 97%
rename from jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java
rename to jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java
index 1cb9e174fc8..6569d8aed3b 100644
--- a/jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java
+++ b/jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/PartialStringMessageSinkTest.java
@@ -67,7 +67,7 @@ public class PartialStringMessageSinkTest
List message = Objects.requireNonNull(endpoint.messages.poll(5, TimeUnit.SECONDS));
assertThat(message.size(), is(1));
- assertThat(message.get(0), is("\uD800\uDF48"));
+ assertThat(message.get(0), is("\uD800\uDF48")); // UTF-8 encoded payload.
}
@Test
@@ -87,7 +87,7 @@ public class PartialStringMessageSinkTest
List message = Objects.requireNonNull(endpoint.messages.poll(5, TimeUnit.SECONDS));
assertThat(message.size(), is(2));
assertThat(message.get(0), is(""));
- assertThat(message.get(1), is("\uD800\uDF48"));
+ assertThat(message.get(1), is("\uD800\uDF48")); // UTF-8 encoded payload.
}
@Test
diff --git a/jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java b/jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java
similarity index 98%
rename from jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java
rename to jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java
index f876c52a942..8696ec91565 100644
--- a/jetty-websocket/websocket-util/src/test/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java
+++ b/jetty-websocket/websocket-util/src/test/java/org/eclipse/jetty/websocket/util/StringMessageSinkTest.java
@@ -72,7 +72,7 @@ public class StringMessageSinkTest
messageSink.accept(new Frame(OpCode.TEXT, utf8Payload).setFin(true), callback);
callback.block(5, TimeUnit.SECONDS);
- assertThat(endpoint.messages.poll(5, TimeUnit.SECONDS), is("\uD800\uDF48"));
+ assertThat(endpoint.messages.poll(5, TimeUnit.SECONDS), is("\uD800\uDF48")); // UTF-8 encoded payload.
}
@Test
@@ -90,7 +90,7 @@ public class StringMessageSinkTest
messageSink.accept(new Frame(OpCode.TEXT, continuationUtf8Payload).setFin(true), callback);
callback.block(5, TimeUnit.SECONDS);
- assertThat(endpoint.messages.poll(5, TimeUnit.SECONDS), is("\uD800\uDF48"));
+ assertThat(endpoint.messages.poll(5, TimeUnit.SECONDS), is("\uD800\uDF48")); // UTF-8 encoded payload.
}
@Test
diff --git a/pom.xml b/pom.xml
index 8602b3544db..620ebcffdeb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
1.21
benchmarks
1.4.0
- 5.5.1
+ 5.6.2
3.6.0
1.3.1
3.1.2.Final
@@ -41,7 +41,7 @@
false
- -Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx1g -Xms1g -Xlog:gc:stderr:time,level,tags
+ -Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx2g -Xms2g -Xlog:gc:stderr:time,level,tags
3.0.0-M4
@@ -56,8 +56,7 @@
false
- 5.3
-
+ 5.4
2.1.1.RELEASE
@@ -1063,6 +1062,16 @@
junit-jupiter
${junit.version}
+
+ org.testcontainers
+ testcontainers
+ 1.14.1
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.14.1
+
org.eclipse.jetty.toolchain
diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml
index 59abe41b08b..d7176621af5 100644
--- a/tests/test-distribution/pom.xml
+++ b/tests/test-distribution/pom.xml
@@ -113,6 +113,12 @@
${project.version}
test
+
+ org.eclipse.jetty.websocket
+ websocket-jetty-client
+ ${project.version}
+ test
+
org.eclipse.jetty.tests
test-felix-webapp
@@ -120,6 +126,13 @@
test
war
+
+ org.eclipse.jetty.tests
+ test-websocket-webapp
+ ${project.version}
+ test
+ war
+
org.eclipse.jetty.tests
test-bad-websocket-webapp
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
index 1d73e20f571..6afc56b7c6e 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
@@ -20,12 +20,14 @@ package org.eclipse.jetty.tests.distribution;
import java.io.BufferedWriter;
import java.io.File;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
@@ -37,10 +39,14 @@ import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
+import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.jupiter.api.Disabled;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
@@ -49,6 +55,7 @@ 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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -410,6 +417,7 @@ public class DistributionTests extends AbstractDistributionTest
"",
"--jpms",
})
+ @DisabledOnJre(JRE.JAVA_14) // TODO: Waiting on JDK14 bug at https://bugs.openjdk.java.net/browse/JDK-8244090.
public void testSimpleWebAppWithWebsocket(String arg) throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
@@ -427,9 +435,13 @@ public class DistributionTests extends AbstractDistributionTest
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
- File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-bad-websocket-webapp:war:" + jettyVersion);
- distribution.installWarFile(war, "test1");
- distribution.installWarFile(war, "test2");
+ File webApp = distribution.resolveArtifact("org.eclipse.jetty.tests:test-websocket-webapp:war:" + jettyVersion);
+ File badWebApp = distribution.resolveArtifact("org.eclipse.jetty.tests:test-bad-websocket-webapp:war:" + jettyVersion);
+
+ distribution.installWarFile(webApp, "test1");
+ distribution.installWarFile(badWebApp, "test2");
+ distribution.installWarFile(badWebApp, "test3");
+ distribution.installWarFile(webApp, "test4");
int port = distribution.freePort();
String[] args2 = {
@@ -437,26 +449,64 @@ public class DistributionTests extends AbstractDistributionTest
"jetty.http.port=" + port//,
//"jetty.server.dumpAfterStart=true"
};
+
try (DistributionTester.Run run2 = distribution.start(args2))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
- // we do not test that anymore because it doesn't work for java14
- //assertFalse(run2.getLogs().stream().anyMatch(s -> s.contains("LinkageError")));
+ assertFalse(run2.getLogs().stream().anyMatch(s -> s.contains("LinkageError")));
startHttpClient();
- ContentResponse response = client.GET("http://localhost:" + port + "/test1/index.jsp");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("Hello"));
- assertThat(response.getContentAsString(), not(containsString("<%")));
+ WebSocketClient wsClient = new WebSocketClient(client);
+ wsClient.start();
+ URI serverUri = URI.create("ws://localhost:" + port);
- client.GET("http://localhost:" + port + "/test2/index.jsp");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("Hello"));
- assertThat(response.getContentAsString(), not(containsString("<%")));
+ // Verify /test1 is able to establish a WebSocket connection.
+ WsListener webSocketListener = new WsListener();
+ Session session = wsClient.connect(webSocketListener, serverUri.resolve("/test1")).get(5, TimeUnit.SECONDS);
+ session.getRemote().sendString("echo message");
+ assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message"));
+ session.close();
+ assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE));
+
+ // Verify that /test2 and /test3 could not be started.
+ ContentResponse response = client.GET(serverUri.resolve("/test2/badonopen/a"));
+ assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus());
+ client.GET("http://localhost:" + port + "/test3/badonopen/a");
+ assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus());
+
+ // Verify /test4 is able to establish a WebSocket connection.
+ webSocketListener = new WsListener();
+ session = wsClient.connect(webSocketListener, serverUri.resolve("/test4")).get(5, TimeUnit.SECONDS);
+ session.getRemote().sendString("echo message");
+ assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message"));
+ session.close();
+ assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE));
}
}
}
+ public static class WsListener implements WebSocketListener
+ {
+ BlockingArrayQueue textMessages = new BlockingArrayQueue<>();
+ private CountDownLatch closeLatch = new CountDownLatch(1);
+ private int closeCode;
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason)
+ {
+ this.closeCode = statusCode;
+ closeLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketText(String message)
+ {
+ textMessages.add(message);
+ }
+ }
+
@Test
public void testStartStopLog4j2Modules() throws Exception
{
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 0504ded0f85..f2239a533b9 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
@@ -56,6 +56,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.HTTP2Session;
@@ -74,6 +75,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 +399,7 @@ public class AsyncIOServletTest extends AbstractTest headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.body(new StringRequestContent(text))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -893,7 +896,7 @@ public class AsyncIOServletTest extends AbstractTest headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.body(new StringRequestContent("XYZ"))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -949,7 +952,7 @@ public class AsyncIOServletTest extends AbstractTest headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.timeout(5, TimeUnit.SECONDS)
.send();
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
index 47e3c28b362..2a4b3c508f7 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
@@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ConnectionStatistics;
@@ -101,7 +102,7 @@ public class ConnectionStatisticsTest extends AbstractTest
byte[] content = new byte[3072];
long contentLength = content.length;
ContentResponse response = scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.CONNECTION, "close")
+ .headers(headers -> headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
index 1fd46c84b6d..53e4f9ae8f8 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
@@ -103,7 +103,7 @@ public class HttpClientContinueTest extends AbstractTest
});
ContentResponse response = scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(contents))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -144,7 +144,7 @@ public class HttpClientContinueTest extends AbstractTest
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
ContentResponse response = scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content1, content2)
{
@Override
@@ -202,7 +202,7 @@ public class HttpClientContinueTest extends AbstractTest
byte[] content2 = new byte[16384];
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content1, content2))
.send(new BufferingResponseListener()
{
@@ -254,7 +254,7 @@ public class HttpClientContinueTest extends AbstractTest
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path("/continue")
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@@ -304,7 +304,7 @@ public class HttpClientContinueTest extends AbstractTest
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path("/redirect")
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@@ -351,7 +351,7 @@ public class HttpClientContinueTest extends AbstractTest
byte[] content = new byte[1024];
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.send(new BufferingResponseListener()
@@ -400,7 +400,7 @@ public class HttpClientContinueTest extends AbstractTest
byte[] content = new byte[1024];
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@@ -460,7 +460,7 @@ public class HttpClientContinueTest extends AbstractTest
byte[] content = new byte[1024];
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@@ -504,7 +504,7 @@ public class HttpClientContinueTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(content)
.send(new BufferingResponseListener()
{
@@ -555,7 +555,7 @@ public class HttpClientContinueTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(chunk1));
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(content)
.send(new BufferingResponseListener()
{
@@ -596,7 +596,7 @@ public class HttpClientContinueTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.onRequestHeaders(request ->
{
content.offer(ByteBuffer.wrap(data));
@@ -660,7 +660,7 @@ public class HttpClientContinueTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(content)
.send(new BufferingResponseListener()
{
@@ -694,7 +694,7 @@ public class HttpClientContinueTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest("localhost", server.getLocalPort())
- .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+ .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(new byte[]{0}))
.send(result ->
{
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java
index f25ef0fa80f..f03b5570f11 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java
@@ -43,6 +43,7 @@ import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -224,23 +225,23 @@ public class HttpClientLoadTest extends AbstractTest headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE));
else if (serverClose)
- request.header("X-Close", "true");
+ request.headers(headers -> headers.put("X-Close", "true"));
if (clientTimeout > 0)
{
- request.header("X-Timeout", String.valueOf(clientTimeout));
+ request.headers(headers -> headers.put("X-Timeout", String.valueOf(clientTimeout)));
request.idleTimeout(clientTimeout, TimeUnit.MILLISECONDS);
}
switch (method)
{
case "GET":
- request.header("X-Download", String.valueOf(contentLength));
+ request.headers(headers -> headers.put("X-Download", String.valueOf(contentLength)));
break;
case "POST":
- request.header("X-Upload", String.valueOf(contentLength));
+ request.headers(headers -> headers.put("X-Upload", String.valueOf(contentLength)));
request.body(new BytesRequestContent(new byte[contentLength]));
break;
}
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
index f1e679e9aa7..a48de6bbf96 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
@@ -619,7 +619,7 @@ public class HttpClientTest extends AbstractTest
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.HEAD)
.path(scenario.servletPath)
- .header(HttpHeader.ACCEPT, "*/*")
+ .headers(headers -> headers.put(HttpHeader.ACCEPT, "*/*"))
.send();
assertEquals(status, response.getStatus());
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java
index 4ce25b5eb2e..01b8d14eda6 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java
@@ -515,9 +515,10 @@ public class HttpClientTransportDynamicTest
// Make an upgrade request from HTTP/1.1 to H2C.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.HTTP2_SETTINGS, "")
- .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -595,9 +596,10 @@ public class HttpClientTransportDynamicTest
// Make an upgrade request from HTTP/1.1 to H2C.
int serverPort = proxyPort + 1; // Any port will do.
ContentResponse response = client.newRequest("localhost", serverPort)
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.HTTP2_SETTINGS, "")
- .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -628,9 +630,10 @@ public class HttpClientTransportDynamicTest
// Make an upgrade request from HTTP/1.1 to H2C.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.HTTP2_SETTINGS, "")
- .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -665,9 +668,10 @@ public class HttpClientTransportDynamicTest
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.HTTP2_SETTINGS, "")
- .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.body(new BytesRequestContent(bytes))
.timeout(5, TimeUnit.SECONDS)
.send(new BufferingResponseListener(bytes.length)
@@ -698,8 +702,8 @@ public class HttpClientTransportDynamicTest
// The upgrade request is missing the required HTTP2-Settings header.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.CONNECTION, "Upgrade")
+ .headers(headers -> headers.add(HttpHeader.UPGRADE, "h2c"))
+ .headers(headers -> headers.add(HttpHeader.CONNECTION, "Upgrade"))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -725,9 +729,10 @@ public class HttpClientTransportDynamicTest
// Make an upgrade request from HTTP/1.1 to H2C.
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
- .header(HttpHeader.UPGRADE, "h2c")
- .header(HttpHeader.HTTP2_SETTINGS, "")
- .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.send(result ->
{
if (result.isFailed())
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java
index a1aa817edd3..6aaa535a18e 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java
@@ -42,6 +42,7 @@ import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.EndPoint;
@@ -180,7 +181,7 @@ public class FailedSelectorTest
ContentResponse response = client.newRequest(dest)
.method(HttpMethod.GET)
- .header(HttpHeader.CONNECTION, "close")
+ .headers(headers -> headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.send();
assertThat(dest + " status", response.getStatus(), is(HttpStatus.OK_200));
@@ -193,7 +194,7 @@ public class FailedSelectorTest
LOG.info("Requesting GET on {}", dest);
ContentResponse response = client.newRequest(dest)
.method(HttpMethod.GET)
- .header(HttpHeader.CONNECTION, "close")
+ .headers(headers -> headers.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.send();
assertThat(dest + " status", response.getStatus(), is(HttpStatus.OK_200));
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/RecoverFailedSelectorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/RecoverFailedSelectorTest.java
new file mode 100644
index 00000000000..9cbbc89c56f
--- /dev/null
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/RecoverFailedSelectorTest.java
@@ -0,0 +1,379 @@
+//
+// ========================================================================
+// 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.test;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.tools.HttpTester;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RecoverFailedSelectorTest
+{
+ private Server server;
+ private ServerConnector connector;
+
+ private void start(Function 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
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java
index 96131d3edc6..361fd148efa 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java
@@ -20,7 +20,6 @@ package org.eclipse.jetty.server.session;
import java.io.IOException;
import java.io.PrintWriter;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -29,11 +28,12 @@ import javax.servlet.http.HttpSession;
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.HttpField;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* ClusteredSessionMigrationTest
@@ -106,14 +106,15 @@ public class ClusteredSessionMigrationTest extends AbstractTestBase
ContentResponse response1 = request1.send();
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
String sessionCookie = response1.getHeaders().get("Set-Cookie");
- assertTrue(sessionCookie != null);
+ assertNotNull(sessionCookie);
// Mangle the cookie, replacing Path with $Path, etc.
- sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+ sessionCookie = sessionCookie.replaceFirst("(\\W)([Pp])ath=", "$1\\$Path=");
// Perform a request to server2 using the session cookie from the previous request
// This should migrate the session from server1 to server2.
Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping.substring(1) + "?action=get");
- request2.header("Cookie", sessionCookie);
+ HttpField cookie = new HttpField("Cookie", sessionCookie);
+ request2.headers(headers -> headers.put(cookie));
ContentResponse response2 = request2.send();
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
}
@@ -140,13 +141,13 @@ public class ClusteredSessionMigrationTest extends AbstractTestBase
private static long createTime = 0;
@Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
doPost(request, response);
}
@Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException
{
HttpSession session = request.getSession(false);
diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml
index 11e301fe167..dee31766e2e 100644
--- a/tests/test-sessions/test-mongodb-sessions/pom.xml
+++ b/tests/test-sessions/test-mongodb-sessions/pom.xml
@@ -12,6 +12,8 @@
${project.groupId}.sessions.mongo
localhost
+
+ 2.2.7
@@ -111,13 +113,28 @@
jetty-test-helper
test
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
- mongodb
+ remote-session-tests
- mongodb.enabled
+ mongo.enabled
true
@@ -128,51 +145,11 @@
maven-surefire-plugin
- ${embedmongo.port}
- ${embedmongo.host}
+ ${mongo.docker.version}
false
-
- com.github.joelittlejohn.embedmongo
- embedmongo-maven-plugin
- 0.4.1
-
-
-
- true
-
- ${project.build.directory}/mongotest
-
-
- file
-
- ${project.build.directory}/embedmongo.log
-
-
-
- false
- https://jenkins.webtide.net/userContent/
- 2.2.1
-
-
-
- start
- process-test-classes
-
- start
-
-
-
- stop
- test
-
- stop
-
-
-
-
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java
index 1d545059ea7..9b641c11ac4 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java
@@ -21,7 +21,6 @@ package org.eclipse.jetty.nosql.mongodb;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -29,6 +28,7 @@ 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.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.session.DefaultSessionCacheFactory;
import org.eclipse.jetty.server.session.Session;
@@ -40,7 +40,6 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* AttributeNameTest
@@ -109,14 +108,15 @@ public class AttributeNameTest
String sessionCookie = response.getHeaders().get(HttpHeader.SET_COOKIE);
- assertTrue(sessionCookie != null);
+ assertNotNull(sessionCookie);
//Mangle the cookie, replacing Path with $Path, etc.
- sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+ sessionCookie = sessionCookie.replaceFirst("(\\W)([Pp])ath=", "$1\\$Path=");
//Make a request to the 2nd server which will do a refresh, use TestServlet to ensure that the
//session attribute with dotted name is not removed
Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
- request2.header("Cookie", sessionCookie);
+ HttpField cookie = new HttpField("Cookie", sessionCookie);
+ request2.headers(headers -> headers.put(cookie));
ContentResponse response2 = request2.send();
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
}
@@ -135,7 +135,7 @@ public class AttributeNameTest
public static class TestServlet extends HttpServlet
{
@Override
- protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
+ protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException
{
String action = request.getParameter("action");
if ("init".equals(action))
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStoreTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStoreTest.java
index c53d73d6342..6380083e62e 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStoreTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStoreTest.java
@@ -42,15 +42,16 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
*/
public class MongoSessionDataStoreTest extends AbstractSessionDataStoreTest
{
+
@BeforeEach
- public void beforeClass() throws Exception
+ public void beforeEach() throws Exception
{
MongoTestHelper.dropCollection();
MongoTestHelper.createCollection();
}
@AfterEach
- public void afterClass() throws Exception
+ public void afterEach() throws Exception
{
MongoTestHelper.dropCollection();
}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java
index 28c0ebe0ca5..d6aef0a6865 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java
@@ -35,6 +35,9 @@ import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -45,37 +48,71 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class MongoTestHelper
{
private static final Logger LOG = LoggerFactory.getLogger(MongoTestHelper.class);
+ private static final Logger MONGO_LOG = LoggerFactory.getLogger("org.eclipse.jetty.nosql.mongodb.MongoLogs");
+
public static final String DB_NAME = "HttpSessions";
public static final String COLLECTION_NAME = "testsessions";
- static MongoClient _mongoClient;
+ static GenericContainer mongo =
+ new GenericContainer("mongo:" + System.getProperty("mongo.docker.version", "2.2.7"))
+ .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG));
- static
+ static MongoClient mongoClient;
+
+ public static void startMongo()
{
try
{
- _mongoClient =
- new MongoClient(System.getProperty("embedmongo.host"), Integer.getInteger("embedmongoPort"));
+ long start = System.currentTimeMillis();
+ mongo.start();
+ String containerIpAddress = mongo.getContainerIpAddress();
+ int mongoPort = mongo.getMappedPort(27017);
+ LOG.info("Mongo container started for {}:{} - {}ms", containerIpAddress, mongoPort,
+ System.currentTimeMillis() - start);
+ System.setProperty("embedmongoHost", containerIpAddress);
+ System.setProperty("embedmongoPort", Integer.toString(mongoPort));
}
- catch (UnknownHostException e)
+ catch (Exception e)
{
- e.printStackTrace();
+ LOG.error(e.getMessage(), e);
+ throw new RuntimeException(e);
}
}
- public static void dropCollection() throws MongoException, UnknownHostException
+ public static void stopMongo()
{
- _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME).drop();
+ mongo.stop();
+ mongoClient = null;
+ }
+
+ public static MongoClient getMongoClient() throws UnknownHostException
+ {
+ boolean restart = false;
+ if (mongo == null || !mongo.isRunning())
+ {
+ startMongo();
+ restart = true;
+ }
+ if (mongoClient == null || restart)
+ {
+ mongoClient = new MongoClient(System.getProperty("embedmongoHost"), Integer.getInteger("embedmongoPort"));
+ }
+ return mongoClient;
+ }
+
+ public static void dropCollection() throws Exception
+ {
+ getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME).drop();
}
public static void createCollection() throws UnknownHostException, MongoException
{
- _mongoClient.getDB(DB_NAME).createCollection(COLLECTION_NAME, null);
+ getMongoClient().getDB(DB_NAME).createCollection(COLLECTION_NAME, null);
}
public static DBCollection getCollection() throws UnknownHostException, MongoException
{
- return _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ return getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
}
public static MongoSessionDataStoreFactory newSessionDataStoreFactory()
@@ -91,7 +128,7 @@ public class MongoTestHelper
public static boolean checkSessionExists(String id)
throws Exception
{
- DBCollection collection = _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ DBCollection collection = getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
DBObject fields = new BasicDBObject();
fields.put(MongoSessionDataStore.__EXPIRY, 1);
@@ -108,7 +145,7 @@ public class MongoTestHelper
public static boolean checkSessionPersisted(SessionData data)
throws Exception
{
- DBCollection collection = _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ DBCollection collection = getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
DBObject fields = new BasicDBObject();
@@ -116,7 +153,7 @@ public class MongoTestHelper
if (sessionDocument == null)
return false; //doesn't exist
- LOG.info("{}", sessionDocument);
+ LOG.debug("{}", sessionDocument);
Boolean valid = (Boolean)sessionDocument.get(MongoSessionDataStore.__VALID);
@@ -182,7 +219,7 @@ public class MongoTestHelper
Map attributes)
throws Exception
{
- DBCollection collection = _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ DBCollection collection = getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
// Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
@@ -231,7 +268,7 @@ public class MongoTestHelper
throws Exception
{
- DBCollection collection = _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ DBCollection collection = getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
// Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
@@ -277,7 +314,7 @@ public class MongoTestHelper
throws Exception
{
//make old-style session to test if we can retrieve it
- DBCollection collection = _mongoClient.getDB(DB_NAME).getCollection(COLLECTION_NAME);
+ DBCollection collection = getMongoClient().getDB(DB_NAME).getCollection(COLLECTION_NAME);
// Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/resources/simplelogger.properties b/tests/test-sessions/test-mongodb-sessions/src/test/resources/simplelogger.properties
new file mode 100644
index 00000000000..5f859960729
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/resources/simplelogger.properties
@@ -0,0 +1,3 @@
+org.slf4j.simpleLogger.defaultLogLevel=info
+org.slf4j.simpleLogger.log.org.eclipse.jetty.nosql.mongodb.MongoLogs=error
+org.slf4j.simpleLogger.log.org.eclipse.jetty.nosql.mongodb.MongoTestHelper=info
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClusteredOrphanedSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClusteredOrphanedSessionTest.java
index 521086eb56d..c0bc2578da5 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClusteredOrphanedSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClusteredOrphanedSessionTest.java
@@ -29,10 +29,12 @@ import javax.servlet.http.HttpSession;
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.HttpField;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
/**
* AbstractClusteredOrphanedSessionTest
@@ -90,9 +92,9 @@ public abstract class AbstractClusteredOrphanedSessionTest extends AbstractTestB
ContentResponse response1 = client.GET("http://localhost:" + port1 + contextPath + servletMapping.substring(1) + "?action=init");
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
String sessionCookie = response1.getHeaders().get("Set-Cookie");
- assertTrue(sessionCookie != null);
+ assertNotNull(sessionCookie);
// Mangle the cookie, replacing Path with $Path, etc.
- sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+ sessionCookie = sessionCookie.replaceFirst("(\\W)([Pp])ath=", "$1\\$Path=");
// Wait for the session to expire.
// The first node does not do any scavenging, but the session
@@ -101,7 +103,8 @@ public abstract class AbstractClusteredOrphanedSessionTest extends AbstractTestB
// Perform one request to server2 to be sure that the session has been expired
Request request = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping.substring(1) + "?action=check");
- request.header("Cookie", sessionCookie);
+ HttpField cookie = new HttpField("Cookie", sessionCookie);
+ request.headers(headers -> headers.put(cookie));
ContentResponse response2 = request.send();
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
}
@@ -141,7 +144,7 @@ public abstract class AbstractClusteredOrphanedSessionTest extends AbstractTestB
else if ("check".equals(action))
{
HttpSession session = request.getSession(false);
- assertTrue(session == null);
+ assertNull(session);
}
}
}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
index dbe70cce2c1..84302679316 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
@@ -28,6 +28,7 @@ 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.HttpField;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
@@ -35,7 +36,7 @@ import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* AbstractWebAppObjectInSessionTest
@@ -133,9 +134,9 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractTestBase
ContentResponse response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
- assertTrue(sessionCookie != null);
+ assertNotNull(sessionCookie);
// Mangle the cookie, replacing Path with $Path, etc.
- sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+ sessionCookie = sessionCookie.replaceFirst("(\\W)([Pp])ath=", "$1\\$Path=");
//ensure request has finished being handled
synchronizer.await(5, TimeUnit.SECONDS);
@@ -143,7 +144,8 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractTestBase
// Perform a request to server2 using the session cookie from the previous request
Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
request2.method(HttpMethod.GET);
- request2.header("Cookie", sessionCookie);
+ HttpField cookie = new HttpField("Cookie", sessionCookie);
+ request2.headers(headers -> headers.put(cookie));
ContentResponse response2 = request2.send();
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
index f6a6c6458ed..72473677ff7 100644
--- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
@@ -28,12 +28,14 @@ import javax.servlet.http.HttpSession;
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.HttpField;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
/**
* ClientCrossContextSessionTest
@@ -81,12 +83,13 @@ public class ClientCrossContextSessionTest
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
- assertTrue(sessionCookie != null);
+ assertNotNull(sessionCookie);
String sessionId = TestServer.extractSessionId(sessionCookie);
// Perform a request to contextB with the same session cookie
Request request = client.newRequest("http://localhost:" + port + contextB + servletMapping);
- request.header("Cookie", "JSESSIONID=" + sessionId);
+ HttpField cookie = new HttpField("Cookie", "JSESSIONID=" + sessionId);
+ request.headers(headers -> headers.put(cookie));
ContentResponse responseB = request.send();
assertEquals(HttpServletResponse.SC_OK, responseB.getStatus());
assertEquals(servletA.sessionId, servletB.sessionId);
@@ -122,7 +125,7 @@ public class ClientCrossContextSessionTest
// Check that we don't see things put in session by contextB
Object objectB = session.getAttribute("B");
- assertTrue(objectB == null);
+ assertNull(objectB);
}
}
@@ -145,7 +148,7 @@ public class ClientCrossContextSessionTest
// Check that we don't see things put in session by contextA
Object objectA = session.getAttribute("A");
- assertTrue(objectA == null);
+ assertNull(objectA);
}
}
}
diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DeleteUnloadableSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DeleteUnloadableSessionTest.java
index 02c1e3edeee..9673fbd094e 100644
--- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DeleteUnloadableSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DeleteUnloadableSessionTest.java
@@ -31,6 +31,7 @@ import javax.servlet.http.HttpSession;
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.HttpField;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -45,14 +46,11 @@ import static org.junit.jupiter.api.Assertions.assertNull;
*/
public class DeleteUnloadableSessionTest
{
-
/**
* DelSessionDataStore
*/
public static class DelSessionDataStore extends AbstractSessionDataStore
{
- int count = 0;
-
Object o = new Object();
String unloadableId = null;
@@ -64,7 +62,7 @@ public class DeleteUnloadableSessionTest
}
@Override
- public boolean exists(String id) throws Exception
+ public boolean exists(String id)
{
return o != null;
}
@@ -77,7 +75,7 @@ public class DeleteUnloadableSessionTest
}
@Override
- public boolean delete(String id) throws Exception
+ public boolean delete(String id)
{
if (id.equals(unloadableId))
{
@@ -88,7 +86,7 @@ public class DeleteUnloadableSessionTest
}
@Override
- public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
+ public void doStore(String id, SessionData data, long lastSaveTime)
{
//pretend it was saved
}
@@ -102,9 +100,8 @@ public class DeleteUnloadableSessionTest
public static class DelSessionDataStoreFactory extends AbstractSessionDataStoreFactory
{
-
@Override
- public SessionDataStore getSessionDataStore(SessionHandler handler) throws Exception
+ public SessionDataStore getSessionDataStore(SessionHandler handler)
{
return new DelSessionDataStore();
}
@@ -141,8 +138,8 @@ public class DeleteUnloadableSessionTest
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
cacheFactory.setRemoveUnloadableSessions(true);
- SessionDataStoreFactory storeFactory = new DelSessionDataStoreFactory();
- ((AbstractSessionDataStoreFactory)storeFactory).setGracePeriodSec(scavengePeriod);
+ AbstractSessionDataStoreFactory storeFactory = new DelSessionDataStoreFactory();
+ storeFactory.setGracePeriodSec(scavengePeriod);
TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory);
ServletContextHandler context = server.addContext(contextPath);
@@ -154,7 +151,7 @@ public class DeleteUnloadableSessionTest
ServletHolder holder = new ServletHolder(servlet);
context.addServlet(holder, servletMapping);
- try (StacklessLogging stackless = new StacklessLogging(DeleteUnloadableSessionTest.class.getPackage()))
+ try (StacklessLogging ignored = new StacklessLogging(DeleteUnloadableSessionTest.class.getPackage()))
{
server.start();
int port = server.getPort();
@@ -166,7 +163,8 @@ public class DeleteUnloadableSessionTest
scopeListener.setExitSynchronizer(latch);
String sessionCookie = "JSESSIONID=w0rm3zxpa6h1zg1mevtv76b3te00.w0;$Path=/";
Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
- request.header("Cookie", sessionCookie);
+ HttpField cookie = new HttpField("Cookie", sessionCookie);
+ request.headers(headers -> headers.put(cookie));
ContentResponse response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DuplicateCookieTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DuplicateCookieTest.java
index a254d2fe3cd..e6513c376ec 100644
--- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DuplicateCookieTest.java
+++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DuplicateCookieTest.java
@@ -61,7 +61,7 @@ public class DuplicateCookieTest
server1.start();
int port1 = server1.getPort();
- try (StacklessLogging stackless = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
+ try (StacklessLogging ignored = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
{
//create a valid session
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
@@ -73,8 +73,8 @@ public class DuplicateCookieTest
//make a request with another session cookie in there that does not exist
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
- request.header("Cookie", "JSESSIONID=123"); //doesn't exist
- request.header("Cookie", "JSESSIONID=4422"); //does exist
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=123")); //doesn't exist
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=4422")); //does exist
ContentResponse response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertEquals("4422", response.getContentAsString());
@@ -104,7 +104,7 @@ public class DuplicateCookieTest
server1.start();
int port1 = server1.getPort();
- try (StacklessLogging stackless = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
+ try (StacklessLogging ignored = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
{
//create a valid session
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
@@ -120,8 +120,8 @@ public class DuplicateCookieTest
//make a request with another session cookie in there that is not valid
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
- request.header("Cookie", "JSESSIONID=1122"); //is valid
- request.header("Cookie", "JSESSIONID=2233"); //is invalid
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=1122")); //is valid
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=2233")); //is invalid
ContentResponse response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertEquals("1122", response.getContentAsString());
@@ -151,7 +151,7 @@ public class DuplicateCookieTest
server1.start();
int port1 = server1.getPort();
- try (StacklessLogging stackless = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
+ try (StacklessLogging ignored = new StacklessLogging(DuplicateCookieTest.class.getPackage()))
{
//create some of unexpired sessions
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
@@ -169,8 +169,8 @@ public class DuplicateCookieTest
//make a request with multiple valid session ids
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
- request.header("Cookie", "JSESSIONID=1234");
- request.header("Cookie", "JSESSIONID=5678");
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=1234"));
+ request.headers(headers -> headers.add("Cookie", "JSESSIONID=5678"));
ContentResponse response = request.send();
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
diff --git a/tests/test-webapps/test-bad-websocket-webapp/pom.xml b/tests/test-webapps/test-bad-websocket-webapp/pom.xml
index 4ae87c9a887..7e7726c941a 100644
--- a/tests/test-webapps/test-bad-websocket-webapp/pom.xml
+++ b/tests/test-webapps/test-bad-websocket-webapp/pom.xml
@@ -17,11 +17,13 @@
org.eclipse.jetty.toolchain
jetty-javax-websocket-api
+ provided
org.eclipse.jetty.websocket
websocket-javax-common
${project.version}
+ provided
org.eclipse.jetty.toolchain
diff --git a/tests/test-webapps/test-websocket-webapp/pom.xml b/tests/test-webapps/test-websocket-webapp/pom.xml
index d9a8d3cfb5d..8759869ec7d 100644
--- a/tests/test-webapps/test-websocket-webapp/pom.xml
+++ b/tests/test-webapps/test-websocket-webapp/pom.xml
@@ -25,11 +25,13 @@
org.eclipse.jetty.toolchain
jetty-javax-websocket-api
+ provided
org.eclipse.jetty.websocket
websocket-javax-common
${project.version}
+ provided
org.eclipse.jetty.toolchain
diff --git a/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnOpenServerEndpoint.java b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java
similarity index 71%
rename from tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnOpenServerEndpoint.java
rename to tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java
index 32b79f14a67..fcbc80642fe 100644
--- a/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnOpenServerEndpoint.java
+++ b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java
@@ -18,8 +18,6 @@
package org.eclipse.jetty.tests.webapp.websocket;
-import java.io.IOException;
-import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
@@ -28,16 +26,15 @@ import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@ServerEndpoint("/onopen/{arg}")
-public class OnOpenServerEndpoint
+@ServerEndpoint(value = "/", decoders = {StringSequenceDecoder.class})
+public class EchoEndpoint
{
- private static final Logger LOGGER = LoggerFactory.getLogger(OnOpenServerEndpoint.class);
- private static String open = "";
+ private static final Logger LOGGER = LoggerFactory.getLogger(EchoEndpoint.class);
@OnMessage
- public String echo(String echo)
+ public String echo(StringSequence echo)
{
- return open + echo;
+ return echo.toString();
}
@OnOpen
@@ -45,12 +42,4 @@ public class OnOpenServerEndpoint
{
LOGGER.info("Session opened");
}
-
- @OnError
- public void onError(Session session, Throwable t)
- throws IOException
- {
- String message = "Error happened:" + t.getMessage();
- session.getBasicRemote().sendText(message);
- }
}
diff --git a/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnCloseServerEndpoint.java b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequence.java
similarity index 50%
rename from tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnCloseServerEndpoint.java
rename to tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequence.java
index 11467a865f4..0c51c77ce1e 100644
--- a/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/OnCloseServerEndpoint.java
+++ b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequence.java
@@ -18,39 +18,37 @@
package org.eclipse.jetty.tests.webapp.websocket;
-import java.io.IOException;
-import javax.websocket.OnClose;
-import javax.websocket.OnError;
-import javax.websocket.OnMessage;
-import javax.websocket.Session;
-import javax.websocket.server.ServerEndpoint;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@ServerEndpoint("/onclose/{arg}")
-public class OnCloseServerEndpoint
+public class StringSequence
+ implements CharSequence
{
- private static final Logger LOGGER = LoggerFactory.getLogger(OnCloseServerEndpoint.class);
- private static String close = "";
+ public String stringBuffer;
- @OnMessage
- public String echo(String echo)
+ public StringSequence(String hold)
{
- return close + echo;
+ stringBuffer = hold;
}
- @OnClose
- public void onClose(Session session)
+ @Override
+ public int length()
{
- LOGGER.info("Session close");
+ return stringBuffer.length();
}
- @OnError
- public void onError(Session session, Throwable t)
- throws IOException
+ @Override
+ public char charAt(int index)
{
- String message = "Error happened:" + t.getMessage();
- session.getBasicRemote().sendText(message);
+ return stringBuffer.charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end)
+ {
+ return stringBuffer.subSequence(start, end);
+ }
+
+ @Override
+ public String toString()
+ {
+ return stringBuffer;
}
}
diff --git a/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequenceDecoder.java b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequenceDecoder.java
new file mode 100644
index 00000000000..fa0d28bc169
--- /dev/null
+++ b/tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/StringSequenceDecoder.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// 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.tests.webapp.websocket;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public class StringSequenceDecoder implements Decoder.Text
+{
+ @Override
+ public StringSequence decode(String s) throws DecodeException
+ {
+ return new StringSequence(s);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+
+ }
+
+ @Override
+ public void destroy()
+ {
+
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}