Merged branch 'jetty-9.4.x' into 'master'.

This commit is contained in:
Simone Bordet 2017-01-10 15:07:26 +01:00
commit a51f800054
25 changed files with 567 additions and 102 deletions

View File

@ -0,0 +1,94 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client.ssl;
import java.io.File;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.Assert;
import org.junit.Test;
public class SslConnectionTest
{
@Test
public void testSslConnectionClosedBeforeFill() throws Exception
{
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.start();
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.start();
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
SSLEngine sslEngine = sslContextFactory.newSSLEngine();
sslEngine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(byteBufferPool, threadPool, endPoint, sslEngine);
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
sslEndPoint.setConnection(new AbstractConnection(sslEndPoint, threadPool)
{
@Override
public void onFillable()
{
}
});
// There are no bytes in the endPoint, so we fill zero.
// However, this will trigger state changes in SSLEngine
// that will later cause it to throw ISE("Internal error").
sslEndPoint.fill(BufferUtil.EMPTY_BUFFER);
// Close the connection before filling.
sslEndPoint.shutdownOutput();
// Put some bytes in the endPoint to trigger
// the required state changes in SSLEngine.
byte[] bytes = new byte[]{0x16, 0x03, 0x03, 0x00, 0x00};
endPoint.addInput(ByteBuffer.wrap(bytes));
// This attempt to read, if not guarded, throws ISE("Internal error").
// We want SSLHandshakeException to be thrown instead, because it is
// handled better (it is an IOException) by the Connection code that
// reads from the EndPoint.
try
{
sslEndPoint.fill(BufferUtil.EMPTY_BUFFER);
Assert.fail();
}
catch (SSLHandshakeException x)
{
// Expected.
}
}
}

View File

@ -20,7 +20,7 @@
==== Setting up the Classpath
You will need to place the following Jetty jar files onto the classpath of your application.
You can obtain them from the http://download.eclipse.org/jetty/stable-9/dist/[Jetty distribution], or the http://central.maven.org/maven2/org/eclipse/jetty/jetty-annotations[Maven repository]:
You can obtain them from the https://www.eclipse.org/jetty/download.html[Jetty distribution], or the http://central.maven.org/maven2/org/eclipse/jetty/jetty-annotations[Maven repository]:
....
jetty-plus.jar

View File

@ -17,7 +17,8 @@
[[startup-base-and-home]]
=== Managing Jetty Base and Jetty Home
Starting with Jetty 9.1, it is possible to maintain a separation between the binary installation of the standalone Jetty (known as `${jetty.home}`), and the customizations for your specific environment (known as `${jetty.base}`).
Instead of managing multiple Jetty implementations out of several different distribution locations, it is possible to maintain a separation between the binary installation of the standalone Jetty (known as `${jetty.home}`), and the customizations for your specific environment(s) (known as `${jetty.base}`).
There should always only be one Jetty Home, but there can be multiple Jetty Base directories that references.
Jetty Base::
* Also known as the `${jetty.base}` property.
@ -26,6 +27,14 @@ Jetty Home::
* Also known as the `${jetty.home}` property.
* This is the location for the Jetty distribution binaries, default XML IoC configurations, and default module definitions.
____
[IMPORTANT]
Jetty Home should always be treated as a standard of truth.
All configuration modifications, changes and additions should be made in the appropriate Jetty Base directory.
____
[[base-vs-home-resolution]]
Potential configuration is resolved from these 2 directory locations.
Check Jetty Base::
@ -241,8 +250,8 @@ jetty.dump.stop=false
--module=annotations
....
The `${jetty.base}/start.ini` is the main startup configuration entry point for Jetty.
In this example you will see that we are enabling a few modules for Jetty, specifying some properties, and also referencing some Jetty IoC XML files (namely the `etc/demo-rewrite-rules.xml` and `etc/test-realm.xml` files)
In this example, `${jetty.base}/start.ini` is the main startup configuration entry point for Jetty.
You will see that we are enabling a few modules for Jetty, specifying some properties, and also referencing some Jetty IoC XML files (namely the `etc/demo-rewrite-rules.xml` and `etc/test-realm.xml` files)
When Jetty's `start.jar` resolves the entries in the `start.ini`, it will follow the link:#base-vs-home-resolution[resolution rules above].

