Merged branch 'jetty-9.4.x' into 'master'.
This commit is contained in:
commit
a51f800054
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@
|
|||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>removeKeystore</id>
|
||||
<id>removeKeystore</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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].
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
----
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}"]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in New Issue