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

@ -74,7 +74,7 @@
</configuration>
</execution>
<execution>
<id>removeKeystore</id>
<id>removeKeystore</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>

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

@ -27,7 +27,7 @@ Its purpose is to provide almost the same functionality as the Jetty plugin for
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ant</artifactId>
</dependency>
----
[[jetty-ant-preparation]]
@ -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.
@ -52,8 +52,8 @@ Begin with an empty `build.xml`:
[source, xml, subs="{sub-order}"]
----
<project name="Jetty-Ant integration test" basedir=".">
</project>
</project>
----
Add a `<taskdef>` that imports all available Jetty tasks:
@ -68,8 +68,8 @@ Add a `<taskdef>` that imports all available Jetty tasks:
<taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />
</project>
</project>
----
Now you are ready to add a new target for running Jetty:
@ -89,7 +89,7 @@ Now you are ready to add a new target for running Jetty:
</target>
</project>
----
This is the minimal configuration you need. You can now start Jetty on the default port of 8080.
@ -133,7 +133,7 @@ ports and connectors:::
</target>
</project>
----
+
____
@ -168,7 +168,7 @@ login services:::
</target>
</project>
----
request log:::
The `requestLog` option allows you to specify a request logger for the Jetty instance.
@ -190,7 +190,7 @@ request log:::
</target>
</project>
----
temporary directory:::
You can configure a directory as a temporary file store for uses such as expanding files and compiling JSPs by supplying the `tempDirectory` option:
@ -211,7 +211,7 @@ temporary directory:::
</target>
</project>
----
other context handlers:::
You may need to configure some other context handlers to run at the same time as your web application.
@ -241,7 +241,7 @@ other context handlers:::
</target>
</project>
----
system properties:::
As a convenience, you can configure system properties by using the `<systemProperties>` element.
@ -266,7 +266,7 @@ system properties:::
</target>
</project>
----
jetty XML file:::
If you have a lot of configuration to apply to the Jetty container, it can be more convenient to put it into a standard Jetty XML configuration file and have the Ant plugin apply it before starting Jetty:
@ -287,7 +287,7 @@ jetty XML file:::
</target>
</project>
----
scanning for changes:::
The most useful mode in which to run the Ant plugin is for it to continue to execute Jetty and automatically restart your web application if any part of it changes (for example, your IDE
@ -311,7 +311,7 @@ scanning for changes:::
</target>
</project>
----
stopping:::
In normal mode (`daemon="false"`), the `<jetty.run>` task runs until you `cntrl-c` it. It may be useful to script both the stop AND the start of Jetty.
@ -341,7 +341,7 @@ stopping:::
</target>
</project>
----
+
To stop jetty via Ant, enter:
@ -375,7 +375,7 @@ execution without pausing ant:::
</target>
</project>
----
==== Deploying a Web Application
@ -403,7 +403,7 @@ The following example deploys a web application that is expanded in the local di
</target>
</project>
----
deploying a WAR file:::
@ -430,7 +430,7 @@ deploying a WAR file:::
</target>
</project>
----
deploying more than one web application:::
@ -458,7 +458,7 @@ deploying more than one web application:::
</target>
</project>
----
===== Configuring the Web Application
@ -491,7 +491,7 @@ Here's an example that specifies the location of the `web.xml` file (equivalent
</target>
</project>
----
Other extra configuration options for the AntWebAppContext include:
@ -528,7 +528,7 @@ extra classes and Jars:::
</target>
</project>
----
context attributes:::
Jetty allows you to set up ServletContext attributes on your web application.
@ -560,7 +560,7 @@ context attributes:::
</target>
</project>
----
`jetty-env.xml` file:::
If you are using features such as link:#configuring_jndi[JNDI] with your web application, you may need to configure a link:#using_jndi[`WEB-INF/jetty-env.xml`] file to define resources. If the structure of your web application project is such that the source of `jetty-env.xml` file resides somewhere other than `WEB-INF`, you can use the `jettyEnvXml` attribute to tell Ant where to find it:
@ -587,7 +587,7 @@ context attributes:::
</target>
</project>
----
context XML file:::
You may prefer or even require to do some advanced configuration of your web application outside of the Ant build file.
@ -616,5 +616,5 @@ project name="Jetty-Ant integration test" basedir=".">
</target>
</project>
----

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,14 +1382,16 @@ public class HttpParser
break;
case EOF_CONTENT:
case CHUNK_END:
setState(State.CLOSED);
return _handler.messageComplete();
case CONTENT:
case CHUNKED_CONTENT:
case CHUNK_SIZE:
case CHUNK_PARAMS:
case CHUNK:
case CONTENT:
case CHUNKED_CONTENT:
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,15 +152,20 @@ 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.contains("."))
throw new IllegalArgumentException("Name cannot contain '.'");
_workerName=workerName;
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;
@ -472,7 +478,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
for (SessionHandler manager:getSessionHandlers())
{
manager.invalidate(id);
}
}
}

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);
@ -203,8 +217,10 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
//make a request to set up a session on the server
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
@ -238,7 +260,9 @@ public abstract class AbstractCreateAndInvalidateTest extends AbstractTestBase
dispatcherB.forward(request, httpServletResponse);
if (action.endsWith("inv"))
session.invalidate();
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).