View File

@ -18,9 +18,9 @@
=== Using the $\{jetty.home} and $\{jetty.base} Concepts to Configure
Security
Jetty 9.1 introduced `${jetty.base}` and `${jetty.home}`.
Jetty implementations are structured around the idea of `${jetty.base}` and `${jetty.home}` directories.
* `${jetty.home}` is the directory location for the jetty distribution (the binaries).
* `${jetty.home}` is the directory location for the Jetty distribution (the binaries) should not be modified.
* `${jetty.base}` is the directory location for your customizations to the distribution.
This separation:

View File

@ -35,7 +35,7 @@ Its purpose is to provide almost the same functionality as the Jetty plugin for
To set up your project for Ant to run Jetty, you need a Jetty distribution and the jetty-ant Jar:
1. http://download.eclipse.org/jetty/[Download] a Jetty distribution and unpack it in the local filesystem.
1. https://www.eclipse.org/jetty/download.html[Download] a Jetty distribution and unpack it in the local filesystem.
2. http://central.maven.org/maven2/org/eclipse/jetty/jetty-ant/[Get] the jetty-ant Jar.
3. Make a directory in your project called `jetty-lib/`.
4. Copy all of the Jars in your Jetty distribution's `lib` directory, and all its subdirectories, into your new `jetty-lib` dir.

View File

@ -22,7 +22,7 @@
The standalone Jetty distribution is available for download from the Eclipse Foundation:
____
*Jetty*
http://download.eclipse.org/jetty
https://www.eclipse.org/jetty/download.html
____
It is available in both zip and gzip formats; download the one most appropriate for your system.

View File

@ -105,6 +105,12 @@ jetty.home::
jetty.base::
The property that defines the location of a specific instance of a jetty server, its configuration, logs and web applications (typically start.ini, start.d, logs and webapps)
____
[IMPORTANT]
Your Jetty Home directory should be treated as a standard of truth and remain unmodified or changed.
Changes or additions to your configuration should take place in the Jetty Base directory.
____
The `jetty.home` and `jetty.base` properties may be explicitly set on the command line, or they can be inferred from the environment if used with commands like:
[source, screen, subs="{sub-order}"]

View File

@ -11,7 +11,7 @@
<packaging>pom</packaging>
<properties>
<assembly-directory>${basedir}/target/home</assembly-directory>
<assembly-directory>${basedir}/target/jetty-home</assembly-directory>
<jetty-setuid-version>1.0.3</jetty-setuid-version>
</properties>
@ -340,6 +340,24 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>set jetty.sh</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<chmod dir="${assembly-directory}/bin" perm="755" includes="**/*.sh" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>

View File

@ -274,7 +274,8 @@ fi
#####################################################
if [ -z "$JETTY_RUN" ]
then
JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_BASE /tmp)
JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_BASE /tmp)/jetty
[ -d "$JETTY_RUN" ] || mkdir $JETTY_RUN
fi
#####################################################
@ -433,7 +434,7 @@ case "$ACTION" in
CH_USER="-c$JETTY_USER"
fi
start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_BASE" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" start-log-file="$JETTY_LOGS/start.log"
start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_BASE" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" start-log-file="$JETTY_RUN/start.log"
else
@ -455,7 +456,7 @@ case "$ACTION" in
chown "$JETTY_USER" "$JETTY_PID"
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
su - "$JETTY_USER" $SU_SHELL -c "
exec ${RUN_CMD[*]} start-log-file="$JETTY_LOGS/start.log" > /dev/null &
exec ${RUN_CMD[*]} start-log-file="$JETTY_RUN/start.log" > /dev/null &
disown \$!
echo \$! > '$JETTY_PID'"
else

View File

@ -28,8 +28,6 @@ import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
@ -133,6 +131,7 @@ public class HttpParser
CHUNK_SIZE,
CHUNK_PARAMS,
CHUNK,
CHUNK_TRAILER,
CHUNK_END,
END,
CLOSE, // The associated stream/endpoint should be closed
@ -682,7 +681,7 @@ public class HttpParser
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("URI is too large >"+_maxHeaderBytes);
throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
throw new BadMessageException(HttpStatus.URI_TOO_LONG_414);
}
_uri.append(array,p-1,len+1);
buffer.position(i-buffer.arrayOffset());
@ -1383,6 +1382,7 @@ public class HttpParser
break;
case EOF_CONTENT:
case CHUNK_END:
setState(State.CLOSED);
return _handler.messageComplete();
@ -1391,6 +1391,7 @@ public class HttpParser
case CHUNK_SIZE:
case CHUNK_PARAMS:
case CHUNK:
case CHUNK_TRAILER:
setState(State.CLOSED);
_handler.earlyEOF();
break;
@ -1592,7 +1593,6 @@ public class HttpParser
case CHUNK_END:
{
// TODO handle chunk trailer
ch=next(buffer);
if (ch==0)
break;
@ -1601,7 +1601,19 @@ public class HttpParser
setState(State.END);
return _handler.messageComplete();
}
throw new IllegalCharacterException(_state,ch,buffer);
setState(State.CHUNK_TRAILER);
break;
}
case CHUNK_TRAILER:
{
// TODO handle chunk trailer values
ch=next(buffer);
if (ch==0)
break;
if (ch == HttpTokens.LINE_FEED)
setState(State.CHUNK_END);
break;
}
case CLOSED:

View File

@ -802,6 +802,101 @@ public class HttpParserTest
Assert.assertTrue(_messageCompleted);
}
@Test
public void testChunkParseTrailer() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"GET /chunk HTTP/1.0\r\n"
+ "Header1: value1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "a;\r\n"
+ "0123456789\r\n"
+ "1a\r\n"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"
+ "0\r\n"
+ "Trailer: value\r\n"
+ "\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
Assert.assertEquals("GET", _methodOrVersion);
Assert.assertEquals("/chunk", _uriOrStatus);
Assert.assertEquals("HTTP/1.0", _versionOrReason);
Assert.assertEquals(1, _headers);
Assert.assertEquals("Header1", _hdr[0]);
Assert.assertEquals("value1", _val[0]);
Assert.assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
Assert.assertTrue(_headerCompleted);
Assert.assertTrue(_messageCompleted);
}
@Test
public void testChunkParseBadTrailer() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"GET /chunk HTTP/1.0\r\n"
+ "Header1: value1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "a;\r\n"
+ "0123456789\r\n"
+ "1a\r\n"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"
+ "0\r\n"
+ "Trailer: value");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
parser.atEOF();
parser.parseNext(BufferUtil.EMPTY_BUFFER);
Assert.assertEquals("GET", _methodOrVersion);
Assert.assertEquals("/chunk", _uriOrStatus);
Assert.assertEquals("HTTP/1.0", _versionOrReason);
Assert.assertEquals(1, _headers);
Assert.assertEquals("Header1", _hdr[0]);
Assert.assertEquals("value1", _val[0]);
Assert.assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
Assert.assertTrue(_headerCompleted);
Assert.assertTrue(_early);
}
@Test
public void testChunkParseNoTrailer() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"GET /chunk HTTP/1.0\r\n"
+ "Header1: value1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "a;\r\n"
+ "0123456789\r\n"
+ "1a\r\n"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"
+ "0\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
parser.atEOF();
parser.parseNext(BufferUtil.EMPTY_BUFFER);
Assert.assertEquals("GET", _methodOrVersion);
Assert.assertEquals("/chunk", _uriOrStatus);
Assert.assertEquals("HTTP/1.0", _versionOrReason);
Assert.assertEquals(1, _headers);
Assert.assertEquals("Header1", _hdr[0]);
Assert.assertEquals("value1", _val[0]);
Assert.assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
Assert.assertTrue(_headerCompleted);
Assert.assertTrue(_messageCompleted);
}
@Test
public void testStartEOF() throws Exception
{

View File

@ -33,9 +33,12 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.api.Session;
@ -54,12 +57,26 @@ import org.junit.Test;
public class PushCacheFilterTest extends AbstractTest
{
private String contextPath = "/push";
@Override
protected void customizeContext(ServletContextHandler context)
{
context.setContextPath(contextPath);
context.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
}
@Override
protected MetaData.Request newRequest(String method, String pathInfo, HttpFields fields)
{
return new MetaData.Request(method, HttpScheme.HTTP, new HostPortHttpField("localhost:" + connector.getLocalPort()), contextPath + servletPath + pathInfo, HttpVersion.HTTP_2, fields);
}
private String newURI(String pathInfo)
{
return "http://localhost:" + connector.getLocalPort() + contextPath + servletPath + pathInfo;
}
@Test
public void testPush() throws Exception
{
@ -83,7 +100,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String referrerURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String referrerURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
@ -115,23 +132,16 @@ public class PushCacheFilterTest extends AbstractTest
// Request again the primary resource, we should get the secondary resource pushed.
primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
final CountDownLatch pushLatch = new CountDownLatch(1);
final CountDownLatch primaryResponseLatch = new CountDownLatch(2);
final CountDownLatch pushLatch = new CountDownLatch(2);
session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
public void onHeaders(Stream stream, HeadersFrame frame)
{
return new Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
pushLatch.countDown();
}
};
MetaData.Response response = (MetaData.Response)frame.getMetaData();
if (response.getStatus() == HttpStatus.OK_200)
primaryResponseLatch.countDown();
}
@Override
@ -141,6 +151,29 @@ public class PushCacheFilterTest extends AbstractTest
if (frame.isEndStream())
primaryResponseLatch.countDown();
}
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
{
return new Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
if (response.getStatus() == HttpStatus.OK_200)
pushLatch.countDown();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
pushLatch.countDown();
}
};
}
});
Assert.assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
@ -257,7 +290,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String primaryURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
@ -360,7 +393,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String primaryURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
@ -451,7 +484,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary, secondary and tertiary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String primaryURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(2);
@ -464,7 +497,7 @@ public class PushCacheFilterTest extends AbstractTest
if (frame.isEndStream())
{
// Request for the secondary resources.
String secondaryURI1 = "http://localhost:" + connector.getLocalPort() + servletPath + secondaryResource1;
String secondaryURI1 = newURI(secondaryResource1);
HttpFields secondaryFields1 = new HttpFields();
secondaryFields1.put(HttpHeader.REFERER, primaryURI);
MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1);
@ -636,7 +669,7 @@ public class PushCacheFilterTest extends AbstractTest
}
}
});
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String primaryURI = newURI(primaryResource);
final Session session = newClient(new Session.Listener.Adapter());
@ -733,7 +766,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String primaryURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
@ -776,7 +809,7 @@ public class PushCacheFilterTest extends AbstractTest
MetaData metaData = frame.getMetaData();
Assert.assertTrue(metaData instanceof MetaData.Request);
MetaData.Request pushedRequest = (MetaData.Request)metaData;
Assert.assertEquals(servletPath + secondaryResource, pushedRequest.getURI().getPathQuery());
Assert.assertEquals(contextPath + servletPath + secondaryResource, pushedRequest.getURI().getPathQuery());
return new Adapter()
{
@Override
@ -826,7 +859,7 @@ public class PushCacheFilterTest extends AbstractTest
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String referrerURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String referrerURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
@ -921,7 +954,7 @@ public class PushCacheFilterTest extends AbstractTest
});
// Request for the primary and secondary resource to build the cache.
final String referrerURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
final String referrerURI = newURI(primaryResource);
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);

View File

@ -606,6 +606,9 @@ public class SslConnection extends AbstractConnection
// Let's try reading some encrypted data... even if we have some already.
int net_filled = getEndPoint().fill(_encryptedInput);
if (net_filled > 0 && !_handshaken && _sslEngine.isOutboundDone())
throw new SSLHandshakeException("Closed during handshake");
decryption: while (true)
{
// Let's unwrap even if we have no net data because in that

View File

@ -15,7 +15,7 @@
<Set name="minGzipSize"><Property name="jetty.gzip.minGzipSize" deprecated="gzip.minGzipSize" default="2048"/></Set>
<Set name="checkGzExists"><Property name="jetty.gzip.checkGzExists" deprecated="gzip.checkGzExists" default="false"/></Set>
<Set name="compressionLevel"><Property name="jetty.gzip.compressionLevel" deprecated="gzip.compressionLevel" default="-1"/></Set>
<Set name="inflateBufferSize"><Property name="jetty.gzip.inflateBufferSize" default="0/></Set>
<Set name="inflateBufferSize"><Property name="jetty.gzip.inflateBufferSize" default="0"/></Set>
<Set name="excludedAgentPatterns">
<Array type="String">

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -151,16 +152,21 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
* lookup the worker name that can be dynamically set by a request
* Customizer.
*
* @param workerName the name of the worker
* @param workerName the name of the worker, if null it is coerced to empty string
*/
public void setWorkerName(String workerName)
{
if (isRunning())
throw new IllegalStateException(getState());
if (workerName == null)
_workerName = "";
else
{
if (workerName.contains("."))
throw new IllegalArgumentException("Name cannot contain '.'");
_workerName=workerName;
}
}
/* ------------------------------------------------------------ */
/**
@ -281,7 +287,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
//add in the id of the node to ensure unique id across cluster
//NOTE this is different to the node suffix which denotes which node the request was received on
if (_workerName!=null)
if (!StringUtil.isBlank(_workerName))
id=_workerName + id;
id = id+Long.toString(COUNTER.getAndIncrement());
@ -417,7 +423,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
@Override
public String getExtendedId(String clusterId, HttpServletRequest request)
{
if (_workerName!=null)
if (!StringUtil.isBlank(_workerName))
{
if (_workerAttr==null)
return clusterId+'.'+_workerName;

View File

@ -605,9 +605,12 @@ public class Session implements SessionHandler.SessionIf
{
checkLocked();
if (_state != State.VALID)
if (_state == State.INVALID)
throw new IllegalStateException("Not valid for write: id="+_sessionData.getId()+" created="+_sessionData.getCreated()+" accessed="+_sessionData.getAccessed()+" lastaccessed="+_sessionData.getLastAccessed()+" maxInactiveMs="+_sessionData.getMaxInactiveMs()+" expiry="+_sessionData.getExpiry());
if (_state == State.INVALIDATING)
return; //in the process of being invalidated, listeners may try to remove attributes
if (!isResident())
throw new IllegalStateException("Not valid for write: id="+_sessionData.getId()+" not resident");
}
@ -626,6 +629,9 @@ public class Session implements SessionHandler.SessionIf
if (_state == State.INVALID)
throw new IllegalStateException("Invalid for read: id="+_sessionData.getId()+" created="+_sessionData.getCreated()+" accessed="+_sessionData.getAccessed()+" lastaccessed="+_sessionData.getLastAccessed()+" maxInactiveMs="+_sessionData.getMaxInactiveMs()+" expiry="+_sessionData.getExpiry());
if (_state == State.INVALIDATING)
return;
if (!isResident())
throw new IllegalStateException("Invalid for read: id="+_sessionData.getId()+" not resident");
}

View File

@ -632,7 +632,9 @@ public class ResponseTest
DefaultSessionCache ss = new DefaultSessionCache(handler);
NullSessionDataStore ds = new NullSessionDataStore();
ss.setSessionDataStore(ds);
handler.setSessionIdManager(new DefaultSessionIdManager(_server));
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(_server);
idMgr.setWorkerName(null);
handler.setSessionIdManager(idMgr);
request.setSessionHandler(handler);
TestSession tsession = new TestSession(handler, "12345");
tsession.setExtendedId(handler.getSessionIdManager().getExtendedId("12345", null));
@ -717,7 +719,9 @@ public class ResponseTest
DefaultSessionCache ss = new DefaultSessionCache(handler);
handler.setSessionCache(ss);
ss.setSessionDataStore(ds);
handler.setSessionIdManager(new DefaultSessionIdManager(_server));
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(_server);
idMgr.setWorkerName(null);
handler.setSessionIdManager(idMgr);
request.setSessionHandler(handler);
request.setSession(new TestSession(handler, "12345"));
handler.setCheckingRemoteSessionIdEncoding(false);

View File

@ -49,7 +49,6 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -161,7 +160,7 @@ public class PushCacheFilter implements Filter
if (LOG.isDebugEnabled())
LOG.debug("{} {} referrer={} conditional={}", request.getMethod(), request.getRequestURI(), referrer, conditional);
String path = URIUtil.addPaths(request.getServletPath(), request.getPathInfo());
String path = request.getRequestURI();
String query = request.getQueryString();
if (query != null)
path += "?" + query;
@ -183,12 +182,11 @@ public class PushCacheFilter implements Filter
String referrerPath = referrerURI.getPath();
if (referrerPath == null)
referrerPath = "/";
if (referrerPath.startsWith(request.getContextPath()))
if (referrerPath.startsWith(request.getContextPath() + "/"))
{
String referrerPathNoContext = referrerPath.substring(request.getContextPath().length());
if (!referrerPathNoContext.equals(path))
if (!referrerPath.equals(path))
{
PrimaryResource primaryResource = _cache.get(referrerPathNoContext);
PrimaryResource primaryResource = _cache.get(referrerPath);
if (primaryResource != null)
{
long primaryTimestamp = primaryResource._timestamp.get();
@ -203,19 +201,19 @@ public class PushCacheFilter implements Filter
if (associated.add(path))
{
if (LOG.isDebugEnabled())
LOG.debug("Associated {} to {}", path, referrerPathNoContext);
LOG.debug("Associated {} to {}", path, referrerPath);
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Not associated {} to {}, exceeded max associations of {}", path, referrerPathNoContext, _maxAssociations);
LOG.debug("Not associated {} to {}, exceeded max associations of {}", path, referrerPath, _maxAssociations);
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Not associated {} to {}, outside associate period of {}ms", path, referrerPathNoContext, _associatePeriod);
LOG.debug("Not associated {} to {}, outside associate period of {}ms", path, referrerPath, _associatePeriod);
}
}
}
@ -223,9 +221,14 @@ public class PushCacheFilter implements Filter
else
{
if (LOG.isDebugEnabled())
LOG.debug("Not associated {} to {}, referring to self", path, referrerPathNoContext);
LOG.debug("Not associated {} to {}, referring to self", path, referrerPath);
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Not associated {} to {}, different context", path, referrerPath);
}
}
}
else

View File

@ -145,7 +145,12 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
// Create Filter
if(isEnabledViaContext(context.getServletContext(), ADD_DYNAMIC_FILTER_KEY, true))
{
WebSocketUpgradeFilter.configureContext(context);
String instanceKey = WebSocketUpgradeFilter.class.getName() + ".SCI";
if(context.getAttribute(instanceKey) == null)
{
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
context.setAttribute(instanceKey, wsuf);
}
}
return jettyContainer;

View File

@ -110,6 +110,7 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
}
private NativeWebSocketConfiguration configuration;
private String instanceKey;
private boolean localConfiguration = false;
private boolean alreadySetToAttribute = false;
@ -343,15 +344,15 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
getFactory().getPolicy().setInputBufferSize(Integer.parseInt(max));
}
String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
if (key == null)
instanceKey = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
if (instanceKey == null)
{
// assume default
key = WebSocketUpgradeFilter.class.getName();
instanceKey = WebSocketUpgradeFilter.class.getName();
}
// Set instance of this filter to context attribute
setToAttribute(config.getServletContext(), key);
setToAttribute(config.getServletContext(), instanceKey);
}
catch (ServletException e)
{

View File

@ -0,0 +1,138 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
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.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class SubProtocolTest
{
@WebSocket
public static class ProtocolEchoSocket
{
private Session session;
private String acceptedProtocol;
@OnWebSocketConnect
public void onConnect(Session session)
{
this.session = session;
this.acceptedProtocol = session.getUpgradeResponse().getAcceptedSubProtocol();
}
@OnWebSocketMessage
public void onMsg(String msg)
{
session.getRemote().sendStringByFuture("acceptedSubprotocol=" + acceptedProtocol);
}
}
public static class ProtocolCreator implements WebSocketCreator
{
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
// Accept first sub-protocol
if (req.getSubProtocols() != null)
{
if (!req.getSubProtocols().isEmpty())
{
String subProtocol = req.getSubProtocols().get(0);
resp.setAcceptedSubProtocol(subProtocol);
}
}
return new ProtocolEchoSocket();
}
}
public static class ProtocolServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
factory.setCreator(new ProtocolCreator());
}
}
private static SimpleServletServer server;
@BeforeClass
public static void startServer() throws Exception
{
server = new SimpleServletServer(new ProtocolServlet());
server.start();
}
@AfterClass
public static void stopServer()
{
server.stop();
}
@Test
public void testSingleProtocol() throws Exception
{
testSubProtocol("echo", "echo");
}
@Test
public void testMultipleProtocols() throws Exception
{
testSubProtocol("chat,info,echo", "chat");
}
private void testSubProtocol(String requestProtocols, String acceptedSubProtocols) throws Exception
{
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
{
client.setTimeout(1, TimeUnit.SECONDS);
client.connect();
client.addHeader("Sec-WebSocket-Protocol: "+ requestProtocols + "\r\n");
client.sendStandardRequest();
client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("showme"));
EventQueue<WebSocketFrame> frames = client.readFrames(1, 30, TimeUnit.SECONDS);
WebSocketFrame tf = frames.poll();
assertThat(ProtocolEchoSocket.class.getSimpleName() + ".onMessage()", tf.getPayloadAsUTF8(), is("acceptedSubprotocol=" + acceptedSubProtocols));
}
}
}

View File

@ -108,6 +108,8 @@ public class BrowserDebugTool implements WebSocketCreator
LOG.debug("User-Agent: {}",ua);
LOG.debug("Sec-WebSocket-Extensions (Request) : {}",rexts);
LOG.debug("Sec-WebSocket-Protocol (Request): {}",req.getHeader("Sec-WebSocket-Protocol"));
LOG.debug("Sec-WebSocket-Protocol (Response): {}",resp.getAcceptedSubProtocol());
req.getExtensions();
return new BrowserSocket(ua,rexts);

View File

@ -111,8 +111,9 @@ public class ServletUpgradeResponse implements UpgradeResponse
{
String name = entry.getKey();
Collection<String> prepend = entry.getValue();
List<String> values = headers.getOrDefault(name,headers.containsKey(name)?null:new ArrayList<>());
List<String> values = headers.getOrDefault(name,new ArrayList<>());
values.addAll(0,prepend);
headers.put(name, values);
}
status = response.getStatus();

View File

@ -25,6 +25,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
@ -46,6 +48,7 @@ import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
@ -59,8 +62,16 @@ import org.junit.Test;
public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
{
protected TestServlet _servlet = new TestServlet();
protected TestServlet _servlet;
protected AbstractTestServer _server1 = null;
protected CountDownLatch _synchronizer;
@Before
public void setUp ()
{
_synchronizer = new CountDownLatch(1);
_servlet = new TestServlet(_synchronizer);
}
/**
@ -156,6 +167,9 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
ContentResponse response = client.GET(url+"?action=forward");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//ensure work has finished on the server side
_synchronizer.await();
//check that the sessions exist persisted
checkSession(_servlet._id, true);
checkSessionByKey (_servlet._id, "0_0_0_0:", true);
@ -204,6 +218,8 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
ContentResponse response = client.GET(url+"?action=forwardinv");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//wait for the request to have finished before checking session
_synchronizer.await(10, TimeUnit.SECONDS);
//check that the session does not exist
checkSession(_servlet._id, false);
@ -220,6 +236,12 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
public static class TestServlet extends HttpServlet
{
public String _id = null;
public CountDownLatch _synchronizer;
public TestServlet (CountDownLatch latch)
{
_synchronizer = latch;
}
@Override
@ -240,6 +262,8 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
if (action.endsWith("inv"))
session.invalidate();
_synchronizer.countDown();
return;
}
@ -247,6 +271,9 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
_id = session.getId();
session.setAttribute("value", new Integer(1));
session.invalidate();
assertNull(request.getSession(false));
assertNotNull(session);
}
}
@ -256,10 +283,11 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
HttpSession session = request.getSession(false);
assertNull(session);
if (session == null) session = request.getSession(true);
// Be sure nothing from contextA is present
Object objectA = session.getAttribute("A");
Object objectA = session.getAttribute("value");
assertTrue(objectA == null);
// Add something, so in contextA we can check if it is visible (it must not).