Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Greg Wilkins 2017-03-30 17:20:22 +11:00
commit dd084b4e74
126 changed files with 3761 additions and 1214 deletions

2
Jenkinsfile vendored
View File

@ -51,7 +51,7 @@ node {
{
stage('Test') {
withEnv(mvnEnv) {
timeout(time: 60, unit: 'MINUTES') {
timeout(time: 90, unit: 'MINUTES') {
// Run test phase / ignore test failures
sh "mvn -B install -Dmaven.test.failure.ignore=true"
// Report failures in the jenkins UI

View File

@ -1,12 +1,74 @@
jetty-10.0.0-SNAPSHOT
jetty-9.4.3-SNAPSHOT
jetty-9.4.4-SNAPSHOT
jetty-9.4.3.v20170317 - 17 March 2017
+ 329 Javadoc for HttpTester and ServletTester needs to reference limited HTTP
version scope
+ 609 websocket ClientCloseTest testServerNoCloseHandshake is failing
+ 856 Add server/port and auth configuration for mongo sessions
+ 1015 Ensure jetty-distribution excludes git / temp files
+ 1049 test-jetty-osgi test exits/crashes the surefire forked JVM
+ 1184 IllegalStateException for HEAD requests responded with 404
+ 1340 PushCacheFilter question
+ 1351 StringIndexOutOfBoundsException thrown on incomplete Accept-Language
header
+ 1353 A Large ClasspathPattern results in infinite loop
+ 1357 RolloverFileOutputStream: No rollout performed at midnight
+ 1363 HttpInput.read deadlock (async mode)
+ 1374 When `Server.start` fails, beans ought to be stopped
+ 1375 Support pushed resources in HTTP client
+ 1378 Slow TLS clients may hang the server
+ 1379 Misleading javadoc for initialization of SessionIdPathParameterName
+ 1383 javadoc build on JDK 8u121 fails due to scripts
+ 1384 Expose StatisticsServlet to webapp
+ 1387 Windows and paxexam failure due to "renaming bundle"
+ 1389 Update to gcloud datastore-0.9.4-beta
+ 1390 HashLoginService and "this.web-inf.url" property are incompatible
+ 1394 Default OS Locale/Encoding/Charset can cause test failures
+ 1396 Set-Cookie produced by Jetty is invalid for RFC6265 and Chrome
+ 1398 Ensure all SessionDataStores store lastsaved time
+ 1399 SlowClientTest is failing on CI
+ 1401 HttpOutput.recycle() does not clear the write listener
+ 1402 Move RFC syntax validation to jetty-http Syntax class
+ 1403 Move new CookieCompliance class to jetty-http
+ 1405 Cookie name cannot be blank or null
jetty-9.3.17.v20170317 - 17 March 2017
+ 329 Javadoc for HttpTester and ServletTester needs to reference limited HTTP
version scope
+ 609 websocket ClientCloseTest testServerNoCloseHandshake is failing
+ 1015 Ensure jetty-distribution excludes git / temp files
+ 1047 ReadPendingException and then thread death
+ 1049 test-jetty-osgi test exits/crashes the surefire forked JVM
+ 1282 ByteArrayEndPointTest.testIdle() failure
+ 1296 Introduce HTTP parser "content complete" event
+ 1326 Jetty shutdown command got NullPointerException (http2 module added to
start)
+ 1328 Response.setBufferSize(int) ISE should be more clear on reason
+ 1340 PushCacheFilter question
+ 1342 Improve ByteBufferPool scalability
+ 1351 StringIndexOutOfBoundsException thrown on incomplete Accept-Language
header
+ 1357 RolloverFileOutputStream: No rollout performed at midnight
+ 1374 When `Server.start` fails, beans ought to be stopped
+ 1375 Support pushed resources in HTTP client
+ 1378 Slow TLS clients may hang the server
+ 1383 javadoc build on JDK 8u121 fails due to scripts
+ 1384 Expose StatisticsServlet to webapp
+ 1387 Windows and paxexam failure due to "renaming bundle"
+ 1389 Update to gcloud datastore-0.9.4-beta
+ 1390 HashLoginService and "this.web-inf.url" property are incompatible
+ 1394 Default OS Locale/Encoding/Charset can cause test failures
+ 1396 Set-Cookie produced by Jetty is invalid for RFC6265 and Chrome
+ 1399 SlowClientTest is failing on CI
+ 1401 HttpOutput.recycle() does not clear the write listener
jetty-9.4.2.v20170220 - 20 February 2017
+ 612 Support HTTP Trailer
+ 1047 ReadPendingException and then thread death
+ 1150 Rationalize the session tests
+ 1226 Undefined JETTY_LOGS breaks jetty.sh
+ 1226 Undefined JETTY_LOGS breaks jetty.sh
+ 1282 ByteArrayEndPointTest.testIdle() failure
+ 1284 IllegalStateException updating session inactive interval
+ 1290 http2-hpack not visible in OSGi
@ -82,19 +144,19 @@ jetty-9.4.1.v20170120 - 20 January 2017
with WEB-INF/lib/jetty-http.jar present
+ 1234 onBadMessage called from with handled message
+ 1239 Charset=unknown produces Exception during testing
+ 1242
+ 1242
org.eclipse.jetty.client.HttpRequestAbortTest.testAbortOnCommitWithContent[1]()
results in EofException
+ 1243
+ 1243
org.eclipse.jetty.proxy.ProxyServletFailureTest.testServerException[0]()
results in ServletException
+ 1244
+ 1244
ProxyServletFailureTest.testProxyRequestStallsContentServerIdlesTimeout()
has TimeoutException visible
+ 1248
+ 1248
org.eclipse.jetty.http2.client.StreamResetTest.testServerExceptionConsumesQueuedData
results in visible Stacktrace
+ 1252
+ 1252
HttpClientStreamTest.testInputStreamContentProviderThrowingWhileReading[transport:
HTTPS]() results in Early EOF
+ 1254 9.4.x Server resource handler welcome files forwarding not working
@ -208,6 +270,13 @@ jetty-9.3.15.v20161220 - 20 December 2016
+ 1186 Where can i find SocketConnector .java and
BlockingChannelConnector.java etc?
jetty-9.2.21.v20170120 - 20 January 2017
+ 592 Support no-value Host header in HttpParser
+ 1229 ClassLoader constraint issue when using NativeWebSocketConfiguration
with WEB-INF/lib/jetty-http.jar present
+ 1267 Request.getRemoteUser can throw undeclared IllegalStateException via
DeferredAuthentication & FormAuthenticator
jetty-9.4.0.RC3 - 05 December 2016
+ 1051 NCSARequestLog/RolloverFileOutputStream does not roll day after DST
ends

View File

@ -58,6 +58,7 @@ public class ManyServletContexts
other.addServlet(new ServletHolder(new HelloServlet("YO!")), "*.yo");
server.start();
server.dumpStdErr();
server.join();
}
}

View File

@ -22,7 +22,6 @@ import java.io.File;
import java.lang.management.ManagementFactory;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.webapp.WebAppContext;
@ -64,6 +63,8 @@ public class OneWebApp
server.dumpStdErr();
server.dumpStdErr();
// The use of server.join() the will make the current thread join and
// wait until the server is done executing.
// See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client;
import static org.hamcrest.CoreMatchers.instanceOf;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -47,10 +49,15 @@ import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class HttpClientURITest extends AbstractHttpClientServerTest
{
@Rule
public ExpectedException expectedException = ExpectedException.none();
public HttpClientURITest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
@ -74,18 +81,20 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
Assert.assertEquals(HttpStatus.OK_200, request.send().getStatus());
}
@Test(expected = IllegalArgumentException.class)
@Test
public void testIDNHost() throws Exception
{
startClient();
expectedException.expect(IllegalArgumentException.class);
client.newRequest(scheme + "://пример.рф"); // example.com-like host in IDN domain
}
@Test(expected = IllegalArgumentException.class)
@Test
public void testIDNRedirect() throws Exception
{
// Internationalized Domain Name.
String exampleHost = scheme + "://пример.рф";
// String exampleHost = scheme + "://пример.рф";
String exampleHost = scheme + "://\uD0BF\uD180\uD0B8\uD0BC\uD0B5\uD180.\uD180\uD184";
String incorrectlyDecoded = new String(exampleHost.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
// Simple server that only parses clear-text HTTP/1.1.
@ -104,19 +113,12 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
HttpField location = response.getHeaders().getField(HttpHeader.LOCATION);
Assert.assertEquals(incorrectlyDecoded, location.getValue());
try
{
client.newRequest("localhost", server.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.followRedirects(true)
.send();
}
catch (ExecutionException x)
{
Throwable cause = x.getCause();
if (cause instanceof IllegalArgumentException)
throw (IllegalArgumentException)cause;
}
expectedException.expect(ExecutionException.class);
expectedException.expectCause(instanceOf(IllegalArgumentException.class));
client.newRequest("localhost", server.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.followRedirects(true)
.send();
}
finally
{

View File

@ -14,6 +14,36 @@
<excludes>
<exclude>**/META-INF/**</exclude>
<exclude>*-config.jar</exclude>
<!-- common OS detritus -->
<exclude>**/.DS_Store</exclude>
<exclude>**/Thumbs.db</exclude>
<exclude>**/desktop.ini</exclude>
<!-- common temp files -->
<exclude>**/*~</exclude>
<exclude>**/*.bak</exclude>
<exclude>**/*.backup</exclude>
<exclude>**/*.old</exclude>
<exclude>**/*.swp</exclude>
<exclude>**/*.debug</exclude>
<exclude>**/*.dump</exclude>
<exclude>**/*.log</exclude>
<exclude>**/~*</exclude>
<!-- common git/scm files -->
<exclude>**/*.orig</exclude>
<exclude>**/*.diff</exclude>
<exclude>**/*.patch</exclude>
<exclude>**/.gitignore</exclude>
<!-- various editor files -->
<exclude>**/*.iml</exclude>
<exclude>**/*.ipr</exclude>
<exclude>**/*.iws</exclude>
<exclude>**/*.idea</exclude>
<exclude>**/.classpath</exclude>
<exclude>**/.project</exclude>
<exclude>**/.settings</exclude>
<!-- maven dust -->
<exclude>**/*.versionsBackup</exclude>
<exclude>**/*.releaseBackup</exclude>
</excludes>
</fileSet>
</fileSets>

View File

@ -12,6 +12,36 @@
</includes>
<excludes>
<exclude>**/target/**</exclude>
<!-- common OS detritus -->
<exclude>**/.DS_Store</exclude>
<exclude>**/Thumbs.db</exclude>
<exclude>**/desktop.ini</exclude>
<!-- common temp files -->
<exclude>**/*~</exclude>
<exclude>**/*.bak</exclude>
<exclude>**/*.backup</exclude>
<exclude>**/*.old</exclude>
<exclude>**/*.swp</exclude>
<exclude>**/*.debug</exclude>
<exclude>**/*.dump</exclude>
<exclude>**/*.log</exclude>
<exclude>**/~*</exclude>
<!-- common git/scm files -->
<exclude>**/*.orig</exclude>
<exclude>**/*.diff</exclude>
<exclude>**/*.patch</exclude>
<exclude>**/.gitignore</exclude>
<!-- various editor files -->
<exclude>**/*.iml</exclude>
<exclude>**/*.ipr</exclude>
<exclude>**/*.iws</exclude>
<exclude>**/*.idea</exclude>
<exclude>**/.classpath</exclude>
<exclude>**/.project</exclude>
<exclude>**/.settings</exclude>
<!-- maven dust -->
<exclude>**/*.versionsBackup</exclude>
<exclude>**/*.releaseBackup</exclude>
</excludes>
</fileSet>
</fileSets>

View File

@ -51,7 +51,7 @@ INFO: resources enabled in ${jetty.base}/start.ini
The replacement `logging.mod` performs a number of tasks.
. `mybase` is a `${jetty.base}` directory
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/ `and becomes the `${jetty.home}` directory for this demonstration.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.

View File

@ -81,7 +81,7 @@ INFO: resources enabled in ${jetty.base}/start.ini
The replacement `logging.mod` performs a number of tasks.
. `mybase` is a `${jetty.base}` directory.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/ `and becomes the `${jetty.home}` directory for this demonstration.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by mybase only.
. The `start.jar --add-to-start=logging,webapp-logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. Several entries are added to the `${jetty.base}/start.ini` configuration.

View File

@ -47,7 +47,7 @@ DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master
The replacement `logging.mod` performs a number of tasks.
. `mybase` is a `${jetty.base}` directory.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/ `and becomes the `${jetty.home}` directory for this demonstration.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.

View File

@ -70,7 +70,7 @@ Logging API::
Slf4j Binding JAR::
* Special JARs, created and maintained by the Slf4j project, that pretend to be the various Logging API implementation classes, but instead just route that Logging API's events to Slf4j to handle.
* There MAY be multiple Slf4j binding JARs present on the classpath at the same time.
* For a single logging API, if you choose to use the Slf4j binding JAR, then you MUST NOT include the SLf4j adapter JAR or underlying logging framework in the classpath as well.
Slf4j Adapter Jar::
* These JARs are created and maintained by the Slf4j project and route Slf4j logging events to a specific underlying logging framework.
@ -118,7 +118,7 @@ INFO: resources enabled in ${jetty.base}/start.ini
The replacement `logging.mod` performs a number of tasks.
. `mybase` is a `${jetty.base}` directory.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/ `and becomes the `${jetty.home}` directory for this demonstration.
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.

View File

@ -26,11 +26,11 @@ $ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-file
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-file initialised in ${jetty.base}/start.d/session-store-file.ini
MKDIR: ${jetty.base}/sessions
INFO : Base directory was modified
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-file initialized in ${jetty.base}/start.d/session-store-file.ini
MKDIR : ${jetty.base}/sessions
INFO : Base directory was modified
----
//TODO - Callout default Session file location - note it is configurable

View File

@ -39,12 +39,14 @@ The following 1 module(s):
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-infinispan-remote initialised in ${jetty.base}/start.d/session-store-infinispan-remote.ini
DOWNLOAD: https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-infinispan/src/main/infinispan-resources/hotrod-client.properties?id=master to ${jetty.base}/resources/hotrod-client.properties
DOWNLOAD: http://central.maven.org/maven2/org/infinispan/infinispan-remote/7.1.1.Final/infinispan-remote-7.1.1.Final.jar to ${jetty.base}/lib/infinispan/infinispan-remote-7.1.1.Final.jar
INFO : Base directory was modified
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-infinispan-remote initialized in ${jetty.base}/start.d/session-store-infinispan-remote.ini
MKDIR : ${jetty.base}/lib/infinispan
DOWNLD: http://central.maven.org/maven2/org/infinispan/infinispan-remote/7.1.1.Final/infinispan-remote-7.1.1.Final.jar to ${jetty.base}/lib/infinispan/infinispan-remote-7.1.1.Final.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/session-store-infinispan-remote/resources/hotrod-client.properties to ${jetty.base}/resources/hotrod-client.properties
INFO : Base directory was modified
----
Doing this enables the remote Infinispan Session module and any dependent modules or files needed for it to run on the server.

View File

@ -26,10 +26,11 @@ $ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-jdbc
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-jdbc initialised in ${jetty.base}/start.d/session-store-jdbc.ini
INFO : Base directory was modified
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : sessions/jdbc/datasource dynamic dependency of session-store-jdbc
INFO : session-store-jdbc initialized in ${jetty.base}/start.d/session-store-jdbc.ini
INFO : Base directory was modified
----
Doing this enables the JDBC Session module and any dependent modules or files needed for it to run on the server.

View File

@ -39,11 +39,13 @@ The following 1 module(s):
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-mongo initialised in ${jetty.base}/start.d/session-store-mongo.ini
DOWNLOAD: http://central.maven.org/maven2/org/mongodb/mongo-java-driver/2.6.1/mongo-java-driver-2.6.1.jar to ${jetty.base}/lib/nosql/mongo-java-driver-2.6.1.jar
INFO : Base directory was modified
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-mongo initialized in ${jetty.base}/start.d/session-store-mongo.ini
INFO : sessions/mongo/address dynamic dependency of session-store-mongo
MKDIR : ${jetty.base}/lib/nosql
DOWNLD: http://central.maven.org/maven2/org/mongodb/mongo-java-driver/2.13.2/mongo-java-driver-2.13.2.jar to ${jetty.base}/lib/nosql/mongo-java-driver-2.13.2.jar
INFO : Base directory was modified
----
Doing this enables the MongoDB Session module and any dependent modules or files needed for it to run on the server.
@ -70,18 +72,37 @@ Opening the `start.d/session-store-mongo.ini` will show a list of all the config
#jetty.session.mongo.dbName=HttpSessions
#jetty.session.mongo.collectionName=jettySessions
#jetty.session.gracePeriod.seconds=3600
connection-type=address
#jetty.session.mongo.host=localhost
#jetty.session.mongo.port=27017
#jetty.session.gracePeriod.seconds=3600
#connection-type=uri
#jetty.session.mongo.connectionString=mongodb://localhost
----
jetty.session.mongo.dbName::
Name of the database in Mongo used to store the Session collection.
jetty.session.mongo.collectionName::
Name of the collection in Mongo used to keep all of the Sessions.
jetty.session.mongo.host::
Host name or address for the remote Mongo instance.
jetty.session.mongo.port::
Port number for the remote Mongo instance.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
connection-type=address::
Used when utilizing a direct connection to the Mongo server.
jetty.session.mongo.host;;
Host name or address for the remote Mongo instance.
jetty.session.mongo.port;;
Port number for the remote Mongo instance.
connection-type=uri::
Used when utilizing MongoURI for secured connections.
jetty.session.mongo.connectionString;;
The string defining the MongoURI value, such as `mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]`.
More information on how to format the MongoURI string can be found in the https://docs.mongodb.com/manual/reference/connection-string/[official documentation for mongo.]
+
____
[NOTE]
You will only use *one* `connection-type` at a time, `address` or `uri`.
If both are utilized in your `session-store-mongo.ini`, only the last `connection-type` configured in the file will be used.
By default, the `connection-type` of `address` is used.
____

View File

@ -31,10 +31,10 @@ The following is the general process by which we operate.
Not everything is specifically relevant since we are at GitHub but the crux of things are detailed there.
The CLA is *critically* important to the process.
The ECA is *critically* important to the process.
[[contributing-cla]]
==== Sign a CLA
[[contributing-eca]]
==== Sign an Eclipse Contributor Agreement (ECA)
The Eclipse Foundation has a strong Intellectual Property policy which tracks contributions in detail to ensure that:
@ -42,12 +42,12 @@ The Eclipse Foundation has a strong Intellectual Property policy which tracks co
2. Does the contributor have the rights to contribute this content to Eclipse?
3. Is the contribution under the projects license(s) (e.g. EPL)
A contributor needs to e-sign a Contributor Licence Agreement (for more explanation see the http://www.eclipse.org/legal/clafaq.php[Eclipse CLA FAQ] ) regardless of how their contribution patch is provided.
A contributor needs to e-sign a Eclipse Contributor Agreement (for more explanation see the http://www.eclipse.org/legal/ecafaq.php[Eclipse ECA FAQ] ) regardless of how their contribution patch is provided.
You can familiarize yourself with the Eclipse wiki page at http://wiki.eclipse.org/Development_Resources/Contributing_via_Git[Contributing via Git].
In order to have a pull request accepted by any Eclipse project you must complete this agreement.
In order to have a pull request accepted by any Eclipse project you *must* complete this agreement.
____
[TIP]
Log into the https://projects.eclipse.org/user/login/sso[Eclipse projects forge] (you will need to create an account with the Eclipse Foundation if you have not already done so); click on "Contributor License Agreement"; and Complete the form.
Log into the https://www.eclipse.org[Eclipse home page] (you will need to create an account with the Eclipse Foundation if you have not already done so), click on "Eclipse ECA", and complete the form.
Be sure to use the _same email address_ when you create any Git commit records.
____
@ -60,13 +60,13 @@ Check out the following link:https://help.github.com/articles/setting-your-email
[[contributing-making-the-commit]]
==== Making the Commit
When making the commit for the pull request it is _vital_ that you "sign-off" on the commit using "git commit -s" option.
When making the commit for the pull request it is _vital_ that you "sign-off" on the commit using `git commit -s` option.
Without this sign-off, your patch cannot be applied to the Jetty repository because it will be rejected.
You can check out the link:https://help.github.com/articles/signing-tags-using-gpg[guide at Github] for more information.
____
[TIP]
One way to think of this is that when you sign the CLA you are indicating that you are free to contribute to eclipse, but that doesn't mean everything you ever do can be contributed.
One way to think of this is that when you sign the ECA you are indicating that you are free to contribute to eclipse, but that doesn't mean everything you ever do can be contributed.
Using the commit signing mechanism indicates that your commit is under the auspices of your agreement.
____
@ -74,9 +74,10 @@ If a pull request is for a particular issue in our repository then the format of
The message should follow the form "Issue #123 <description of the commit>".
When the Jetty project runs releases we have an automated process that scans for commits with this format for inclusion in our VERSION.txt file.
____
[source, screen]
----
> git commit -s -m "Issue #123 resolving the issue by adding widget"
____
----
[[contributing-the-pull-request]]
==== The Pull Request
@ -87,10 +88,11 @@ Pull requests are very much a GitHub process so best link:https://help.github.co
==== Our Policies
We wholeheartedly welcome contributions to Jetty and will do our best to process them in a timely fashion.
While not every contribution will be accepted as is our commitment is to work with interested parties on the things they care about.
With that in mind, short of some simple contributions we can only handle pull requests with actively engaged parties.
While not every contribution will be accepted, our commitment is to work with interested parties on the things they care about.
With that in mind, we can only handle pull requests with actively engaged parties.
We reserve the right to abandon pull requests whose authors do no respond in a timely fashion.
We will generally adhere to the following time frames for contributions.
We will generally adhere to the following time frames for contributions:
* Invalid Pull Requests - 1 week
** These pull requests do not follow the contribution requirements for some reason, be it missing contributor agreement or the wrong email.

View File

@ -11,6 +11,7 @@
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.gcloud.session.GCloudSessionDataStoreFactory">
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="maxRetries"><Property name="jetty.session.gcloud.maxRetries" default="5"/></Set>
<Set name="backoffMs"><Property name="jetty.session.gcloud.backoffMs" default="1000"/></Set>
<Set name="namespace"><Property name="jetty.session.gcloud.namespace" default=""/></Set>

View File

@ -26,6 +26,8 @@ etc/sessions/gcloud/session-store.xml
[ini-template]
## GCloudDatastore Session config
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
#jetty.session.gcloud.maxRetries=5
#jetty.session.gcloud.backoffMs=1000
#jetty.session.gcloud.namespace=

View File

@ -98,6 +98,7 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
public static final String EXPIRY = "expiry";
public static final String MAXINACTIVE = "maxInactive";
public static final String ATTRIBUTES = "attributes";
public static final String LASTSAVED = "lastSaved";
public static final String KIND = "GCloudSession";
protected String _kind = KIND;
@ -107,6 +108,7 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
protected String _accessed = ACCESSED;
protected String _lastAccessed = LASTACCESSED;
protected String _lastNode = LASTNODE;
protected String _lastSaved = LASTSAVED;
protected String _createTime = CREATETIME;
protected String _cookieSetTime = COOKIESETTIME;
protected String _expiry = EXPIRY;
@ -302,6 +304,23 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
_attributes = attributes;
}
/**
* @return the lastSaved
*/
public String getLastSaved()
{
return _lastSaved;
}
/**
* @param lastSaved the lastSaved to set
*/
public void setLastSaved(String lastSaved)
{
checkNotNull(lastSaved);
_lastSaved = lastSaved;
}
/**
* @see java.lang.Object#toString()
*/
@ -529,11 +548,15 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
for (ExpiryInfo item:info)
{
if (StringUtil.isBlank(item.getLastNode()))
{
expired.add(item.getId()); //nobody managing it
}
else
{
if (_context.getWorkerName().equals(item.getLastNode()))
{
expired.add(item.getId()); //we're managing it, we can expire it
}
else
{
if (_lastExpiryCheckTime <= 0)
@ -541,8 +564,7 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
//our first check, just look for sessions that we managed by another node that
//expired at least 3 graceperiods ago
if (item.getExpiry() < (now - (1000L * (3 * _gracePeriodSec))))
expired.add(item.getId());
}
expired.add(item.getId()); }
else
{
//another node was last managing it, only expire it if it expired a graceperiod ago
@ -636,11 +658,12 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
*/
protected Set<ExpiryInfo> queryExpiryByIndex () throws Exception
{
long now = System.currentTimeMillis();
Set<ExpiryInfo> info = new HashSet<>();
Query<ProjectionEntity> query = Query.newProjectionEntityQueryBuilder()
.setKind(_model.getKind())
.setProjection(_model.getId(), _model.getLastNode(), _model.getExpiry())
.setFilter(CompositeFilter.and(PropertyFilter.gt(_model.getExpiry(), 0), PropertyFilter.le(_model.getExpiry(), System.currentTimeMillis())))
.setFilter(CompositeFilter.and(PropertyFilter.gt(_model.getExpiry(), 0), PropertyFilter.le(_model.getExpiry(), now)))
.setLimit(_maxResults)
.build();
@ -746,7 +769,6 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
{
if (LOG.isDebugEnabled()) LOG.debug("Writing session {} to DataStore", data.getId());
Entity entity = entityFromSession(data, makeKey(id, _context));
//attempt the update with exponential back-off
@ -868,6 +890,7 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
.set(_model.getLastNode(),session.getLastNode())
.set(_model.getExpiry(), session.getExpiry())
.set(_model.getMaxInactive(), session.getMaxInactiveMs())
.set(_model.getLastSaved(), session.getLastSaved())
.set(_model.getAttributes(), BlobValue.newBuilder(Blob.copyFrom(baos.toByteArray())).setExcludeFromIndexes(true).build()).build();
@ -902,6 +925,17 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
long createTime = entity.getLong(_model.getCreateTime());
long cookieSet = entity.getLong(_model.getCookieSetTime());
String lastNode = entity.getString(_model.getLastNode());
long lastSaved = 0;
//for compatibility with previously saved sessions, lastSaved may not be present
try
{
lastSaved = entity.getLong(_model.getLastSaved());
}
catch (DatastoreException e)
{
LOG.ignore(e);
}
long expiry = entity.getLong(_model.getExpiry());
long maxInactive = entity.getLong(_model.getMaxInactive());
Blob blob = (Blob) entity.getBlob(_model.getAttributes());
@ -912,6 +946,7 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
session.setVhost(vhost);
session.setCookieSet(cookieSet);
session.setLastNode(lastNode);
session.setLastSaved(lastSaved);
session.setExpiry(expiry);
try (ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(blob.asInputStream()))
{

View File

@ -94,6 +94,7 @@ public class GCloudSessionDataStoreFactory extends AbstractSessionDataStoreFacto
ds.setMaxRetries(getMaxRetries());
ds.setGracePeriodSec(getGracePeriodSec());
ds.setNamespace(_namespace);
ds.setSavePeriodSec(getSavePeriodSec());
return ds;
}

View File

@ -13,7 +13,7 @@
<name>Jetty :: GCloud</name>
<properties>
<gcloud.version>0.9.2-beta</gcloud.version>
<gcloud.version>0.10.0-beta</gcloud.version>
</properties>
<modules>

View File

@ -37,7 +37,7 @@
<extensions>true</extensions>
<configuration>
<instructions>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<!--
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
-->

View File

@ -45,6 +45,11 @@ public class BadMessageException extends RuntimeException
this(400,reason);
}
public BadMessageException(String reason, Throwable cause)
{
this(400, reason, cause);
}
public BadMessageException(int code, String reason)
{
super(code+": "+reason);

View File

@ -0,0 +1,25 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.http;
/**
* The compliance for Cookie handling.
*
*/
public enum CookieCompliance { RFC6265, RFC2965 }

View File

@ -73,7 +73,7 @@ public class HttpStatus
public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
public final static int RANGE_NOT_SATISFIABLE_416 = 416;
public final static int EXPECTATION_FAILED_417 = 417;
public final static int IM_A_TEAPOT_418 = 417;
public final static int IM_A_TEAPOT_418 = 418;
public final static int ENHANCE_YOUR_CALM_420 = 420;
public final static int MISDIRECTED_REQUEST_421 = 421;
public final static int UNPROCESSABLE_ENTITY_422 = 422;
@ -153,7 +153,7 @@ public class HttpStatus
UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
RANGE_NOT_SATISFIABLE(RANGE_NOT_SATISFIABLE_416, "Range Not Satisfiable"),
EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
IM_A_TEAPOT(IM_A_TEAPOT_418, "Im a Teapot"),
IM_A_TEAPOT(IM_A_TEAPOT_418, "I'm a Teapot"),
ENHANCE_YOUR_CALM(ENHANCE_YOUR_CALM_420, "Enhance your Calm"),
MISDIRECTED_REQUEST(MISDIRECTED_REQUEST_421, "Misdirected Request"),
UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),

View File

@ -0,0 +1,142 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.http;
import java.util.Objects;
/**
* Collection of Syntax validation methods.
* <p>
* Use in a similar way as you would {@link java.util.Objects#requireNonNull(Object)}
* </p>
*/
public final class Syntax
{
/**
* Per RFC2616: Section 2.2, a token follows these syntax rules
* <pre>
* token = 1*&lt;any CHAR except CTLs or separators&gt;
* CHAR = &lt;any US-ASCII character (octets 0 - 127)&gt;
* CTL = &lt;any US-ASCII control character
* (octets 0 - 31) and DEL (127)&gt;
* separators = "(" | ")" | "&lt;" | "&gt;" | "@"
* | "," | ";" | ":" | "\" | &lt;"&gt;
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT
* </pre>
*
* @param value the value to test
* @param msg the message to be prefixed if an {@link IllegalArgumentException} is thrown.
* @throws IllegalArgumentException if the value is invalid per spec
*/
public static void requireValidRFC2616Token(String value, String msg)
{
Objects.requireNonNull(msg, "msg cannot be null");
if (value == null)
{
return;
}
int valueLen = value.length();
if (valueLen == 0)
{
return;
}
for (int i = 0; i < valueLen; i++)
{
char c = value.charAt(i);
// 0x00 - 0x1F are low order control characters
// 0x7F is the DEL control character
if ((c <= 0x1F) || (c == 0x7F))
throw new IllegalArgumentException(msg + ": RFC2616 tokens may not contain control characters");
if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@'
|| c == ',' || c == ';' || c == ':' || c == '\\' || c == '"'
|| c == '/' || c == '[' || c == ']' || c == '?' || c == '='
|| c == '{' || c == '}' || c == ' ')
{
throw new IllegalArgumentException(msg + ": RFC2616 tokens may not contain separator character: [" + c + "]");
}
if (c >= 0x80)
throw new IllegalArgumentException(msg + ": RFC2616 tokens characters restricted to US-ASCII: 0x" + Integer.toHexString(c));
}
}
/**
* Per RFC6265, Cookie.value follows these syntax rules
* <pre>
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
* ; US-ASCII characters excluding CTLs,
* ; whitespace DQUOTE, comma, semicolon,
* ; and backslash
* </pre>
*
* @param value the value to test
* @throws IllegalArgumentException if the value is invalid per spec
*/
public static void requireValidRFC6265CookieValue(String value)
{
if (value == null)
{
return;
}
int valueLen = value.length();
if (valueLen == 0)
{
return;
}
int i = 0;
if (value.charAt(0) == '"')
{
// Has starting DQUOTE
if (valueLen <= 1 || (value.charAt(valueLen - 1) != '"'))
{
throw new IllegalArgumentException("RFC6265 Cookie values must have balanced DQUOTES (if used)");
}
// adjust search range to exclude DQUOTES
i++;
valueLen--;
}
for (; i < valueLen; i++)
{
char c = value.charAt(i);
// 0x00 - 0x1F are low order control characters
// 0x7F is the DEL control character
if ((c <= 0x1F) || (c == 0x7F))
throw new IllegalArgumentException("RFC6265 Cookie values may not contain control characters");
if ((c == ' ' /* 0x20 */) ||
(c == '"' /* 0x2C */) ||
(c == ';' /* 0x3B */) ||
(c == '\\' /* 0x5C */))
{
throw new IllegalArgumentException("RFC6265 Cookie values may not contain character: [" + c + "]");
}
if (c >= 0x80)
throw new IllegalArgumentException("RFC6265 Cookie values characters restricted to US-ASCII: 0x" + Integer.toHexString(c));
}
}
}

View File

@ -30,6 +30,7 @@ doc=application/msword
dtd=application/xml-dtd
dvi=application/x-dvi
dxr=application/x-director
eot=application/vnd.ms-fontobject
eps=application/postscript
etx=text/x-setext
exe=application/octet-stream
@ -171,6 +172,8 @@ wml=text/vnd.wap.wml
wmlc=application/vnd.wap.wmlc
wmls=text/vnd.wap.wmlscript
wmlsc=application/vnd.wap.wmlscriptc
woff=application/font-woff
woff2=font/woff2
wrl=model/vrml
wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
xbm=image/x-xbitmap

View File

@ -31,6 +31,14 @@ public class HttpStatusCodeTest
assertNull("Invalid code: 800", HttpStatus.getCode(800));
assertNull("Invalid code: 190", HttpStatus.getCode(190));
}
@Test
public void testImATeapot()
{
assertEquals("I'm a Teapot", HttpStatus.getMessage(418));
assertEquals("Expectation Failed", HttpStatus.getMessage(417));
}
public void testHttpMethod()
{

View File

@ -21,16 +21,15 @@ package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.Utf8Appendable;
import org.junit.Assert;
import org.junit.Test;
public class HttpURITest
@ -117,33 +116,6 @@ public class HttpURITest
assertThat(uri.getHost(),is("foo"));
assertThat(uri.getPath(),is("/bar"));
}
@Test
public void testUnicodeErrors() throws UnsupportedEncodingException
{
String uri="http://server/path?invalid=data%uXXXXhere%u000";
try
{
URLDecoder.decode(uri,"UTF-8");
Assert.assertTrue(false);
}
catch (IllegalArgumentException e)
{
}
HttpURI huri=new HttpURI(uri);
MultiMap<String> params = new MultiMap<>();
huri.decodeQueryTo(params);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
huri=new HttpURI(uri);
params = new MultiMap<>();
huri.decodeQueryTo(params,StandardCharsets.UTF_8);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
}
@Test
public void testExtB() throws Exception

View File

@ -0,0 +1,134 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.http;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test;
public class SyntaxTest
{
@Test
public void testRequireValidRFC2616Token_Good()
{
String tokens[] = {
"name",
"",
null,
"n.a.m.e",
"na-me",
"+name",
"na*me",
"na$me",
"#name"
};
for (String token : tokens)
{
Syntax.requireValidRFC2616Token(token, "Test Based");
// No exception should occur here
}
}
@Test
public void testRequireValidRFC2616Token_Bad()
{
String tokens[] = {
"\"name\"",
"name\t",
"na me",
"name\u0082",
"na\tme",
"na;me",
"{name}",
"[name]",
"\""
};
for (String token : tokens)
{
try
{
Syntax.requireValidRFC2616Token(token, "Test Based");
fail("RFC2616 Token [" + token + "] Should have thrown " + IllegalArgumentException.class.getName());
}
catch (IllegalArgumentException e)
{
assertThat("Testing Bad RFC2616 Token [" + token + "]", e.getMessage(),
allOf(containsString("Test Based"),
containsString("RFC2616")));
}
}
}
@Test
public void testRequireValidRFC6265CookieValue_Good()
{
String values[] = {
"value",
"",
null,
"val=ue",
"val-ue",
"\"value\"",
"val/ue",
"v.a.l.u.e"
};
for (String value : values)
{
Syntax.requireValidRFC6265CookieValue(value);
// No exception should occur here
}
}
@Test
public void testRequireValidRFC6265CookieValue_Bad()
{
String values[] = {
"va\tlue",
"\t",
"value\u0000",
"val\u0082ue",
"va lue",
"va;lue",
"\"value",
"value\"",
"val\\ue",
"val\"ue",
"\""
};
for (String value : values)
{
try
{
Syntax.requireValidRFC6265CookieValue(value);
fail("RFC6265 Cookie Value [" + value + "] Should have thrown " + IllegalArgumentException.class.getName());
}
catch (IllegalArgumentException e)
{
assertThat("Testing Bad RFC6265 Cookie Value [" + value + "]", e.getMessage(), containsString("RFC6265"));
}
}
}
}

View File

@ -740,7 +740,7 @@ public class PushCacheFilterTest extends AbstractTest
{
String name = "foo";
String value = "bar";
final String primaryResource = "/primary.html";
final String primaryResource = "/primary.html?"+name + "=" +value;
final String secondaryResource = "/secondary.html?" + name + "=" + value;
start(new HttpServlet()
{

View File

@ -241,7 +241,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
{
HttpTransportOverHTTP2 transport = new HttpTransportOverHTTP2(connector, this);
transport.setStream(stream);
channel = new ServerHttpChannelOverHTTP2(connector, httpConfig, getEndPoint(), transport);
channel = newServerHttpChannelOverHTTP2(connector, httpConfig, transport);
if (LOG.isDebugEnabled())
LOG.debug("Creating channel {} for {}", channel, this);
}
@ -249,6 +249,11 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
return channel;
}
protected ServerHttpChannelOverHTTP2 newServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration httpConfig, HttpTransportOverHTTP2 transport)
{
return new ServerHttpChannelOverHTTP2(connector, httpConfig, getEndPoint(), transport);
}
private void offerChannel(HttpChannelOverHTTP2 channel)
{
synchronized (this)
@ -299,7 +304,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
return true;
}
private class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2 implements Closeable
protected class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2 implements Closeable
{
public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
{

View File

@ -22,6 +22,7 @@
<Set name="cache"><Ref id="cache"/></Set>
<Set name="infinispanIdleTimeoutSec"><Property name="jetty.session.infinispan.idleTimeout.seconds" default="0" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>

View File

@ -24,6 +24,7 @@
<Set name="cache"><Ref id="remoteCache"/></Set>
<Set name="infinispanIdleTimeoutSec"><Property name="jetty.session.infinispan.idleTimeout.seconds" default="0" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>

View File

@ -25,3 +25,6 @@ Infinispan is an open source project hosted on Github and released under the Apa
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -31,8 +31,4 @@ http://www.apache.org/licenses/LICENSE-2.0.html
#jetty.session.infinispan.remoteCacheName=sessions
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -61,6 +61,7 @@ public class InfinispanSessionDataStoreFactory extends AbstractSessionDataStoreF
store.setGracePeriodSec(getGracePeriodSec());
store.setInfinispanIdleTimeoutSec(getInfinispanIdleTimeoutSec());
store.setCache(getCache());
store.setSavePeriodSec(getSavePeriodSec());
return store;
}

View File

@ -36,7 +36,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.6.1</version>
<version>2.13.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

View File

@ -13,6 +13,7 @@
<Set name="dbName"><Property name="jetty.session.mongo.dbName" default="HttpSessions" /></Set>
<Set name="collectionName"><Property name="jetty.session.mongo.collectionName" default="jettySessions" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="host"><Property name="jetty.session.mongo.host" default="localhost"/></Set>
<Set name="port"><Property name="jetty.session.mongo.port" default="27017"/></Set>
</New>

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- ===================================================================== -->
<!-- Configure a factory for MongoSessionDataStores -->
<!-- ===================================================================== -->
<Call name="addBean">
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory">
<Set name="dbName"><Property name="jetty.session.mongo.dbName" default="HttpSessions" /></Set>
<Set name="collectionName"><Property name="jetty.session.mongo.collectionName" default="jettySessions" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="connectionString"><Property name="jetty.session.mongo.connectionString" default="mongodb://localhost"/></Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -9,9 +9,10 @@ session-store
[depend]
sessions
sessions/mongo/${connection-type}
[files]
maven://org.mongodb/mongo-java-driver/2.6.1|lib/nosql/mongo-java-driver-2.6.1.jar
maven://org.mongodb/mongo-java-driver/2.13.2|lib/nosql/mongo-java-driver-2.13.2.jar
[lib]
lib/jetty-nosql-${jetty.version}.jar
@ -22,13 +23,20 @@ The java driver for the MongoDB document-based database system is hosted on GitH
http://www.mongodb.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[xml]
etc/sessions/mongo/session-store.xml
[ini]
connection-type?=address
[ini-template]
#jetty.session.mongo.dbName=HttpSessions
#jetty.session.mongo.collectionName=jettySessions
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
connection-type=address
#jetty.session.mongo.host=localhost
#jetty.session.mongo.port=27017
#jetty.session.gracePeriod.seconds=3600
#connection-type=uri
#jetty.session.mongo.connectionString=mongodb://localhost

View File

@ -0,0 +1,6 @@
[description]
Server/port address connections for session storage
[xml]
etc/sessions/mongo/session-store-by-address.xml

View File

@ -0,0 +1,6 @@
[description]
MongoURI connections for session storage
[xml]
etc/sessions/mongo/session-store-by-uri.xml

View File

@ -117,6 +117,12 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
*/
public final static String __VERSION = __METADATA + ".version";
public final static String __LASTSAVED = __METADATA + ".lastSaved";
public final static String __LASTNODE = __METADATA + ".lastNode";
/**
* Last access time of session
*/
@ -202,14 +208,15 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
if (valid == null || !valid)
return;
Object version = getNestedValue(sessionDocument, getContextSubfield(__VERSION));
Long lastSaved = (Long)getNestedValue(sessionDocument, getContextSubfield(__LASTSAVED));
String lastNode = (String)getNestedValue(sessionDocument, getContextSubfield(__LASTNODE));
Long created = (Long)sessionDocument.get(__CREATED);
Long accessed = (Long)sessionDocument.get(__ACCESSED);
Long maxInactive = (Long)sessionDocument.get(__MAX_IDLE);
Long expiry = (Long)sessionDocument.get(__EXPIRY);
Long expiry = (Long)sessionDocument.get(__EXPIRY);
NoSqlSessionData data = null;
// get the session for the context
@ -228,6 +235,8 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
data.setExpiry(expiry);
data.setContextPath(_context.getCanonicalContextPath());
data.setVhost(_context.getVhost());
data.setLastSaved(lastSaved);
data.setLastNode(lastNode);
HashMap<String, Object> attributes = new HashMap<>();
for (String name : sessionSubDocumentForContext.keySet())
@ -427,7 +436,7 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
*/
@Override
public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
{
{
NoSqlSessionData nsqd = (NoSqlSessionData)data;
// Form query for upsert
@ -449,12 +458,16 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
sets.put(__CREATED,nsqd.getCreated());
sets.put(__VALID,true);
sets.put(getContextSubfield(__VERSION),version);
sets.put(getContextSubfield(__LASTSAVED), data.getLastSaved());
sets.put(getContextSubfield(__LASTNODE), data.getLastNode());
sets.put(__MAX_IDLE, nsqd.getMaxInactiveMs());
sets.put(__EXPIRY, nsqd.getExpiry());
nsqd.setVersion(version);
}
else
{
sets.put(getContextSubfield(__LASTSAVED), data.getLastSaved());
sets.put(getContextSubfield(__LASTNODE), data.getLastNode());
version = new Long(((Number)version).longValue() + 1);
nsqd.setVersion(version);
update.put("$inc",_version_1);

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.server.session.SessionDataStore;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoURI;
/**
* MongoSessionDataStoreFactory
@ -37,11 +38,12 @@ import com.mongodb.MongoException;
*/
public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactory
{
String _dbName;
String _dbName;
String _collectionName;
String _host;
String _connectionString;
int _port = -1;
/**
* @return the host
*/
@ -74,10 +76,6 @@ public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactor
_port = port;
}
/**
* @return the dbName
*/
@ -94,6 +92,22 @@ public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactor
_dbName = dbName;
}
/**
* @return the connectionString
*/
public String getConnectionString()
{
return _connectionString;
}
/**
* @param connectionString the connection string to set. This has priority over dbHost and port
*/
public void setConnectionString(String connectionString)
{
_connectionString = connectionString;
}
/**
* @return the collectionName
*/
@ -110,10 +124,10 @@ public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactor
_collectionName = collectionName;
}
/**
* @throws MongoException
* @throws UnknownHostException
/**
* @throws MongoException
* @throws UnknownHostException
* @see org.eclipse.jetty.server.session.SessionDataStoreFactory#getSessionDataStore(org.eclipse.jetty.server.session.SessionHandler)
*/
@Override
@ -121,8 +135,12 @@ public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactor
{
MongoSessionDataStore store = new MongoSessionDataStore();
store.setGracePeriodSec(getGracePeriodSec());
store.setSavePeriodSec(getSavePeriodSec());
Mongo mongo;
if (!StringUtil.isBlank(getHost()) && getPort() != -1)
if (!StringUtil.isBlank(getConnectionString()))
mongo = new Mongo(new MongoURI(getConnectionString()));
else if (!StringUtil.isBlank(getHost()) && getPort() != -1)
mongo = new Mongo(getHost(), getPort());
else if (!StringUtil.isBlank(getHost()))
mongo = new Mongo(getHost());

View File

@ -14,8 +14,8 @@
<bundle-symbolic-name>${project.groupId}.boot.test.osgi</bundle-symbolic-name>
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
<assembly-directory>target/distribution</assembly-directory>
<exam.version>3.5.0</exam.version>
<url.version>1.5.2</url.version>
<exam.version>4.10.0</exam.version>
<url.version>2.5.2</url.version>
<injection.bundle.version>1.0</injection.bundle.version>
</properties>
<dependencies>
@ -69,30 +69,16 @@
<version>2.1.1</version>
</dependency>
<!-- OSGi R4 frameworks -->
<!--
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>4.4.0</version>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.11.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<version>3.10.0-v20140606-1445</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>3.5.100</version>
<scope>test</scope>
</dependency>
@ -413,6 +399,8 @@
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<!-- downgrading to version 2.18 here as there's a known JVM crash issue with the 2.19.x series -->
<version>2.18.1</version>
<configuration>
<!-- No point defining -Xbootclasspath as the actual OSGi VM is run as a forked process by pax-exam -->
<!-- But we do pass the sys property of the alpn-boot jar so that it can be configured inside tests -->

View File

@ -12,7 +12,7 @@
<Arg>
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">Test Realm</Set>
<Set name="config"><Property name="jetty.home" default="src/test/config"/>realm.properties</Set>
<Set name="config"><Property name="jetty.home" default="src/test/config"/>/realm.properties</Set>
</New>
</Arg>
</Call>

View File

@ -76,31 +76,29 @@ public class TestJettyOSGiBootContextAsService
// a bundle that registers a webapp as a service for the jetty osgi core
// to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
return options.toArray(new Option[options.size()]);
}
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
File etc = new File("src/test/config/etc");
StringBuffer xmlConfigs = new StringBuffer();
xmlConfigs.append(new File(etc, "jetty.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, jettySelectorFileName).toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc,"jetty-deployer.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-testrealm.xml").toURI());
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml"));
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs.toString()));
options.add(systemProperty("jetty.http.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_HTTP_PORT)));
options.add(systemProperty("jetty.ssl.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_SSL_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.home").value(etc.getParentFile().getAbsolutePath()));
return options;
}

View File

@ -90,7 +90,7 @@ public class TestJettyOSGiBootCore
public static List<Option> coreJettyDependencies()
{
List<Option> res = new ArrayList<Option>();
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start());

View File

@ -32,6 +32,8 @@ import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.osgi.framework.BundleContext;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
@ -42,6 +44,7 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
* HTTP2 setup.
*/
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class TestJettyOSGiBootHTTP2
{
private static final String LOG_LEVEL = "WARN";
@ -59,9 +62,8 @@ public class TestJettyOSGiBootHTTP2
options.addAll(http2JettyDependencies());
options.add(CoreOptions.junitBundles());
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.osgi.LEVEL").value(LOG_LEVEL))));
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
return options.toArray(new Option[options.size()]);
}
@ -76,7 +78,7 @@ public class TestJettyOSGiBootHTTP2
File checkALPNBoot = new File(alpnBoot);
if (!checkALPNBoot.exists()) { throw new IllegalStateException("Unable to find the alpn boot jar here: " + alpnBoot); }
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot));
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + checkALPNBoot.getAbsolutePath()));
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start());

View File

@ -18,9 +18,14 @@
package org.eclipse.jetty.osgi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
@ -41,13 +46,6 @@ import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* TestJettyOSGiBootWebAppAsService
*
@ -79,9 +77,8 @@ public class TestJettyOSGiBootWebAppAsService
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(TestJettyOSGiBootCore.jspDependencies());
options.addAll(testDependencies());
@ -90,21 +87,21 @@ public class TestJettyOSGiBootWebAppAsService
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
File etc = new File("src/test/config/etc");
StringBuffer xmlConfigs = new StringBuffer();
xmlConfigs.append(new File(etc, "jetty.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, jettySelectorFileName).toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-deployer.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-testrealm.xml").toURI());
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml"));
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs.toString()));
options.add(systemProperty("jetty.http.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_HTTP_PORT)));
options.add(systemProperty("jetty.ssl.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_SSL_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.home").value(etc.getParentFile().getAbsolutePath()));
return options;
}

View File

@ -18,10 +18,15 @@
package org.eclipse.jetty.osgi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
@ -36,15 +41,8 @@ import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.options.extra.WorkingDirectoryOption;
import org.osgi.framework.BundleContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
@ -71,9 +69,8 @@ public class TestJettyOSGiBootWithAnnotations
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.annotations.LEVEL").value(LOG_LEVEL))));
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
// options.addAll(TestJettyOSGiBootCore.consoleDependencies());
options.addAll(jspDependencies());
options.addAll(annotationDependencies());
@ -83,27 +80,26 @@ public class TestJettyOSGiBootWithAnnotations
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
File etc = new File("src/test/config/etc");
List<Option> options = new ArrayList<Option>();
String xmlConfigs = etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-ssl.xml;"
+ etc
+ "/jetty-https.xml;"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml";
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
StringBuffer xmlConfigs = new StringBuffer();
xmlConfigs.append(new File(etc, "jetty.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc,jettySelectorFileName).toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-ssl.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-https.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-deployer.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-testrealm.xml").toURI());
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs.toString()));
options.add(systemProperty("jetty.http.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_HTTP_PORT)));
options.add(systemProperty("jetty.ssl.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_SSL_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.home").value(etc.getParentFile().getAbsolutePath()));
return options;
}
@ -121,7 +117,7 @@ public class TestJettyOSGiBootWithAnnotations
res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-spec-webapp").classifier("webbundle").versionAsInProject());
return res;
}
@Ignore
@ -130,6 +126,7 @@ public class TestJettyOSGiBootWithAnnotations
{
TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
TestOSGiUtil.debugBundles(bundleContext);
}
// at the moment can't run httpservice with jsp at the same time.

View File

@ -18,16 +18,22 @@
package org.eclipse.jetty.osgi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.toolchain.test.OS;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -37,12 +43,6 @@ import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
@ -68,41 +68,40 @@ public class TestJettyOSGiBootWithJsp
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.osgi.LEVEL").value(LOG_LEVEL))));
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[options.size()]);
}
public static List<Option> configureJettyHomeAndPort(boolean ssl,String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
File etc = new File(OS.separators("src/test/config/etc"));
List<Option> options = new ArrayList<Option>();
String xmlConfigs = etc + "/jetty.xml";
StringBuffer xmlConfigs = new StringBuffer();
xmlConfigs.append(new File(etc, "jetty.xml").toURI());
xmlConfigs.append(";");
if (ssl)
xmlConfigs += ";"
+ etc
+ "/jetty-ssl.xml;"
+ etc
+ "/jetty-https.xml;";
xmlConfigs+= ";"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml";
{
xmlConfigs.append(new File(etc, "jetty-ssl.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-https.xml").toURI());
xmlConfigs.append(";");
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
}
xmlConfigs.append(new File(etc, jettySelectorFileName).toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-deployer.xml").toURI());
xmlConfigs.append(";");
xmlConfigs.append(new File(etc, "jetty-testrealm.xml").toURI());
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs.toString()));
options.add(systemProperty("jetty.http.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_HTTP_PORT)));
options.add(systemProperty("jetty.ssl.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_SSL_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.base").value(etcFolder.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.home").value(etc.getParentFile().getAbsolutePath()));
options.add(systemProperty("jetty.base").value(etc.getParentFile().getAbsolutePath()));
return options;
}

View File

@ -9,5 +9,8 @@ log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d [%5p][%c] %m%n
# Level tuning
log4j.logger.org.eclipse.jetty=INFO
# log4j.logger.org.eclipse.jetty=DEBUG
# log4j.logger.org.eclipse.jetty.security=DEBUG
log4j.logger.shaded.org.eclipse.aether=WARN
log4j.logger.shaded.org.apache.http=WARN
log4j.logger.org.ops4j=WARN

View File

@ -18,18 +18,15 @@
package org.eclipse.jetty.security;
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.security.PropertyUserStore.UserListener;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.Credential;
/* ------------------------------------------------------------ */
/**
@ -52,10 +49,10 @@ public class HashLoginService extends AbstractLoginService
{
private static final Logger LOG = Log.getLogger(HashLoginService.class);
protected PropertyUserStore _propertyUserStore;
protected String _config;
protected Resource _configResource;
protected boolean hotReload = false; // default is not to reload
private PropertyUserStore _propertyUserStore;
private File _configFile;
private Resource _configResource;
private boolean hotReload = false; // default is not to reload
/* ------------------------------------------------------------ */
@ -79,13 +76,22 @@ public class HashLoginService extends AbstractLoginService
/* ------------------------------------------------------------ */
public String getConfig()
{
return _config;
if(_configFile == null)
{
return null;
}
return _configFile.getAbsolutePath();
}
/* ------------------------------------------------------------ */
/**
* @deprecated use {@link #setConfig(String)} instead
*/
@Deprecated
public void getConfig(String config)
{
_config = config;
setConfig(config);
}
/* ------------------------------------------------------------ */
@ -96,14 +102,17 @@ public class HashLoginService extends AbstractLoginService
/* ------------------------------------------------------------ */
/**
* Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
* Load realm users from properties file.
* <p>
* The property file maps usernames to password specs followed by an optional comma separated list of role names.
* </p>
*
* @param config
* Filename or url of user properties file.
* @param configFile
* Filename of user properties file.
*/
public void setConfig(String config)
public void setConfig(String configFile)
{
_config = config;
_configFile = new File(configFile);
}
/**
@ -182,11 +191,11 @@ public class HashLoginService extends AbstractLoginService
if (_propertyUserStore == null)
{
if(LOG.isDebugEnabled())
LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " hotReload: " + hotReload);
LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _configFile + " hotReload: " + hotReload);
_propertyUserStore = new PropertyUserStore();
_propertyUserStore.setHotReload(hotReload);
_propertyUserStore.setConfigPath(_config);
_propertyUserStore.setConfigPath(_configFile);
_propertyUserStore.start();
}
}

View File

@ -128,6 +128,12 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
*/
public void setConfigPath(File configFile)
{
if(configFile == null)
{
_configPath = null;
return;
}
_configPath = configFile.toPath();
}

View File

@ -12,54 +12,52 @@
<Call name="insertHandler">
<Arg>
<New id="GzipHandler" class="org.eclipse.jetty.server.handler.gzip.GzipHandler">
<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="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="syncFlush"><Property name="jetty.gzip.syncFlush" default="false" /></Set>
<Set name="excludedAgentPatterns">
<Array type="String">
<Item><Property name="jetty.gzip.excludedUserAgent" deprecated="gzip.excludedUserAgent" default=".*MSIE.6\.0.*"/></Item>
</Array>
</Set>
<Set name="excludedAgentPatterns">
<Array type="String">
<Item><Property name="jetty.gzip.excludedUserAgent" deprecated="gzip.excludedUserAgent" default=".*MSIE.6\.0.*"/></Item>
</Array>
</Set>
<Set name="includedMethods">
<Array type="String">
<Item>GET</Item>
</Array>
</Set>
<Set name="includedMethodList"><Property name="jetty.gzip.includedMethodList" default="GET" /></Set>
<Set name="excludedMethodList"><Property name="jetty.gzip.excludedMethodList" default="" /></Set>
<!--
<Set name="includedPaths">
<Array type="String">
<Item>/*</Item>
</Array>
</Set>
-->
<!--
<Set name="includedMethods">
<Array type="String">
<Item>GET</Item>
</Array>
</Set>
<!--
<Set name="excludedPaths">
<Array type="String">
<Item>*.gz</Item>
</Array>
</Set>
-->
<Set name="includedPaths">
<Array type="String">
<Item>/*</Item>
</Array>
</Set>
<!--
<Call name="addIncludedMimeTypes">
<Arg><Array type="String">
<Item>some/type</Item>
</Array></Arg>
</Call>
-->
<Set name="excludedPaths">
<Array type="String">
<Item>*.gz</Item>
</Array>
</Set>
<!--
<Call name="addExcludedMimeTypes">
<Arg><Array type="String">
<Item>some/type</Item>
</Array></Arg>
</Call>
-->
<Call name="addIncludedMimeTypes">
<Arg><Array type="String">
<Item>some/type</Item>
</Array></Arg>
</Call>
<Call name="addExcludedMimeTypes">
<Arg><Array type="String">
<Item>some/type</Item>
</Array></Arg>
</Call>
-->
</New>
</Arg>

View File

@ -90,6 +90,7 @@
<Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set>
<Set name="blockingTimeout"><Property name="jetty.httpConfig.blockingTimeout" default="-1"/></Set>
<Set name="persistentConnectionsEnabled"><Property name="jetty.httpConfig.persistentConnectionsEnabled" default="true"/></Set>
<Set name="cookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.cookieCompliance" default="RFC6265"/></Arg></Call></Set>
</New>
<!-- =========================================================== -->

View File

@ -12,6 +12,7 @@
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.server.session.FileSessionDataStoreFactory">
<Set name="deleteUnrestorableFiles"><Property name="jetty.session.file.deleteUnrestorableFiles" default="false" /></Set>
<Set name="storeDir"><Property name="jetty.session.file.storeDir"/></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>

View File

@ -11,6 +11,7 @@
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.server.session.JDBCSessionDataStoreFactory">
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="databaseAdaptor">
<Ref id="databaseAdaptor"/>
</Set>

View File

@ -26,3 +26,9 @@ etc/jetty-gzip.xml
## Inflate request buffer size, or 0 for no request inflation
# jetty.gzip.inflateBufferSize=0
## Comma separated list of included methods
# jetty.gzip.includedMethodList=GET
## Comma separated list of excluded methods
# jetty.gzip.excludedMethodList=

View File

@ -67,6 +67,9 @@ etc/jetty.xml
## Maximum time to block in total for a blocking IO operation (default -1 is to use idleTimeout on progress)
# jetty.httpConfig.blockingTimeout=-1
## Cookie compliance mode of: RFC2965, RFC6265
# jetty.httpConfig.cookieCompliance=RFC6265
### Server configuration
## Whether ctrl+c on the console gracefully stops the Jetty server
# jetty.server.stopAtShutdown=true
@ -85,3 +88,4 @@ etc/jetty.xml
## How frequently sessions are inspected
#jetty.sessionInspectionInterval.seconds=60

View File

@ -19,4 +19,4 @@ sessions/
[ini-template]
jetty.session.file.storeDir=${jetty.base}/sessions
#jetty.session.file.deleteUnrestorableFiles=false
#jetty.session.savePeriod.seconds=0

View File

@ -23,6 +23,7 @@ db-connection-type?=datasource
##
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
## Connection type:Datasource
db-connection-type=datasource

View File

@ -129,7 +129,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
* <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
* method to create a new Connection instance.</li>
* </ol>
* The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
* The default number of acceptor tasks is the minimum of 1 and the number of available CPUs divided by 8. Having more acceptors may reduce
* the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive). Typically the default is
* sufficient for modern persistent protocols (HTTP/1.1, HTTP/2 etc.)
*/

View File

@ -46,18 +46,33 @@ public class ClassLoaderDump implements Dumpable
{
if (_loader==null)
out.append("No ClassLoader\n");
else if (_loader instanceof Dumpable)
{
ContainerLifeCycle.dump(out,indent,Collections.singleton(_loader));
}
else if (_loader instanceof URLClassLoader)
{
out.append(String.valueOf(_loader)).append("\n");
ClassLoader parent = _loader.getParent();
if (parent==null)
ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()));
else if (parent == Server.class.getClassLoader())
ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent.toString()));
else if (parent instanceof Dumpable)
ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
else
ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(new ClassLoaderDump(parent)));
}
else
{
out.append(String.valueOf(_loader)).append("\n");
Object parent = _loader.getParent();
if (parent != null)
{
if (_loader instanceof URLClassLoader)
ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent.toString()));
else
ContainerLifeCycle.dump(out,indent,Collections.singleton(parent.toString()));
}
ClassLoader parent = _loader.getParent();
if (parent==Server.class.getClassLoader())
ContainerLifeCycle.dump(out,indent,Collections.singleton(parent.toString()));
else if (parent instanceof Dumpable)
ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
else if (parent!=null)
ContainerLifeCycle.dump(out,indent,Collections.singleton(new ClassLoaderDump(parent)));
}
}
}

View File

@ -225,28 +225,53 @@ public class Dispatcher implements RequestDispatcher
return String.format("Dispatcher@0x%x{%s,%s}",hashCode(),_named,_uri);
}
private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
@SuppressWarnings("Duplicates")
private void commitResponse(ServletResponse response, Request baseRequest) throws IOException, ServletException
{
if (baseRequest.getResponse().isWriting())
{
try
{
// Try closing Writer first (based on knowledge in Response obj)
response.getWriter().close();
}
catch (IllegalStateException e)
catch (IllegalStateException e1)
{
response.getOutputStream().close();
try
{
// Try closing OutputStream as alternate route
// This path is possible due to badly behaving Response wrappers
response.getOutputStream().close();
}
catch(IllegalStateException e2)
{
ServletException servletException = new ServletException("Unable to commit the response", e2);
servletException.addSuppressed(e1);
throw servletException;
}
}
}
else
{
try
{
// Try closing OutputStream first (based on knowledge in Response obj)
response.getOutputStream().close();
}
catch (IllegalStateException e)
catch (IllegalStateException e1)
{
response.getWriter().close();
try
{
// Try closing Writer as alternate route
// This path is possible due to badly behaving Response wrappers
response.getWriter().close();
}
catch(IllegalStateException e2)
{
ServletException servletException = new ServletException("Unable to commit the response", e2);
servletException.addSuppressed(e1);
throw servletException;
}
}
}
}

View File

@ -381,6 +381,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
throw _state.getAsyncContextEvent().getThrowable();
}
case READ_PRODUCE:
{
_request.getHttpInput().produceContent();
break;
}
case READ_CALLBACK:
{
ContextHandler handler=_state.getContextHandler();

View File

@ -272,8 +272,11 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
@Override
public boolean headerComplete()
{
if (_complianceViolations != null)
if (_complianceViolations != null && !_complianceViolations.isEmpty())
{
this.getRequest().setAttribute(ATTR_COMPLIANCE_VIOLATIONS, _complianceViolations);
_complianceViolations=null;
}
boolean persistent;

View File

@ -78,6 +78,7 @@ public class HttpChannelState
ERROR_DISPATCH, // handle a normal error
ASYNC_ERROR, // handle an async error
WRITE_CALLBACK, // handle an IO write callback
READ_PRODUCE, // Check is a read is possible by parsing/filling
READ_CALLBACK, // handle an IO read callback
COMPLETE, // Complete the response
TERMINATED, // No further actions
@ -99,19 +100,15 @@ public class HttpChannelState
ERRORED // The error has been processed
}
private enum Interest
private enum AsyncRead
{
NONE(false),
NEEDED(true),
REGISTERED(true);
private final boolean _interested;
Interest(boolean interest)
{
_interested = interest;
}
private boolean isInterested() { return _interested;}
NONE, // No isReady; No data
AVAILABLE, // No isReady; onDataAvailable
NEEDED, // isReady()==false handling; No data
REGISTERED, // isReady()==false !handling; No data
POSSIBLE, // isReady()==false async callback called (http/1 only)
PRODUCING, // isReady()==false handling content production (http/1 only)
READY // isReady() was false, data now available
}
private final Locker _locker=new Locker();
@ -120,8 +117,7 @@ public class HttpChannelState
private State _state;
private Async _async;
private boolean _initial;
private boolean _asyncReadPossible;
private Interest _asyncRead=Interest.NONE;
private AsyncRead _asyncRead=AsyncRead.NONE;
private boolean _asyncWritePossible;
private long _timeoutMs=DEFAULT_TIMEOUT;
private AsyncContextEvent _event;
@ -187,15 +183,14 @@ public class HttpChannelState
public String toStringLocked()
{
return String.format("%s@%x{s=%s a=%s i=%b r=%s/%s w=%b}",
getClass().getSimpleName(),
hashCode(),
_state,
_async,
_initial,
_asyncRead,
_asyncReadPossible,
_asyncWritePossible);
return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",
getClass().getSimpleName(),
hashCode(),
_state,
_async,
_initial,
_asyncRead,
_asyncWritePossible);
}
@ -234,11 +229,18 @@ public class HttpChannelState
return Action.TERMINATED;
case ASYNC_WOKEN:
if (_asyncReadPossible && _asyncRead.isInterested())
switch (_asyncRead)
{
_state=State.ASYNC_IO;
_asyncRead=Interest.NONE;
return Action.READ_CALLBACK;
case POSSIBLE:
_state=State.ASYNC_IO;
_asyncRead=AsyncRead.PRODUCING;
return Action.READ_PRODUCE;
case READY:
_state=State.ASYNC_IO;
_asyncRead=AsyncRead.NONE;
return Action.READ_CALLBACK;
default:
break;
}
if (_asyncWritePossible)
@ -385,7 +387,7 @@ public class HttpChannelState
protected Action unhandle()
{
Action action;
boolean read_interested=false;
boolean read_interested = false;
try(Locker.Lock lock= _locker.lock())
{
@ -412,7 +414,7 @@ public class HttpChannelState
}
_initial=false;
switch(_async)
async: switch(_async)
{
case COMPLETE:
_state=State.COMPLETING;
@ -427,13 +429,31 @@ public class HttpChannelState
break;
case STARTED:
if (_asyncReadPossible && _asyncRead.isInterested())
switch(_asyncRead)
{
_state=State.ASYNC_IO;
_asyncRead=Interest.NONE;
action=Action.READ_CALLBACK;
case READY:
_state=State.ASYNC_IO;
_asyncRead=AsyncRead.NONE;
action=Action.READ_CALLBACK;
break async;
case POSSIBLE:
_state=State.ASYNC_IO;
action=Action.READ_PRODUCE;
break async;
case NEEDED:
case PRODUCING:
_asyncRead=AsyncRead.REGISTERED;
read_interested=true;
case NONE:
case AVAILABLE:
case REGISTERED:
break;
}
else if (_asyncWritePossible)
if (_asyncWritePossible)
{
_state=State.ASYNC_IO;
_asyncWritePossible=false;
@ -443,11 +463,6 @@ public class HttpChannelState
{
_state=State.ASYNC_WAIT;
action=Action.WAIT;
if (_asyncRead==Interest.NEEDED)
{
_asyncRead=Interest.REGISTERED;
read_interested=true;
}
Scheduler scheduler=_channel.getScheduler();
if (scheduler!=null && _timeoutMs>0)
_event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
@ -915,8 +930,7 @@ public class HttpChannelState
_state=State.IDLE;
_async=Async.NOT_ASYNC;
_initial=true;
_asyncReadPossible=false;
_asyncRead=Interest.NONE;
_asyncRead=AsyncRead.NONE;
_asyncWritePossible=false;
_timeoutMs=DEFAULT_TIMEOUT;
_event=null;
@ -943,8 +957,7 @@ public class HttpChannelState
_state=State.UPGRADED;
_async=Async.NOT_ASYNC;
_initial=true;
_asyncReadPossible=false;
_asyncRead=Interest.NONE;
_asyncRead=AsyncRead.NONE;
_asyncWritePossible=false;
_timeoutMs=DEFAULT_TIMEOUT;
_event=null;
@ -1133,19 +1146,30 @@ public class HttpChannelState
if (LOG.isDebugEnabled())
LOG.debug("onReadUnready {}",toStringLocked());
// We were already unready, this is not a state change, so do nothing
if (_asyncRead!=Interest.REGISTERED)
switch(_asyncRead)
{
_asyncReadPossible=false; // Assumes this has been checked in isReady() with lock held
if (_state==State.ASYNC_WAIT)
{
interested=true;
_asyncRead=Interest.REGISTERED;
}
else
{
_asyncRead=Interest.NEEDED;
}
case NONE:
case AVAILABLE:
case READY:
case NEEDED:
if (_state==State.ASYNC_WAIT)
{
interested=true;
_asyncRead=AsyncRead.REGISTERED;
}
else
{
_asyncRead=AsyncRead.NEEDED;
}
break;
case REGISTERED:
case POSSIBLE:
case PRODUCING:
break;
default:
throw new IllegalStateException(toStringLocked());
}
}
@ -1160,7 +1184,7 @@ public class HttpChannelState
* is returned.
* @return True IFF the channel was unready and in ASYNC_WAIT state
*/
public boolean onReadPossible()
public boolean onDataAvailable()
{
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
@ -1168,11 +1192,33 @@ public class HttpChannelState
if (LOG.isDebugEnabled())
LOG.debug("onReadPossible {}",toStringLocked());
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT && _asyncRead.isInterested())
switch(_asyncRead)
{
woken=true;
_state=State.ASYNC_WOKEN;
case NONE:
_asyncRead=AsyncRead.AVAILABLE;
break;
case AVAILABLE:
break;
case PRODUCING:
_asyncRead=AsyncRead.READY;
break;
case NEEDED:
case REGISTERED:
case POSSIBLE:
case READY:
_asyncRead=AsyncRead.READY;
if (_state==State.ASYNC_WAIT)
{
woken=true;
_state=State.ASYNC_WOKEN;
}
break;
default:
throw new IllegalStateException(toStringLocked());
}
}
return woken;
@ -1181,7 +1227,7 @@ public class HttpChannelState
/**
* Called to signal that the channel is ready for a callback.
* This is similar to calling {@link #onReadUnready()} followed by
* {@link #onReadPossible()}, except that as content is already
* {@link #onDataAvailable()}, except that as content is already
* available, read interest is never set.
* @return true if woken
*/
@ -1192,13 +1238,52 @@ public class HttpChannelState
{
if (LOG.isDebugEnabled())
LOG.debug("onReadReady {}",toStringLocked());
_asyncRead=Interest.REGISTERED;
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT)
switch(_asyncRead)
{
woken=true;
_state=State.ASYNC_WOKEN;
case NONE:
case AVAILABLE:
_asyncRead=AsyncRead.READY;
if (_state==State.ASYNC_WAIT)
{
woken=true;
_state=State.ASYNC_WOKEN;
}
break;
default:
throw new IllegalStateException(toStringLocked());
}
}
return woken;
}
/**
* Called to indicate that more content may be available,
* but that a handling thread may need to produce (fill/parse)
* it. Typically called by the async read success callback.
*/
public boolean onReadPossible()
{
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
if (LOG.isDebugEnabled())
LOG.debug("onReadReady {}",toStringLocked());
switch(_asyncRead)
{
case REGISTERED:
_asyncRead=AsyncRead.POSSIBLE;
if (_state==State.ASYNC_WAIT)
{
woken=true;
_state=State.ASYNC_WOKEN;
}
break;
default:
throw new IllegalStateException(toStringLocked());
}
}
return woken;
@ -1217,9 +1302,8 @@ public class HttpChannelState
if (LOG.isDebugEnabled())
LOG.debug("onEof {}",toStringLocked());
// Force read interest so onAllDataRead can be called
_asyncRead=Interest.REGISTERED;
_asyncReadPossible=true;
// Force read ready so onAllDataRead can be called
_asyncRead=AsyncRead.READY;
if (_state==State.ASYNC_WAIT)
{
woken=true;
@ -1229,14 +1313,6 @@ public class HttpChannelState
return woken;
}
public boolean isReadPossible()
{
try(Locker.Lock lock= _locker.lock())
{
return _asyncReadPossible;
}
}
public boolean onWritePossible()
{
boolean wake=false;

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.Jetty;
@ -67,6 +68,7 @@ public class HttpConfiguration
private int _maxErrorDispatches = 10;
private boolean _useDirectByteBuffers = false;
private long _minRequestDataRate;
private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265;
/* ------------------------------------------------------------ */
/**
@ -126,6 +128,7 @@ public class HttpConfiguration
_maxErrorDispatches=config._maxErrorDispatches;
_useDirectByteBuffers=config._useDirectByteBuffers;
_minRequestDataRate=config._minRequestDataRate;
_cookieCompliance=config._cookieCompliance;
}
/* ------------------------------------------------------------ */
@ -545,4 +548,22 @@ public class HttpConfiguration
{
_minRequestDataRate=bytesPerSecond;
}
/* ------------------------------------------------------------ */
public CookieCompliance getCookieCompliance()
{
return _cookieCompliance;
}
/* ------------------------------------------------------------ */
public void setCookieCompliance(CookieCompliance cookieCompliance)
{
_cookieCompliance = cookieCompliance==null?CookieCompliance.RFC6265:cookieCompliance;
}
/* ------------------------------------------------------------ */
public boolean isCookieCompliance(CookieCompliance compliance)
{
return _cookieCompliance.equals(compliance);
}
}

View File

@ -622,10 +622,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
@Override
public void succeeded()
{
if (fillAndParseForContent())
if (_channel.getState().onReadPossible())
_channel.handle();
else if (!_input.isFinished() && !_input.hasContent())
asyncReadFillInterested();
}
@Override

View File

@ -577,7 +577,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null)
_inputQ.notify();
else
woken = _channelState.onReadPossible();
woken = _channelState.onDataAvailable();
}
return woken;
}
@ -612,7 +612,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null)
_inputQ.notify();
else
woken = _channelState.onReadPossible();
woken = _channelState.onDataAvailable();
}
}
return woken;
@ -800,7 +800,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null)
_inputQ.notify();
else
woken = _channelState.onReadPossible();
woken = _channelState.onDataAvailable();
}
return woken;

View File

@ -918,6 +918,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_commitSize = _bufferSize;
releaseBuffer();
_written = 0;
_writeListener = null;
_onError = null;
reopen();
}

View File

@ -332,13 +332,31 @@ public class Request implements HttpServletRequest
// once extracted and may have already been extracted by getParts() or
// by a processing happening after a form-based authentication.
if (_contentParameters == null)
extractContentParameters();
{
try
{
extractContentParameters();
}
catch(IllegalStateException | IllegalArgumentException e)
{
throw new BadMessageException("Unable to parse form content", e);
}
}
}
// Extract query string parameters; these may be replaced by a forward()
// and may have already been extracted by mergeQueryParameters().
if (_queryParameters == null)
extractQueryParameters();
{
try
{
extractQueryParameters();
}
catch(IllegalStateException | IllegalArgumentException e)
{
throw new BadMessageException("Unable to parse URI query", e);
}
}
// Do parameters need to be combined?
if (_queryParameters==NO_PARAMS || _queryParameters.size()==0)
@ -1432,7 +1450,7 @@ public class Request implements HttpServletRequest
s.renewId(this);
if (getRemoteUser() != null)
s.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (s.isIdChanged())
if (s.isIdChanged() && _sessionHandler.isUsingCookies())
_channel.getResponse().addCookie(_sessionHandler.getSessionCookie(s, getContextPath(), isSecure()));
}

View File

@ -35,6 +35,7 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpCookie;
@ -50,6 +51,7 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.Syntax;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
@ -117,6 +119,7 @@ public class Response implements HttpServletResponse
private enum EncodingFrom { NOT_SET, INFERRED, SET_LOCALE, SET_CONTENT_TYPE, SET_CHARACTER_ENCODING };
private static final EnumSet<EncodingFrom> __localeOverride = EnumSet.of(EncodingFrom.NOT_SET,EncodingFrom.INFERRED);
private static final EnumSet<EncodingFrom> __explicitCharset = EnumSet.of(EncodingFrom.SET_LOCALE,EncodingFrom.SET_CHARACTER_ENCODING);
public Response(HttpChannel channel, HttpOutput out)
@ -172,7 +175,13 @@ public class Response implements HttpServletResponse
public void addCookie(HttpCookie cookie)
{
addSetCookie(
if (StringUtil.isBlank(cookie.getName()))
{
throw new IllegalArgumentException("Cookie.name cannot be blank/null");
}
if (getHttpChannel().getHttpConfiguration().isCookieCompliance(CookieCompliance.RFC2965))
addSetRFC2965Cookie(
cookie.getName(),
cookie.getValue(),
cookie.getDomain(),
@ -182,6 +191,15 @@ public class Response implements HttpServletResponse
cookie.isSecure(),
cookie.isHttpOnly(),
cookie.getVersion());
else
addSetRFC6265Cookie(
cookie.getName(),
cookie.getValue(),
cookie.getDomain(),
cookie.getPath(),
cookie.getMaxAge(),
cookie.isSecure(),
cookie.isHttpOnly());
}
@Override
@ -201,7 +219,14 @@ public class Response implements HttpServletResponse
comment = null;
}
}
addSetCookie(cookie.getName(),
if (StringUtil.isBlank(cookie.getName()))
{
throw new IllegalArgumentException("Cookie.name cannot be blank/null");
}
if (getHttpChannel().getHttpConfiguration().isCookieCompliance(CookieCompliance.RFC2965))
addSetRFC2965Cookie(cookie.getName(),
cookie.getValue(),
cookie.getDomain(),
cookie.getPath(),
@ -210,9 +235,89 @@ public class Response implements HttpServletResponse
cookie.getSecure(),
httpOnly || cookie.isHttpOnly(),
cookie.getVersion());
else
addSetRFC6265Cookie(cookie.getName(),
cookie.getValue(),
cookie.getDomain(),
cookie.getPath(),
cookie.getMaxAge(),
cookie.getSecure(),
httpOnly || cookie.isHttpOnly());
}
/**
* Format a set cookie value by RFC6265
*
* @param name the name
* @param value the value
* @param domain the domain
* @param path the path
* @param maxAge the maximum age
* @param isSecure true if secure cookie
* @param isHttpOnly true if for http only
*/
public void addSetRFC6265Cookie(
final String name,
final String value,
final String domain,
final String path,
final long maxAge,
final boolean isSecure,
final boolean isHttpOnly)
{
// Check arguments
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Bad cookie name");
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
// Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules
Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name");
// Ensure that Per RFC6265, Cookie.value follows syntax rules
Syntax.requireValidRFC6265CookieValue(value);
// Format value and params
StringBuilder buf = __cookieBuilder.get();
buf.setLength(0);
buf.append(name).append('=').append(value==null?"":value);
// Append path
if (path!=null && path.length()>0)
buf.append(";Path=").append(path);
// Append domain
if (domain!=null && domain.length()>0)
buf.append(";Domain=").append(domain);
// Handle max-age and/or expires
if (maxAge >= 0)
{
// Always use expires
// This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
buf.append(";Expires=");
if (maxAge == 0)
buf.append(__01Jan1970_COOKIE);
else
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
buf.append(";Max-Age=");
buf.append(maxAge);
}
// add the other fields
if (isSecure)
buf.append(";Secure");
if (isHttpOnly)
buf.append(";HttpOnly");
// add the set cookie
_fields.add(HttpHeader.SET_COOKIE, buf.toString());
// Expire responses with set-cookie headers so they do not get cached.
_fields.put(__EXPIRES_01JAN1970);
}
/**
* Format a set cookie value
*
@ -226,7 +331,7 @@ public class Response implements HttpServletResponse
* @param isHttpOnly true if for http only
* @param version version of cookie logic to use (0 == default behavior)
*/
public void addSetCookie(
public void addSetRFC2965Cookie(
final String name,
final String value,
final String domain,
@ -1321,10 +1426,19 @@ public class Response implements HttpServletResponse
HttpField ct=content.getContentType();
if (ct!=null)
{
_fields.put(ct);
_contentType=ct.getValue();
_characterEncoding=content.getCharacterEncoding();
_mimeType=content.getMimeType();
if (_characterEncoding!=null &&
content.getCharacterEncoding()==null &&
__explicitCharset.contains(_encodingFrom))
{
setContentType(content.getMimeType().getBaseType().asString());
}
else
{
_fields.put(ct);
_contentType=ct.getValue();
_characterEncoding=content.getCharacterEncoding();
_mimeType=content.getMimeType();
}
}
HttpField ce=content.getContentEncoding();

View File

@ -71,7 +71,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
* that these callbacks may do some non-blocking IO work, but will always dispatch to the
* {@link Executor} service any blocking, long running or application tasks.
* <p>
* The default number of selectors is equal to the number of processors available to the JVM,
* The default number of selectors is equal to half of the number of processors available to the JVM,
* which should allow optimal performance even if all the connections used are performing
* significant non-blocking work in the callback tasks.
*/

View File

@ -206,7 +206,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
private volatile Availability _availability;
private volatile Availability _availability = Availability.UNAVAILABLE;
/* ------------------------------------------------------------ */
public ContextHandler()
@ -684,18 +684,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
/* ------------------------------------------------------------ */
/**
* @return true if this context is accepting new requests
* @return true if this context is shutting down
*/
@ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
public boolean isShutdown()
{
switch(_availability)
{
case SHUTDOWN:
return true;
default:
return false;
}
return _availability == Availability.SHUTDOWN;
}
/* ------------------------------------------------------------ */

View File

@ -59,31 +59,25 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class GzipHandler extends HandlerWrapper implements GzipFactory
{
public static final String GZIP = "gzip";
public static final String DEFLATE = "deflate";
public static final int DEFAULT_MIN_GZIP_SIZE=16;
private static final Logger LOG = Log.getLogger(GzipHandler.class);
public final static String GZIP = "gzip";
public final static String DEFLATE = "deflate";
public final static int DEFAULT_MIN_GZIP_SIZE=16;
private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
private boolean _checkGzExists = true;
private boolean _syncFlush = false;
private int _inflateBufferSize = -1;
private EnumSet<DispatcherType> _dispatchers = EnumSet.of(DispatcherType.REQUEST);
// non-static, as other GzipHandler instances may have different configurations
private final ThreadLocal<Deflater> _deflater = new ThreadLocal<>();
private final IncludeExclude<String> _agentPatterns=new IncludeExclude<>(RegexSet.class);
private final IncludeExclude<String> _methods = new IncludeExclude<>();
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
private HttpField _vary;
/* ------------------------------------------------------------ */
/**
* Instantiates a new gzip handler.
* The excluded Mime Types are initialized to common known
@ -110,12 +104,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude("application/brotli");
_mimeTypes.exclude("application/x-xz");
_mimeTypes.exclude("application/x-rar-compressed");
LOG.debug("{} mime types {}",this,_mimeTypes);
if (LOG.isDebugEnabled())
LOG.debug("{} mime types {}",this,_mimeTypes);
_agentPatterns.exclude(".*MSIE 6.0.*");
}
/* ------------------------------------------------------------ */
/**
* @param patterns Regular expressions matching user agents to exclude
*/
@ -124,7 +119,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_agentPatterns.exclude(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to exclude in compression
*/
@ -134,28 +128,24 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.exclude(m);
}
/* ------------------------------------------------------------ */
public EnumSet<DispatcherType> getDispatcherTypes()
{
return _dispatchers;
}
/* ------------------------------------------------------------ */
public void setDispatcherTypes(EnumSet<DispatcherType> dispatchers)
{
_dispatchers = dispatchers;
}
/* ------------------------------------------------------------ */
public void setDispatcherTypes(DispatcherType... dispatchers)
{
_dispatchers = EnumSet.copyOf(Arrays.asList(dispatchers));
}
/* ------------------------------------------------------------ */
/**
* Set the mime types.
* Adds mime types to the excluded list.
*
* @param types The mime types to exclude (without charset or other parameters).
* For backward compatibility the mimetypes may be comma separated strings, but this
* will not be supported in future versions.
@ -166,9 +156,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude(StringUtil.csvSplit(t));
}
/* ------------------------------------------------------------ */
/**
* Add path to excluded paths list.
* Add paths to excluded paths list.
* <p>
* There are 2 syntaxes supported, Servlet <code>url-pattern</code> based, and
* Regex based. This means that the initial characters on the path spec
@ -198,16 +187,14 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.exclude(StringUtil.csvSplit(p));
}
/* ------------------------------------------------------------ */
/**
* @param patterns Regular expressions matching user agents to exclude
* @param patterns Regular expressions matching user agents to include
*/
public void addIncludedAgentPatterns(String... patterns)
{
_agentPatterns.include(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to include in compression
*/
@ -217,7 +204,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.include(m);
}
/* ------------------------------------------------------------ */
/**
* @return True if {@link Deflater#SYNC_FLUSH} is used, else {@link Deflater#NO_FLUSH}
*/
@ -226,7 +212,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _syncFlush;
}
/* ------------------------------------------------------------ */
/**
* <p>Set the {@link Deflater} flush mode to use. {@link Deflater#SYNC_FLUSH}
* should be used if the application wishes to stream the data, but this may
@ -238,7 +223,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_syncFlush = syncFlush;
}
/* ------------------------------------------------------------ */
/**
* Add included mime types. Inclusion takes precedence over
* exclusion.
@ -252,9 +236,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.include(StringUtil.csvSplit(t));
}
/* ------------------------------------------------------------ */
/**
* Add path specs to include.
* Adds paths specs to the included list.
* <p>
* There are 2 syntaxes supported, Servlet <code>url-pattern</code> based, and
* Regex based. This means that the initial characters on the path spec
@ -270,7 +253,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
* <li>All other syntaxes are unsupported</li>
* </ul>
* <p>
* Note: inclusion takes precedence over exclude.
* Note: inclusion takes precedence over exclusion.
*
* @param pathspecs Path specs (as per servlet spec) to include. If a
* ServletContext is available, the paths are relative to the context path,
@ -282,7 +265,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.include(StringUtil.csvSplit(p));
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
@ -290,19 +272,16 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
super.doStart();
}
/* ------------------------------------------------------------ */
public boolean getCheckGzExists()
{
return _checkGzExists;
}
/* ------------------------------------------------------------ */
public int getCompressionLevel()
{
return _compressionLevel;
}
/* ------------------------------------------------------------ */
@Override
public Deflater getDeflater(Request request, long content_length)
{
@ -344,87 +323,73 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return df;
}
/* ------------------------------------------------------------ */
public String[] getExcludedAgentPatterns()
{
Set<String> excluded=_agentPatterns.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedMethods()
{
Set<String> excluded=_methods.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedMimeTypes()
{
Set<String> excluded=_mimeTypes.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedPaths()
{
Set<String> excluded=_paths.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedAgentPatterns()
{
Set<String> includes=_agentPatterns.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMethods()
{
Set<String> includes=_methods.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMimeTypes()
{
Set<String> includes=_mimeTypes.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedPaths()
{
Set<String> includes=_paths.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
@Deprecated
public String[] getMethods()
{
return getIncludedMethods();
}
/* ------------------------------------------------------------ */
/**
* Get the minimum response size.
*
* @return minimum response size
* @return minimum response size that triggers compression
*/
public int getMinGzipSize()
{
return _minGzipSize;
}
/* ------------------------------------------------------------ */
protected HttpField getVaryField()
{
return _vary;
}
/* ------------------------------------------------------------ */
/**
* @return size in bytes of the buffer to inflate compressed request, or 0 for no inflation.
*/
@ -433,7 +398,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _inflateBufferSize;
}
/* ------------------------------------------------------------ */
/**
* @param size size in bytes of the buffer to inflate compressed request, or 0 for no inflation.
*/
@ -442,10 +406,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_inflateBufferSize = size;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
@ -568,12 +528,9 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
}
/* ------------------------------------------------------------ */
/**
* Checks to see if the userAgent is excluded
*
* @param ua the user agent
* @return boolean true if excluded
* @return whether compressing is allowed for the given user agent
*/
protected boolean isAgentGzipable(String ua)
{
@ -583,20 +540,15 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _agentPatterns.test(ua);
}
/* ------------------------------------------------------------ */
@Override
public boolean isMimeTypeGzipable(String mimetype)
{
return _mimeTypes.test(mimetype);
}
/* ------------------------------------------------------------ */
/**
* Checks to see if the path is included or not excluded
*
* @param requestURI
* the request uri
* @return boolean true if gzipable
* @param requestURI the request uri
* @return whether compressing is allowed for the given the path
*/
protected boolean isPathGzipable(String requestURI)
{
@ -606,7 +558,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _paths.test(requestURI);
}
/* ------------------------------------------------------------ */
@Override
public void recycle(Deflater deflater)
{
@ -619,9 +570,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
deflater.end();
}
/* ------------------------------------------------------------ */
/**
* @param checkGzExists If true, check if a static gz file exists for
* @param checkGzExists whether to check if a static gz file exists for
* the resource that the DefaultServlet may serve as precompressed.
*/
public void setCheckGzExists(boolean checkGzExists)
@ -629,7 +579,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_checkGzExists = checkGzExists;
}
/* ------------------------------------------------------------ */
/**
* @param compressionLevel The compression level to use to initialize {@link Deflater#setLevel(int)}
*/
@ -638,7 +587,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_compressionLevel = compressionLevel;
}
/* ------------------------------------------------------------ */
/**
* @param patterns Regular expressions matching user agents to exclude
*/
@ -648,19 +596,16 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
addExcludedAgentPatterns(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param method to exclude
* @param methods the HTTP methods to exclude
*/
public void setExcludedMethods(String... method)
public void setExcludedMethods(String... methods)
{
_methods.getExcluded().clear();
_methods.exclude(method);
_methods.exclude(methods);
}
/* ------------------------------------------------------------ */
/**
* Set the mime types.
* @param types The mime types to exclude (without charset or other parameters)
*/
public void setExcludedMimeTypes(String... types)
@ -669,7 +614,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude(types);
}
/* ------------------------------------------------------------ */
/**
* @param pathspecs Path specs (as per servlet spec) to exclude. If a
* ServletContext is available, the paths are relative to the context path,
@ -681,7 +625,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.exclude(pathspecs);
}
/* ------------------------------------------------------------ */
/**
* @param patterns Regular expressions matching user agents to include
*/
@ -691,7 +634,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
addIncludedAgentPatterns(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to include in compression
*/
@ -701,10 +643,9 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.include(methods);
}
/* ------------------------------------------------------------ */
/**
* Set included mime types. Inclusion takes precedence over
* exclusion.
* Sets included mime types. Inclusion takes precedence over exclusion.
*
* @param types The mime types to include (without charset or other parameters)
*/
public void setIncludedMimeTypes(String... types)
@ -713,9 +654,9 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.include(types);
}
/* ------------------------------------------------------------ */
/**
* Set the path specs to include. Inclusion takes precedence over exclusion.
*
* @param pathspecs Path specs (as per servlet spec) to include. If a
* ServletContext is available, the paths are relative to the context path,
* otherwise they are absolute
@ -726,9 +667,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.include(pathspecs);
}
/* ------------------------------------------------------------ */
/**
* Set the minimum response size to trigger dynamic compresssion
* Set the minimum response size to trigger dynamic compression
*
* @param minGzipSize minimum response size in bytes
*/
@ -736,4 +676,24 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
{
_minGzipSize = minGzipSize;
}
public void setIncludedMethodList(String csvMethods)
{
setIncludedMethods(StringUtil.csvSplit(csvMethods));
}
public String getIncludedMethodList()
{
return String.join(",", getIncludedMethods());
}
public void setExcludedMethodList(String csvMethods)
{
setExcludedMethods(StringUtil.csvSplit(csvMethods));
}
public String getExcludedMethodList()
{
return String.join(",", getExcludedMethods());
}
}

View File

@ -21,9 +21,12 @@ package org.eclipse.jetty.server.session;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* AbstractSessionDataStore
@ -32,10 +35,12 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
*/
public abstract class AbstractSessionDataStore extends ContainerLifeCycle implements SessionDataStore
{
final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
protected SessionContext _context; //context associated with this session data store
protected int _gracePeriodSec = 60 * 60; //default of 1hr
protected long _lastExpiryCheckTime = 0; //last time in ms that getExpired was called
protected int _savePeriodSec = 0; //time in sec between saves
/**
* Store the session data persistently.
@ -74,21 +79,33 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
@Override
public void store(String id, SessionData data) throws Exception
{
if (data == null)
return;
long lastSave = data.getLastSaved();
long savePeriodMs = (_savePeriodSec <=0? 0: TimeUnit.SECONDS.toMillis(_savePeriodSec));
//set the last saved time to now
data.setLastSaved(System.currentTimeMillis());
try
if (LOG.isDebugEnabled())
LOG.debug("Store: id={}, dirty={}, lsave={}, period={}, elapsed={}", id,data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis()-lastSave));
//save session if attribute changed or never been saved or time between saves exceeds threshold
if (data.isDirty() || (lastSave <= 0) || ((System.currentTimeMillis()-lastSave) > savePeriodMs))
{
//call the specific store method, passing in previous save time
doStore(id, data, lastSave);
data.setDirty(false); //only undo the dirty setting if we saved it
}
catch (Exception e)
{
//reset last save time if save failed
data.setLastSaved(lastSave);
throw e;
//set the last saved time to now
data.setLastSaved(System.currentTimeMillis());
try
{
//call the specific store method, passing in previous save time
doStore(id, data, lastSave);
data.setDirty(false); //only undo the dirty setting if we saved it
}
catch (Exception e)
{
//reset last save time if save failed
data.setLastSaved(lastSave);
throw e;
}
}
}
@ -148,6 +165,37 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
}
/**
* @return the savePeriodSec
*/
public int getSavePeriodSec()
{
return _savePeriodSec;
}
/**
* The minimum time in seconds between save operations.
* Saves normally occur every time the last request
* exits as session. If nothing changes on the session
* except for the access time and the persistence technology
* is slow, this can cause delays.
* <p>
* By default the value is 0, which means we save
* after the last request exists. A non zero value
* means that we will skip doing the save if the
* session isn't dirty if the elapsed time since
* the session was last saved does not exceed this
* value.
*
* @param savePeriodSec the savePeriodSec to set
*/
public void setSavePeriodSec(int savePeriodSec)
{
_savePeriodSec = savePeriodSec;
}
/**
* @see java.lang.Object#toString()
*/

View File

@ -28,6 +28,7 @@ public abstract class AbstractSessionDataStoreFactory implements SessionDataStor
{
int _gracePeriodSec;
int _savePeriodSec;
@ -47,6 +48,24 @@ public abstract class AbstractSessionDataStoreFactory implements SessionDataStor
{
_gracePeriodSec = gracePeriodSec;
}
/**
* @return the savePeriodSec
*/
public int getSavePeriodSec()
{
return _savePeriodSec;
}
/**
* @param savePeriodSec the savePeriodSec to set
*/
public void setSavePeriodSec(int savePeriodSec)
{
_savePeriodSec = savePeriodSec;
}
}

View File

@ -153,10 +153,14 @@ public class CachingSessionDataStore extends ContainerLifeCycle implements Sessi
@Override
public void store(String id, SessionData data) throws Exception
{
long lastSaved = data.getLastSaved();
//write to the SessionDataStore first
_store.store(id, data);
//then update the cache with written data
_cache.store(id,data);
//if the store saved it, then update the cache too
if (data.getLastSaved() != lastSaved)
_cache.store(id,data);
}

View File

@ -145,20 +145,17 @@ public class DefaultSessionCache extends AbstractSessionCache
{
for (Session session: _sessions.values())
{
//if we have a backing store and the session is dirty make sure it is written out
//if we have a backing store so give the session to it to write out if necessary
if (_sessionDataStore != null)
{
if (session.getSessionData().isDirty())
session.willPassivate();
try
{
session.willPassivate();
try
{
_sessionDataStore.store(session.getId(), session.getSessionData());
}
catch (Exception e)
{
LOG.warn(e);
}
_sessionDataStore.store(session.getId(), session.getSessionData());
}
catch (Exception e)
{
LOG.warn(e);
}
doDelete (session.getId()); //remove from memory
}

View File

@ -29,15 +29,18 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -120,9 +123,42 @@ public class FileSessionDataStore extends AbstractSessionDataStore
final long now = System.currentTimeMillis();
HashSet<String> expired = new HashSet<String>();
File[] files = _storeDir.listFiles(new FilenameFilter()
{
HashSet<String> idsWithContext = new HashSet<>();
//one pass to get all idWithContext
File [] files = _storeDir.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
if (dir != _storeDir)
return false;
//dir may contain files that don't match our naming pattern
if (!match(name))
{
return false;
}
String idWithContext = getIdWithContextFromString(name);
if (!StringUtil.isBlank(idWithContext))
idsWithContext.add(idWithContext);
return true;
}
});
//got the list of all sessionids with their contexts, remove all old files for each one
for (String idWithContext:idsWithContext)
{
deleteOldFiles(_storeDir, idWithContext);
}
//now find sessions that have expired in any context
files = _storeDir.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
@ -130,16 +166,15 @@ public class FileSessionDataStore extends AbstractSessionDataStore
return false;
//dir may contain files that don't match our naming pattern
int index = name.indexOf('_');
if (index < 0)
if (!match(name))
return false;
try
{
long expiry = Long.parseLong(name.substring(0, index));
long expiry = getExpiryFromString(name);
return expiry > 0 && expiry < now;
}
catch (NumberFormatException e)
catch (Exception e)
{
return false;
}
@ -184,8 +219,9 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
public void run ()
{
File file = getFile(_storeDir,id);
//get rid of all but the newest file for a session
File file = deleteOldFiles(_storeDir, getIdWithContext(id));
if (file == null || !file.exists())
{
if (LOG.isDebugEnabled())
@ -195,18 +231,18 @@ public class FileSessionDataStore extends AbstractSessionDataStore
try (FileInputStream in = new FileInputStream(file))
{
SessionData data = load(in);
//delete restored file
file.delete();
SessionData data = load(in, id);
data.setLastSaved(file.lastModified());
reference.set(data);
}
catch (UnreadableSessionDataException e)
{
if (isDeleteUnrestorableFiles() && file.exists() && file.getParentFile().equals(_storeDir));
if (isDeleteUnrestorableFiles() && file.exists() && file.getParentFile().equals(_storeDir))
{
file.delete();
LOG.warn("Deleted unrestorable file for session {}", id);
}
exception.set(e);
}
catch (Exception e)
@ -235,13 +271,11 @@ public class FileSessionDataStore extends AbstractSessionDataStore
File file = null;
if (_storeDir != null)
{
//remove any existing file for the session
file = getFile(_storeDir, id);
if (file != null && file.exists())
file.delete();
//remove any existing files for the session
deleteAllFiles(_storeDir, getIdWithContext(id));
//make a fresh file using the latest session expiry
file = new File(_storeDir, getFileNameWithExpiry(data));
file = new File(_storeDir, getIdWithContextAndExpiry(data));
try(FileOutputStream fos = new FileOutputStream(file,false))
{
@ -287,7 +321,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
@Override
public boolean exists(String id) throws Exception
{
File sessionFile = getFile(_storeDir, id);
File sessionFile = deleteOldFiles(_storeDir, getIdWithContext(id));
if (sessionFile == null || !sessionFile.exists())
return false;
@ -331,19 +365,32 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
/**
* Get the session id with its context.
*
* @param id identity of session
* @return the filename of the session data store
* @return the session id plus context
*/
private String getFileName (String id)
private String getIdWithContext (String id)
{
return _context.getCanonicalContextPath()+"_"+_context.getVhost()+"_"+id;
}
private String getFileNameWithExpiry (SessionData data)
/**
* Get the session id with its context and its expiry time
* @param data
* @return the session id plus context and expiry
*/
private String getIdWithContextAndExpiry (SessionData data)
{
return ""+data.getExpiry()+"_"+getFileName(data.getId());
return ""+data.getExpiry()+"_"+getIdWithContext(data.getId());
}
/**
* Work out which session id the file relates to.
* @param file the file to check
* @return the session id the file relates to.
*/
private String getIdFromFile (File file)
{
if (file == null)
@ -353,15 +400,73 @@ public class FileSessionDataStore extends AbstractSessionDataStore
return name.substring(name.lastIndexOf('_')+1);
}
/**
* Get the expiry time of the session stored in the file.
* @param file the file from which to extract the expiry time
* @return the expiry time
*/
private long getExpiryFromFile (File file)
{
if (file == null)
return 0;
String name = file.getName();
String s = name.substring(0, name.indexOf('_'));
return getExpiryFromString(file.getName());
}
private long getExpiryFromString (String filename)
{
if (StringUtil.isBlank(filename) || filename.indexOf("_") < 0)
throw new IllegalStateException ("Invalid or missing filename");
String s = filename.substring(0, filename.indexOf('_'));
return (s==null?0:Long.parseLong(s));
}
/**
* Extract the session id and context from the filename.
* @param file the file whose name to use
* @return the session id plus context
*/
private String getIdWithContextFromFile (File file)
{
if (file == null)
return null;
String s = getIdWithContextFromString(file.getName());
return s;
}
/**
* Extract the session id and context from the filename
* @param filename the name of the file to use
* @return the session id plus context
*/
private String getIdWithContextFromString (String filename)
{
if (StringUtil.isBlank(filename) || filename.indexOf('_') < 0)
return null;
return filename.substring(filename.indexOf('_')+1);
}
/**
* Check if the filename matches our session pattern
* @param filename
* @return
*/
private boolean match (String filename)
{
if (StringUtil.isBlank(filename))
return false;
String[] parts = filename.split("_");
//Need at least 4 parts for a valid filename
if (parts.length < 4)
return false;
return true;
}
/**
@ -383,7 +488,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
if (dir != storeDir)
return false;
return (name.contains(getFileName(id)));
return (name.contains(getIdWithContext(id)));
}
});
@ -392,16 +497,149 @@ public class FileSessionDataStore extends AbstractSessionDataStore
return null;
return files[0];
}
/**
* Remove all existing session files for the session in the context
* @param storeDir where the session files are stored
* @param idInContext the session id within a particular context
*/
private void deleteAllFiles(final File storeDir, final String idInContext)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
return false;
return (name.contains(idInContext));
}
});
//no files for that id
if (files == null || files.length < 1)
return;
//delete all files
for (File f:files)
{
try
{
Files.deleteIfExists(f.toPath());
}
catch (Exception e)
{
LOG.warn("Unable to delete session file", e);
}
}
}
/**
* Delete all but the most recent file for a given session id in a context.
*
* @param storeDir the directory in which sessions are stored
* @param idWithContext the id of the session
* @return the most recent remaining file for the session, can be null
*/
private File deleteOldFiles (final File storeDir, final String idWithContext)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
return false;
if (!match(name))
return false;
return (name.contains(idWithContext));
}
});
//no file for that session
if (files == null || files.length == 0)
return null;
//delete all but the most recent file
File newest = null;
for (File f:files)
{
try
{
if (newest == null)
{
//haven't looked at any files yet
newest = f;
}
else
{
if (f.lastModified() > newest.lastModified())
{
//this file is more recent
Files.deleteIfExists(newest.toPath());
newest = f;
}
else if (f.lastModified() < newest.lastModified())
{
//this file is older
Files.deleteIfExists(f.toPath());
}
else
{
//files have same last modified times, decide based on latest expiry time
long exp1 = getExpiryFromFile(newest);
long exp2 = getExpiryFromFile(f);
if (exp2 >= exp1)
{
//this file has a later expiry date
Files.deleteIfExists(newest.toPath());
newest = f;
}
else
{
//this file has an earlier expiry date
Files.deleteIfExists(f.toPath());
}
}
}
}
catch (Exception e)
{
LOG.warn("Unable to delete old session file", e);
}
}
return newest;
}
/**
* @param is inputstream containing session data
* @param expectedId the id we've been told to load
* @return the session data
* @throws Exception
*/
private SessionData load (InputStream is)
private SessionData load (InputStream is, String expectedId)
throws Exception
{
String id = null;
String id = null; //the actual id from inside the file
try
{
@ -434,7 +672,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
catch (Exception e)
{
throw new UnreadableSessionDataException(id, _context, e);
throw new UnreadableSessionDataException(expectedId, _context, e);
}
}

View File

@ -78,6 +78,7 @@ public class FileSessionDataStoreFactory extends AbstractSessionDataStoreFactory
fsds.setDeleteUnrestorableFiles(isDeleteUnrestorableFiles());
fsds.setStoreDir(getStoreDir());
fsds.setGracePeriodSec(getGracePeriodSec());
fsds.setSavePeriodSec(getSavePeriodSec());
return fsds;
}

View File

@ -48,6 +48,7 @@ public class JDBCSessionDataStoreFactory extends AbstractSessionDataStoreFactory
ds.setDatabaseAdaptor(_adaptor);
ds.setSessionTableSchema(_schema);
ds.setGracePeriodSec(getGracePeriodSec());
ds.setSavePeriodSec(getSavePeriodSec());
return ds;
}

View File

@ -0,0 +1,200 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import org.eclipse.jetty.util.component.Dumpable;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class ClassLoaderDumptTest
{
@Test
public void testSimple() throws Exception
{
Server server = new Server();
ClassLoader loader = new ClassLoader()
{
public String toString()
{
return "SimpleLoader";
}
};
server.addBean(new ClassLoaderDump(loader));
StringBuilder out = new StringBuilder();
server.dump(out);
String dump = out.toString();
assertThat(dump,containsString("+- SimpleLoader"));
assertThat(dump,containsString("+> "+Server.class.getClassLoader()));
}
@Test
public void testParent() throws Exception
{
Server server = new Server();
ClassLoader loader = new ClassLoader(Server.class.getClassLoader())
{
public String toString()
{
return "ParentedLoader";
}
};
server.addBean(new ClassLoaderDump(loader));
StringBuilder out = new StringBuilder();
server.dump(out);
String dump = out.toString();
assertThat(dump,containsString("+- ParentedLoader"));
assertThat(dump,containsString("| +- "+Server.class.getClassLoader()));
assertThat(dump,containsString("+> "+Server.class.getClassLoader()));
}
@Test
public void testNested() throws Exception
{
Server server = new Server();
ClassLoader middleLoader = new ClassLoader(Server.class.getClassLoader())
{
public String toString()
{
return "MiddleLoader";
}
};
ClassLoader loader = new ClassLoader(middleLoader)
{
public String toString()
{
return "TopLoader";
}
};
server.addBean(new ClassLoaderDump(loader));
StringBuilder out = new StringBuilder();
server.dump(out);
String dump = out.toString();
assertThat(dump,containsString("+- TopLoader"));
assertThat(dump,containsString("| +- MiddleLoader"));
assertThat(dump,containsString("| +- "+Server.class.getClassLoader()));
assertThat(dump,containsString("+> "+Server.class.getClassLoader()));
}
@Test
public void testDumpable() throws Exception
{
Server server = new Server();
ClassLoader middleLoader = new DumpableClassLoader(Server.class.getClassLoader());
ClassLoader loader = new ClassLoader(middleLoader)
{
public String toString()
{
return "TopLoader";
}
};
server.addBean(new ClassLoaderDump(loader));
StringBuilder out = new StringBuilder();
server.dump(out);
String dump = out.toString();
assertThat(dump,containsString("+- TopLoader"));
assertThat(dump,containsString("| +- DumpableClassLoader"));
assertThat(dump,not(containsString("| +- "+Server.class.getClassLoader())));
assertThat(dump,containsString("+> "+Server.class.getClassLoader()));
}
public static class DumpableClassLoader extends ClassLoader implements Dumpable
{
public DumpableClassLoader(ClassLoader parent)
{
super(parent);
}
@Override
public String dump()
{
return "DumpableClassLoader";
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(dump()).append('\n');
}
public String toString()
{
return "DumpableClassLoader";
}
}
@Test
public void testUrlClassLoaders() throws Exception
{
Server server = new Server();
ClassLoader middleLoader = new URLClassLoader(new URL[]
{new URL("file:/one"),new URL("file:/two"),new URL("file:/three"),},
Server.class.getClassLoader())
{
public String toString()
{
return "MiddleLoader";
}
};
ClassLoader loader = new URLClassLoader(new URL[]
{new URL("file:/ONE"),new URL("file:/TWO"),new URL("file:/THREE"),},
middleLoader)
{
public String toString()
{
return "TopLoader";
}
};
server.addBean(new ClassLoaderDump(loader));
StringBuilder out = new StringBuilder();
server.dump(out);
String dump = out.toString();
System.err.println(dump);
assertThat(dump,containsString("+- TopLoader"));
assertThat(dump,containsString("| +- file:/ONE"));
assertThat(dump,containsString("| +- file:/TWO"));
assertThat(dump,containsString("| +- file:/THREE"));
assertThat(dump,containsString("| +- MiddleLoader"));
assertThat(dump,containsString("| +- file:/one"));
assertThat(dump,containsString("| +- file:/two"));
assertThat(dump,containsString("| +- file:/three"));
assertThat(dump,containsString("| +- "+Server.class.getClassLoader()));
assertThat(dump,containsString("+> "+Server.class.getClassLoader()));
}
}

View File

@ -120,9 +120,9 @@ public class HttpInputAsyncStateTest
}
@Override
public boolean onReadPossible()
public boolean onDataAvailable()
{
boolean wake = super.onReadPossible();
boolean wake = super.onDataAvailable();
__history.add("onReadPossible "+wake);
return wake;
}

View File

@ -42,19 +42,19 @@ public class HttpInputTest
@Override
public void onError(Throwable t)
{
_history.add("onError:" + t);
_history.add("l.onError:" + t);
}
@Override
public void onDataAvailable() throws IOException
{
_history.add("onDataAvailable");
_history.add("l.onDataAvailable");
}
@Override
public void onAllDataRead() throws IOException
{
_history.add("onAllDataRead");
_history.add("l.onAllDataRead");
}
};
private HttpInput _in;
@ -99,21 +99,28 @@ public class HttpInputTest
@Override
public void onReadUnready()
{
_history.add("unready");
_history.add("s.onReadUnready");
super.onReadUnready();
}
@Override
public boolean onReadPossible()
{
_history.add("onReadPossible");
_history.add("s.onReadPossible");
return super.onReadPossible();
}
@Override
public boolean onDataAvailable()
{
_history.add("s.onDataAvailable");
return super.onDataAvailable();
}
@Override
public boolean onReadReady()
{
_history.add("ready");
_history.add("s.onReadReady");
return super.onReadReady();
}
})
@ -387,17 +394,17 @@ public class HttpInputTest
{
_in.setReadListener(_listener);
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
}
@ -406,21 +413,21 @@ public class HttpInputTest
{
_in.setReadListener(_listener);
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.addContent(new TContent("AB"));
_fillAndParseSimulate.add("CD");
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.run();
Assert.assertThat(_history.poll(), Matchers.equalTo("onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.equalTo("l.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));
@ -434,7 +441,7 @@ public class HttpInputTest
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 1"));
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.read(), Matchers.equalTo((int)'C'));
@ -446,7 +453,7 @@ public class HttpInputTest
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
}
@ -455,13 +462,13 @@ public class HttpInputTest
{
_in.setReadListener(_listener);
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.eof();
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));
Assert.assertThat(_in.isFinished(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.read(), Matchers.equalTo(-1));
@ -474,22 +481,22 @@ public class HttpInputTest
{
_in.setReadListener(_listener);
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.addContent(new TContent("AB"));
_fillAndParseSimulate.add("_EOF_");
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.run();
Assert.assertThat(_history.poll(), Matchers.equalTo("onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.equalTo("l.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));
@ -504,7 +511,7 @@ public class HttpInputTest
Assert.assertThat(_in.isFinished(), Matchers.equalTo(false));
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 1"));
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isFinished(), Matchers.equalTo(false));
@ -521,21 +528,21 @@ public class HttpInputTest
{
_in.setReadListener(_listener);
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(false));
Assert.assertThat(_history.poll(), Matchers.equalTo("produceContent 0"));
Assert.assertThat(_history.poll(), Matchers.equalTo("unready"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onReadUnready"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.failed(new TimeoutException());
Assert.assertThat(_history.poll(), Matchers.equalTo("onReadPossible"));
Assert.assertThat(_history.poll(), Matchers.equalTo("s.onDataAvailable"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
_in.run();
Assert.assertThat(_in.isFinished(), Matchers.equalTo(true));
Assert.assertThat(_history.poll(), Matchers.equalTo("onError:java.util.concurrent.TimeoutException"));
Assert.assertThat(_history.poll(), Matchers.equalTo("l.onError:java.util.concurrent.TimeoutException"));
Assert.assertThat(_history.poll(), Matchers.nullValue());
Assert.assertThat(_in.isReady(), Matchers.equalTo(true));

View File

@ -143,7 +143,9 @@ public class HttpTrailersTest
{
client.setSoTimeout(5000);
String request = "" +
try
{
String request = "" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
@ -151,13 +153,18 @@ public class HttpTrailersTest
"0\r\n" +
"Trailer: " + new String(huge) + "\r\n" +
"\r\n";
OutputStream output = client.getOutputStream();
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
OutputStream output = client.getOutputStream();
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
catch(Exception e)
{
// May be thrown if write fails and error handling is aborted
}
}
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
@ -57,16 +58,17 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.Utf8Appendable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
@ -97,6 +99,10 @@ public class RequestTest
_server.addConnector(_connector);
_handler = new RequestHandler();
_server.setHandler(_handler);
ErrorHandler errors = new ErrorHandler();
errors.setShowStacks(true);
_server.addBean(errors);
_server.start();
}
@ -115,13 +121,19 @@ public class RequestTest
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
Map<String,String[]> map = null;
//do the parse
map = request.getParameterMap();
assertEquals("aaa"+Utf8Appendable.REPLACEMENT+"bbb",map.get("param")[0]);
assertEquals("value",map.get("other")[0]);
return true;
try
{
Map<String, String[]> map = null;
// do the parse
map = request.getParameterMap();
return false;
}
catch(BadMessageException e)
{
// Should be able to retrieve the raw query
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%ZZbbb&other=value");
}
}
};
@ -135,7 +147,6 @@ public class RequestTest
String responses=_connector.getResponse(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
@ -265,7 +276,7 @@ public class RequestTest
"Accept-Language: XX;q=0, en-au;q=0.9\r\n"+
"\r\n";
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@ -402,8 +413,18 @@ public class RequestTest
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
String value=request.getParameter("param");
return value.startsWith("aaa") && value.endsWith("bb");
try
{
// This throws an exception if attempted
request.getParameter("param");
return false;
}
catch(BadMessageException e)
{
// Should still be able to get the raw query.
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%E7bbb");
}
}
};
@ -523,7 +544,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
int i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -537,7 +558,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -549,7 +570,7 @@ public class RequestTest
"GET http://myhost:8888/ HTTP/1.0\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -562,7 +583,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -577,7 +598,7 @@ public class RequestTest
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
@ -591,7 +612,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
@ -605,7 +626,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
@ -619,7 +640,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
@ -635,7 +656,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
@ -651,7 +672,7 @@ public class RequestTest
"x-forwarded-proto: https\n"+
"\n");
i=0;
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
@ -699,7 +720,7 @@ public class RequestTest
Log.getRootLogger().debug("test l={}",l);
String response = _connector.getResponse(request);
Log.getRootLogger().debug(response);
assertThat(response, Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
assertEquals(l,length.get());
content+="x";
}
@ -728,7 +749,7 @@ public class RequestTest
"\r\n"+
content;
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@Test
@ -752,7 +773,7 @@ public class RequestTest
"\r\n"+
content;
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@Test
@ -778,7 +799,7 @@ public class RequestTest
"\r\n"+
content;
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@Test
@ -806,7 +827,7 @@ public class RequestTest
"\r\n"+
content;
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@Test
@ -834,7 +855,7 @@ public class RequestTest
"\r\n"+
content;
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, containsString(" 200 OK"));
}
@ -1026,8 +1047,8 @@ public class RequestTest
"Host: myhost\n"+
"Connection: close\n"+
"\n");
assertThat(response,Matchers.containsString(" 302 Found"));
assertThat(response,Matchers.containsString("Location: http://myhost/foo"));
assertThat(response, containsString(" 302 Found"));
assertThat(response, containsString("Location: http://myhost/foo"));
}
@Test
@ -1097,9 +1118,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
"GET / HTTP/1.1\n"+
@ -1107,9 +1128,9 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
"GET / HTTP/1.1\n"+
@ -1118,18 +1139,18 @@ public class RequestTest
"\n"
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
"GET / HTTP/1.0\n"+
"Host: whatever\n"+
"\n"
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
"GET / HTTP/1.0\n"+
@ -1137,8 +1158,8 @@ public class RequestTest
"Connection: Other, close\n"+
"\n"
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
"GET / HTTP/1.0\n"+
@ -1147,9 +1168,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: keep-alive"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: keep-alive"));
assertThat(response, containsString("Hello World"));
_handler._checker = new RequestTester()
{
@ -1169,10 +1190,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: TE"));
assertThat(response, Matchers.containsString("Connection: Other"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: TE"));
assertThat(response, containsString("Connection: Other"));
response=_connector.getResponse(
"GET / HTTP/1.1\n"+
@ -1180,9 +1200,9 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
}
@Test
@ -1427,11 +1447,10 @@ public class RequestTest
{
try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
LOG.info("Expecting maxFormKeys limit and Closing HttpParser exceptions...");
// Expecting maxFormKeys limit and Closing HttpParser exceptions...
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
StringBuilder buf = new StringBuilder(4000000);
buf.append("a=b");
@ -1439,7 +1458,7 @@ public class RequestTest
File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
if (evil_keys.exists())
{
LOG.info("Using real evil keys!");
// Using real evil keys!
try (BufferedReader in = new BufferedReader(new FileReader(evil_keys)))
{
String key=null;
@ -1474,8 +1493,11 @@ public class RequestTest
buf;
long start=System.currentTimeMillis();
String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString("IllegalStateException"));
String rawResponse = _connector.getResponse(request);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis();
assertTrue((now-start)<5000);
}
@ -1515,8 +1537,11 @@ public class RequestTest
buf;
long start=System.currentTimeMillis();
String response = _connector.getResponse(request);
assertTrue(response.contains("IllegalStateException"));
String rawResponse = _connector.getResponse(request);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis();
assertTrue((now-start)<5000);
}

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -34,10 +36,12 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.HttpCookie;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -53,6 +57,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@ -65,8 +70,8 @@ import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
import org.eclipse.jetty.server.session.NullSessionDataStore;
import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.server.session.SessionData;
@ -122,7 +127,7 @@ public class ResponseTest
public InetSocketAddress getLocalAddress()
{
return LOCALADDRESS;
}
}
};
_channel = new HttpChannel(connector, new HttpConfiguration(), endp, new HttpTransport()
{
@ -174,6 +179,7 @@ public class ResponseTest
_server.join();
}
@SuppressWarnings("InjectedReferences") // to allow for invalid encoding strings in this testcase
@Test
public void testContentType() throws Exception
{
@ -513,9 +519,6 @@ public class ResponseTest
assertEquals("foo2/bar2;charset=utf-8", response.getContentType());
}
@Test
public void testContentTypeWithOther() throws Exception
{
@ -881,9 +884,59 @@ public class ResponseTest
String set = response.getHttpFields().get("Set-Cookie");
assertEquals("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly", set);
}
@Test
public void testAddCookieComplianceRFC2965() throws Exception
{
Response response = getResponse();
response.getHttpChannel().getHttpConfiguration().setCookieCompliance(CookieCompliance.RFC2965);
Cookie cookie = new Cookie("name", "value");
cookie.setDomain("domain");
cookie.setPath("/path");
cookie.setSecure(true);
cookie.setComment("comment__HTTP_ONLY__");
response.addCookie(cookie);
String set = response.getHttpFields().get("Set-Cookie");
assertEquals("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
}
/**
* Testing behavior documented in Chrome bug
* https://bugs.chromium.org/p/chromium/issues/detail?id=700618
*/
@Test
public void testAddCookie_JavaxServletHttp() throws Exception
{
Response response = getResponse();
Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", UTF_8.toString()));
cookie.setPath("/secure");
response.addCookie(cookie);
String set = response.getHttpFields().get("Set-Cookie");
assertEquals("foo=bar%3Bbaz;Path=/secure", set);
}
/**
* Testing behavior documented in Chrome bug
* https://bugs.chromium.org/p/chromium/issues/detail?id=700618
*/
@Test
public void testAddCookie_JavaNet() throws Exception
{
HttpCookie cookie = new HttpCookie("foo", URLEncoder.encode("bar;baz", UTF_8.toString()));
cookie.setPath("/secure");
assertEquals("foo=\"bar%3Bbaz\";$Path=\"/secure\"", cookie.toString());
}
@Test
public void testCookiesWithReset() throws Exception
@ -910,7 +963,7 @@ public class ResponseTest
assertNotNull(set);
ArrayList<String> list = Collections.list(set);
assertEquals(2, list.size());
assertTrue(list.contains("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
assertTrue(list.contains("name=value;Path=/path;Domain=domain;Secure;HttpOnly"));
assertTrue(list.contains("name2=value2;Path=/path;Domain=domain"));
//get rid of the cookies
@ -934,23 +987,23 @@ public class ResponseTest
}
@Test
public void testSetCookie() throws Exception
public void testSetRFC2965Cookie() throws Exception
{
Response response = _channel.getResponse();
HttpFields fields = response.getHttpFields();
response.addSetCookie("null",null,null,null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("null",null,null,null,-1,null,false,false,-1);
assertEquals("null=",fields.get("Set-Cookie"));
fields.clear();
response.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("minimal","value",null,null,-1,null,false,false,-1);
assertEquals("minimal=value",fields.get("Set-Cookie"));
fields.clear();
//test cookies with same name, domain and path
response.addSetCookie("everything","something","domain","path",0,"noncomment",true,true,0);
response.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","something","domain","path",0,"noncomment",true,true,0);
response.addSetRFC2965Cookie("everything","value","domain","path",0,"comment",true,true,0);
Enumeration<String> e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=something;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=noncomment",e.nextElement());
@ -961,8 +1014,8 @@ public class ResponseTest
//test cookies with same name, different domain
fields.clear();
response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
response.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","other","domain1","path",0,"blah",true,true,0);
response.addSetRFC2965Cookie("everything","value","domain2","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
@ -972,8 +1025,8 @@ public class ResponseTest
//test cookies with same name, same path, one with domain, one without
fields.clear();
response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
response.addSetCookie("everything","value","","path",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","other","domain1","path",0,"blah",true,true,0);
response.addSetRFC2965Cookie("everything","value","","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
@ -984,8 +1037,8 @@ public class ResponseTest
//test cookies with same name, different path
fields.clear();
response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
response.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","other","domain1","path1",0,"blah",true,true,0);
response.addSetRFC2965Cookie("everything","value","domain1","path2",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
@ -995,8 +1048,8 @@ public class ResponseTest
//test cookies with same name, same domain, one with path, one without
fields.clear();
response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
response.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","other","domain1","path1",0,"blah",true,true,0);
response.addSetRFC2965Cookie("everything","value","domain1","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
@ -1006,8 +1059,8 @@ public class ResponseTest
//test cookies same name only, no path, no domain
fields.clear();
response.addSetCookie("everything","other","","",0,"blah",true,true,0);
response.addSetCookie("everything","value","","",0,"comment",true,true,0);
response.addSetRFC2965Cookie("everything","other","","",0,"blah",true,true,0);
response.addSetRFC2965Cookie("everything","value","","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
@ -1015,44 +1068,230 @@ public class ResponseTest
assertFalse(e.hasMoreElements());
fields.clear();
response.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
response.addSetRFC2965Cookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
String setCookie=fields.get("Set-Cookie");
assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
fields.clear();
response.addSetCookie("name","value",null,null,-1,null,false,false,0);
response.addSetRFC2965Cookie("name","value",null,null,-1,null,false,false,0);
setCookie=fields.get("Set-Cookie");
assertEquals(-1,setCookie.indexOf("Version="));
fields.clear();
response.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0);
response.addSetRFC2965Cookie("name","v a l u e",null,null,-1,null,false,false,0);
setCookie=fields.get("Set-Cookie");
fields.clear();
response.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.get("Set-Cookie"));
fields.clear();
response.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
response.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
response.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("name","value","domain",null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("name","other","domain",null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("name","more","domain",null,-1,null,false,false,-1);
e = fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertThat(e.nextElement(), Matchers.startsWith("name=value"));
assertThat(e.nextElement(), Matchers.startsWith("name=other"));
assertThat(e.nextElement(), Matchers.startsWith("name=more"));
response.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
response.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("foo","bar","domain",null,-1,null,false,false,-1);
response.addSetRFC2965Cookie("foo","bob","domain",null,-1,null,false,false,-1);
assertThat(fields.get("Set-Cookie"), Matchers.startsWith("name=value"));
fields.clear();
response.addSetCookie("name","value%=",null,null,-1,null,false,false,0);
response.addSetRFC2965Cookie("name","value%=",null,null,-1,null,false,false,0);
setCookie=fields.get("Set-Cookie");
assertEquals("name=value%=",setCookie);
}
@Test
public void testSetRFC6265Cookie() throws Exception
{
Response response = _channel.getResponse();
HttpFields fields = response.getHttpFields();
response.addSetRFC6265Cookie("null",null,null,null,-1,false,false);
assertEquals("null=",fields.get("Set-Cookie"));
fields.clear();
response.addSetRFC6265Cookie("minimal","value",null,null,-1,false,false);
assertEquals("minimal=value",fields.get("Set-Cookie"));
fields.clear();
//test cookies with same name, domain and path
response.addSetRFC6265Cookie("everything","something","domain","path",0,true,true);
response.addSetRFC6265Cookie("everything","value","domain","path",0,true,true);
Enumeration<String> e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=something;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.get("Expires"));
assertFalse(e.hasMoreElements());
//test cookies with same name, different domain
fields.clear();
response.addSetRFC6265Cookie("everything","other","domain1","path",0,true,true);
response.addSetRFC6265Cookie("everything","value","domain2","path",0,true,true);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same path, one with domain, one without
fields.clear();
response.addSetRFC6265Cookie("everything","other","domain1","path",0,true,true);
response.addSetRFC6265Cookie("everything","value","","path",0,true,true);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, different path
fields.clear();
response.addSetRFC6265Cookie("everything","other","domain1","path1",0,true,true);
response.addSetRFC6265Cookie("everything","value","domain1","path2",0,true,true);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same domain, one with path, one without
fields.clear();
response.addSetRFC6265Cookie("everything","other","domain1","path1",0,true,true);
response.addSetRFC6265Cookie("everything","value","domain1","",0,true,true);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies same name only, no path, no domain
fields.clear();
response.addSetRFC6265Cookie("everything","other","","",0,true,true);
response.addSetRFC6265Cookie("everything","value","","",0,true,true);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertEquals("everything=value;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly",e.nextElement());
assertFalse(e.hasMoreElements());
String badNameExamples[] = {
"\"name\"",
"name\t",
"na me",
"name\u0082",
"na\tme",
"na;me",
"{name}",
"[name]",
"\""
};
for (String badNameExample : badNameExamples)
{
fields.clear();
try
{
response.addSetRFC6265Cookie(badNameExample, "value", null, "/", 1, true, true);
}
catch (IllegalArgumentException ex)
{
// System.err.printf("%s: %s%n", ex.getClass().getSimpleName(), ex.getMessage());
assertThat("Testing bad name: [" + badNameExample + "]", ex.getMessage(),
allOf(containsString("RFC6265"), containsString("RFC2616")));
}
}
String badValueExamples[] = {
"va\tlue",
"\t",
"value\u0000",
"val\u0082ue",
"va lue",
"va;lue",
"\"value",
"value\"",
"val\\ue",
"val\"ue",
"\""
};
for (String badValueExample : badValueExamples)
{
fields.clear();
try
{
response.addSetRFC6265Cookie("name", badValueExample, null, "/", 1, true, true);
}
catch (IllegalArgumentException ex)
{
// System.err.printf("%s: %s%n", ex.getClass().getSimpleName(), ex.getMessage());
assertThat("Testing bad value [" + badValueExample + "]", ex.getMessage(), Matchers.containsString("RFC6265"));
}
}
String goodNameExamples[] = {
"name",
"n.a.m.e",
"na-me",
"+name",
"na*me",
"na$me",
"#name"
};
for (String goodNameExample : goodNameExamples)
{
fields.clear();
response.addSetRFC6265Cookie(goodNameExample, "value", null, "/", 1, true, true);
// should not throw an exception
}
String goodValueExamples[] = {
"value",
"",
null,
"val=ue",
"val-ue",
"val/ue",
"v.a.l.u.e"
};
for (String goodValueExample : goodValueExamples)
{
fields.clear();
response.addSetRFC6265Cookie("name", goodValueExample, null, "/", 1, true, true);
// should not throw an exception
}
fields.clear();
response.addSetRFC6265Cookie("name","value","domain",null,-1,false,false);
response.addSetRFC6265Cookie("name","other","domain",null,-1,false,false);
response.addSetRFC6265Cookie("name","more","domain",null,-1,false,false);
e = fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertThat(e.nextElement(), Matchers.startsWith("name=value"));
assertThat(e.nextElement(), Matchers.startsWith("name=other"));
assertThat(e.nextElement(), Matchers.startsWith("name=more"));
response.addSetRFC6265Cookie("foo","bar","domain",null,-1,false,false);
response.addSetRFC6265Cookie("foo","bob","domain",null,-1,false,false);
assertThat(fields.get("Set-Cookie"), Matchers.startsWith("name=value"));
}
private Response getResponse()
{
_channel.recycle();

View File

@ -447,7 +447,11 @@ public class ContextHandlerTest
Assert.assertTrue(handler.isProtectedTarget("/ABC/7777"));
}
@Test
public void testIsShutdown() {
ContextHandler handler = new ContextHandler();
Assert.assertEquals(false, handler.isShutdown());
}
private void checkResourcePathsForExampleWebApp(String root) throws IOException
{

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@ -54,10 +55,63 @@ public class FileSessionManagerTest
_log.setHideStacks(_stacks);
}
@After
public void after()
{
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
if (testDir.exists())
FS.ensureEmpty(testDir);
}
@Test
public void testDangerousSessionIdRemoval() throws Exception
{
String expectedFilename = "_0.0.0.0_dangerFile";
File targetFile = MavenTestingUtils.getTargetFile(expectedFilename);
try
{
Server server = new Server();
SessionHandler handler = new SessionHandler();
handler.setServer(server);
final DefaultSessionIdManager idmgr = new DefaultSessionIdManager(server);
idmgr.setServer(server);
server.setSessionIdManager(idmgr);
FileSessionDataStore ds = new FileSessionDataStore();
ds.setDeleteUnrestorableFiles(true);
DefaultSessionCache ss = new DefaultSessionCache(handler);
handler.setSessionCache(ss);
ss.setSessionDataStore(ds);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
handler.setSessionIdManager(idmgr);
handler.start();
//Create a file that is in the parent dir of the session storeDir
targetFile.createNewFile();
Assert.assertTrue("File should exist!", MavenTestingUtils.getTargetFile(expectedFilename).exists());
//Verify that passing in a relative filename outside of the storedir does not lead
//to deletion of file (needs deleteUnrecoverableFiles(true))
Session session = handler.getSession("../_0.0.0.0_dangerFile");
Assert.assertTrue(session == null);
Assert.assertTrue("File should exist!", MavenTestingUtils.getTargetFile(expectedFilename).exists());
}
finally
{
if (targetFile.exists())
IO.delete(targetFile);
}
}
@Test
public void testDeleteOfOlderFiles() throws Exception
{
Server server = new Server();
SessionHandler handler = new SessionHandler();
@ -67,32 +121,53 @@ public class FileSessionManagerTest
server.setSessionIdManager(idmgr);
FileSessionDataStore ds = new FileSessionDataStore();
ds.setDeleteUnrestorableFiles(true);
ds.setDeleteUnrestorableFiles(false); //turn off deletion of unreadable session files
DefaultSessionCache ss = new DefaultSessionCache(handler);
handler.setSessionCache(ss);
ss.setSessionDataStore(ds);
//manager.setLazyLoad(true);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
testDir.mkdirs();
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
handler.setSessionIdManager(idmgr);
handler.start();
//create a bunch of older files for same session abc
String name1 = "100__0.0.0.0_abc";
File f1 = new File(testDir, name1);
if (f1.exists())
Assert.assertTrue(f1.delete());
f1.createNewFile();
Thread.sleep(1100);
//Create a file that is in the parent dir of the session storeDir
String expectedFilename = "_0.0.0.0_dangerFile";
MavenTestingUtils.getTargetFile(expectedFilename).createNewFile();
Assert.assertTrue("File should exist!", MavenTestingUtils.getTargetFile(expectedFilename).exists());
//Verify that passing in the relative filename of an unrecoverable session does not lead
//to deletion of file outside the session dir (needs deleteUnrecoverableFiles(true))
Session session = handler.getSession("../_0.0.0.0_dangerFile");
Assert.assertTrue(session == null);
Assert.assertTrue("File should exist!", MavenTestingUtils.getTargetFile(expectedFilename).exists());
String name2 = "101__0.0.0.0_abc";
File f2 = new File(testDir, name2);
if (f2.exists())
Assert.assertTrue(f2.delete());
f2.createNewFile();
Thread.sleep(1100);
String name3 = "102__0.0.0.0_abc";
File f3 = new File(testDir, name3);
if (f3.exists())
Assert.assertTrue(f3.delete());
f3.createNewFile();
Thread.sleep(1100);
Session session = handler.getSession("abc");
Assert.assertTrue(!f1.exists());
Assert.assertTrue(!f2.exists());
Assert.assertTrue(f3.exists());
}
@Test
public void testValidSessionIdRemoval() throws Exception
public void testUnrestorableFileRemoval() throws Exception
{
Server server = new Server();
SessionHandler handler = new SessionHandler();
@ -105,7 +180,7 @@ public class FileSessionManagerTest
FileSessionDataStore ds = new FileSessionDataStore();
ss.setSessionDataStore(ds);
handler.setSessionCache(ss);
ds.setDeleteUnrestorableFiles(true);
ds.setDeleteUnrestorableFiles(true); //invalid file will be removed
handler.setSessionIdManager(idmgr);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
@ -114,7 +189,7 @@ public class FileSessionManagerTest
ds.setStoreDir(testDir);
handler.start();
String expectedFilename = "_0.0.0.0_validFile123";
String expectedFilename = (System.currentTimeMillis()+ 10000)+"__0.0.0.0_validFile123";
Assert.assertTrue(new File(testDir, expectedFilename).createNewFile());
@ -129,8 +204,7 @@ public class FileSessionManagerTest
public void testHashSession() throws Exception
{
File testDir = MavenTestingUtils.getTargetTestingDir("saved");
IO.delete(testDir);
testDir.mkdirs();
FS.ensureEmpty(testDir);
Server server = new Server();
SessionHandler handler = new SessionHandler();
@ -212,9 +286,8 @@ public class FileSessionManagerTest
DefaultSessionCache ss = new DefaultSessionCache(handler);
handler.setSessionCache(ss);
ss.setSessionDataStore(ds);
//manager.setLazyLoad(true);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
testDir.mkdirs();
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
handler.setSessionIdManager(idmgr);
handler.start();

View File

@ -44,13 +44,15 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public class SlowClientsTest
{
private Logger logger = Log.getLogger(getClass());
@Test
@Test(timeout = 10000)
public void testSlowClientsWithSmallThreadPool() throws Exception
{
File keystore = MavenTestingUtils.getTestResourceFile("keystore");
@ -59,8 +61,8 @@ public class SlowClientsTest
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
int maxThreads = 8;
int contentLength = 32 * 1024 * 1024;
int maxThreads = 6;
int contentLength = 8 * 1024 * 1024;
QueuedThreadPool serverThreads = new QueuedThreadPool(maxThreads);
serverThreads.setDetailedDump(true);
Server server = new Server(serverThreads);

View File

@ -729,6 +729,8 @@ public class DefaultServletTest
String response = connector.getResponse("GET /context/data0.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Content-Length: 12", response);
assertResponseNotContains("Extra Info", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseNotContains("Content-Type: text/plain;charset=utf-8", response);
server.stop();
context.addFilter(OutputFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
@ -738,6 +740,7 @@ public class DefaultServletTest
assertResponseContains("Content-Length: 2", response); // 20 something long
assertResponseContains("Extra Info", response);
assertResponseNotContains("Content-Length: 12", response);
assertResponseContains("Content-Type: text/plain;charset=utf-8", response);
server.stop();
context.getServletHandler().setFilterMappings(new FilterMapping[]{});
@ -1226,6 +1229,7 @@ public class DefaultServletTest
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
response.getOutputStream().println("Extra Info");
response.setCharacterEncoding("utf-8");
chain.doFilter(request, response);
}

View File

@ -71,6 +71,8 @@ import org.eclipse.jetty.util.log.Logger;
* secondary resources are pushed to the client, unless the request carries
* {@code If-xxx} header that hint that the client has the resources in its
* cache.</p>
* <p>If the init param useQueryInKey is set, then the query string is used as
* as part of the key to identify a resource</p>
*/
@ManagedObject("Push cache based on the HTTP 'Referer' header")
public class PushCacheFilter implements Filter
@ -83,6 +85,7 @@ public class PushCacheFilter implements Filter
private long _associatePeriod = 4000L;
private int _maxAssociations = 16;
private long _renew = System.nanoTime();
private boolean _useQueryInKey;
@Override
public void init(FilterConfig config) throws ServletException
@ -104,6 +107,8 @@ public class PushCacheFilter implements Filter
for (String p : StringUtil.csvSplit(ports))
_ports.add(Integer.parseInt(p));
_useQueryInKey = Boolean.parseBoolean(config.getInitParameter("useQueryInKey"));
// Expose for JMX.
config.getServletContext().setAttribute(config.getFilterName(), this);
@ -162,7 +167,7 @@ public class PushCacheFilter implements Filter
String path = request.getRequestURI();
String query = request.getQueryString();
if (query != null)
if (_useQueryInKey && query != null)
path += "?" + query;
if (referrer != null)
{
@ -179,7 +184,7 @@ public class PushCacheFilter implements Filter
{
if (HttpMethod.GET.is(request.getMethod()))
{
String referrerPath = referrerURI.getPath();
String referrerPath = _useQueryInKey?referrerURI.getPathQuery():referrerURI.getPath();
if (referrerPath == null)
referrerPath = "/";
if (referrerPath.startsWith(request.getContextPath() + "/"))

View File

@ -29,7 +29,6 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -316,88 +315,53 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
for (int i=offset;i<end;i++)
{
char c=query.charAt(i);
try
switch (c)
{
switch (c)
{
case '&':
value = buffer.toReplacedString();
buffer.reset();
if (key != null)
{
map.add(key,value);
}
else if (value!=null&&value.length()>0)
{
map.add(value,"");
}
key = null;
value=null;
break;
case '&':
value = buffer.toReplacedString();
buffer.reset();
if (key != null)
{
map.add(key,value);
}
else if (value!=null&&value.length()>0)
{
map.add(value,"");
}
key = null;
value=null;
break;
case '=':
if (key!=null)
{
buffer.append(c);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
if (i+2<end)
{
if ('u'==query.charAt(i+1))
{
i++;
if (i+4<end)
{
char top=query.charAt(++i);
char hi=query.charAt(++i);
char lo=query.charAt(++i);
char bot=query.charAt(++i);
buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
}
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
}
else
{
char hi=query.charAt(++i);
char lo=query.charAt(++i);
buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
}
}
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
break;
default:
case '=':
if (key!=null)
{
buffer.append(c);
break;
}
}
catch(NotUtf8Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
catch(NumberFormatException e)
{
buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
LOG.warn(e.toString());
LOG.debug(e);
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
if (i+2<end)
{
char hi=query.charAt(++i);
char lo=query.charAt(++i);
buffer.append(decodeHexByte(hi,lo));
}
else
{
throw new Utf8Appendable.NotUtf8Exception("Incomplete % encoding");
}
break;
default:
buffer.append(c);
break;
}
}
@ -472,26 +436,8 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
case '%':
int code0=in.read();
if ('u'==code0)
{
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
}
int code1=in.read();
buffer.append(decodeHexChar(code0,code1));
break;
default:
@ -537,99 +483,51 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
int totalLength=0;
while ((b=in.read())>=0)
{
try
switch ((char) b)
{
switch ((char) b)
{
case '&':
value = buffer.toReplacedString();
buffer.reset();
if (key != null)
{
map.add(key,value);
}
else if (value!=null&&value.length()>0)
{
map.add(value,"");
}
key = null;
value=null;
if (maxKeys>0 && map.size()>maxKeys)
case '&':
value = buffer.toReplacedString();
buffer.reset();
if (key != null)
{
map.add(key,value);
}
else if (value!=null&&value.length()>0)
{
map.add(value,"");
}
key = null;
value=null;
if (maxKeys>0 && map.size()>maxKeys)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
break;
break;
case '=':
if (key!=null)
{
buffer.append((byte)b);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
int code0=in.read();
boolean decoded=false;
if ('u'==code0)
{
code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
if (code0>=0)
{
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
{
buffer.getStringBuilder().append(Character.toChars
((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
decoded=true;
}
}
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
{
buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
decoded=true;
}
}
if (!decoded)
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
break;
default:
case '=':
if (key!=null)
{
buffer.append((byte)b);
break;
}
}
catch(NotUtf8Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
catch(NumberFormatException e)
{
buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
LOG.warn(e.toString());
LOG.debug(e);
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
char code0= (char) in.read();
char code1= (char) in.read();
buffer.append(decodeHexByte(code0,code1));
break;
default:
buffer.append((byte)b);
break;
}
if (maxLength>=0 && (++totalLength > maxLength))
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
throw new IllegalStateException("Form is too large");
}
if (key != null)
@ -768,27 +666,8 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
break;
case '%':
int code0=in.read();
if ('u'==code0)
{
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
}
int code1=in.read();
output.write(decodeHexChar(code0,code1));
break;
default:
output.write(c);
@ -797,7 +676,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
totalLength++;
if (maxLength>=0 && totalLength > maxLength)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
throw new IllegalStateException("Form is too large");
}
size=output.size();
@ -874,42 +753,10 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
if ((i+2)<length)
{
try
{
if ('u'==encoded.charAt(offset+i+1))
{
if((i+5)<length)
{
int o=offset+i+2;
i+=5;
String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
buffer.getStringBuffer().append(unicode);
}
else
{
i=length;
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
}
else
{
int o=offset+i+1;
i+=2;
byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
buffer.append(b);
}
}
catch(NotUtf8Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
catch(NumberFormatException e)
{
LOG.warn(e.toString());
LOG.debug(e);
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
int o=offset+i+1;
i+=2;
byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
buffer.append(b);
}
else
{
@ -973,44 +820,15 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
{
if(i+2<length)
{
try
{
if ('u'==encoded.charAt(offset+i+1))
{
if (i+6<length)
{
int o=offset+i+2;
i+=6;
String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
byte[] reencoded = unicode.getBytes(charset);
System.arraycopy(reencoded,0,ba,n,reencoded.length);
n+=reencoded.length;
}
else
{
ba[n++] = (byte)'?';
i=length;
}
}
else
{
int o=offset+i+1;
i+=3;
ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
n++;
}
}
catch(Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
ba[n++] = (byte)'?';
}
int o=offset+i+1;
i+=3;
ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
n++;
}
else
{
ba[n++] = (byte)'?';
i=length;
ba[n++] = (byte)'?';
i=length;
}
}
else if (c=='+')
@ -1046,7 +864,30 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
return buffer.toString();
}
}
private static char decodeHexChar(int hi, int lo)
{
try
{
return (char) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + (char) hi + (char) lo + "'");
}
}
private static byte decodeHexByte(char hi, char lo)
{
try
{
return (byte) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + hi + lo + "'");
}
}
/* ------------------------------------------------------------ */

View File

@ -18,7 +18,9 @@
package org.eclipse.jetty.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
@ -26,17 +28,16 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/* ------------------------------------------------------------ */
/** Util meta Tests.
*
/**
* URL Encoding / Decoding Tests
*/
public class URLEncodedTest
{
/* -------------------------------------------------------------- */
static
{
/*
@ -46,9 +47,10 @@ public class URLEncodedTest
System.setProperty("org.eclipse.jetty.util.UrlEncoding.charset", StringUtil.__ISO_8859_1);
*/
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
/* -------------------------------------------------------------- */
@Test
public void testUrlEncoded()
{
@ -120,82 +122,8 @@ public class URLEncodedTest
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded encode","Name8=xx%2C++yy++%2Czz", url_encoded.encode());
assertEquals("encoded get", url_encoded.getString("Name8"),"xx, yy ,zz");
url_encoded.clear();
url_encoded.decode("Name11=%u30EDxxVerdi+%C6+og+2zz", StandardCharsets.ISO_8859_1);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "?xxVerdi \u00c6 og 2zz",url_encoded.getString("Name11"));
url_encoded.clear();
url_encoded.decode("Name12=%u30EDxxVerdi+%2F+og+2zz", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", url_encoded.getString("Name12"),"\u30edxxVerdi / og 2zz");
url_encoded.clear();
url_encoded.decode("Name14=%uXXXXa%GGb%+%c%+%d", StandardCharsets.ISO_8859_1);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get","?a?b?c?d", url_encoded.getString("Name14"));
url_encoded.clear();
url_encoded.decode("Name14=%uXXXX%GG%+%%+%", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd\ufffd",url_encoded.getString("Name14"));
/* Not every jvm supports this encoding */
if (java.nio.charset.Charset.isSupported("SJIS"))
{
url_encoded.clear();
url_encoded.decode("Name9=%u30ED%83e%83X%83g", Charset.forName("SJIS")); // "Test" in Japanese Katakana
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\u30ed\u30c6\u30b9\u30c8", url_encoded.getString("Name9"));
}
else
assertTrue("Charset SJIS not supported by jvm", true);
}
/* -------------------------------------------------------------- */
@Test
public void testBadEncoding()
{
UrlEncoded url_encoded = new UrlEncoded();
url_encoded.decode("Name15=xx%zzyy", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "xx\ufffdyy", url_encoded.getString("Name15"));
String bad="Name=%FF%FF%FF";
MultiMap<String> map = new MultiMap<String>();
UrlEncoded.decodeUtf8To(bad,map);
assertEquals("encoded param size",1, map.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", map.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%FF%FF%FF", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", url_encoded.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%EF%EF%EF", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd", url_encoded.getString("Name"));
assertEquals("x",UrlEncoded.decodeString("x",0,1,StandardCharsets.UTF_8));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%",0,2,StandardCharsets.UTF_8));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%2",0,3,StandardCharsets.UTF_8));
assertEquals("x ",UrlEncoded.decodeString("x%20",0,4,StandardCharsets.UTF_8));
assertEquals("xxx",UrlEncoded.decodeString("xxx",0,3,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%",0,4,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u",0,5,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u1",0,6,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u12",0,7,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u123",0,8,StandardCharsets.UTF_8));
assertEquals("xxx\u1234",UrlEncoded.decodeString("xxx%u1234",0,9,StandardCharsets.UTF_8));
}
/* -------------------------------------------------------------- */
@Test
public void testUrlEncodedStream()
@ -209,6 +137,7 @@ public class URLEncodedTest
{StringUtil.__UTF16,StringUtil.__UTF16,"%00%30"},
};
// Note: "%30" -> decode -> "0"
for (int i=0;i<charsets.length;i++)
{
@ -216,10 +145,10 @@ public class URLEncodedTest
MultiMap<String> m = new MultiMap<>();
UrlEncoded.decodeTo(in, m, charsets[i][1]==null?null:Charset.forName(charsets[i][1]),-1,-1);
assertEquals(charsets[i][1]+" stream length",4,m.size());
assertEquals(charsets[i][1]+" stream name\\n","value 0",m.getString("name\n"));
assertEquals(charsets[i][1]+" stream name1","",m.getString("name1"));
assertEquals(charsets[i][1]+" stream name2","",m.getString("name2"));
assertEquals(charsets[i][1]+" stream n\u00e3me3","value 3",m.getString("n\u00e3me3"));
assertThat(charsets[i][1]+" stream name\\n",m.getString("name\n"),is("value 0"));
assertThat(charsets[i][1]+" stream name1",m.getString("name1"),is(""));
assertThat(charsets[i][1]+" stream name2",m.getString("name2"),is(""));
assertThat(charsets[i][1]+" stream n\u00e3me3",m.getString("n\u00e3me3"),is("value 3"));
}
@ -270,21 +199,19 @@ public class URLEncodedTest
String expected = new String(TypeUtil.fromHexString(hex),"utf-8");
Assert.assertEquals(expected,url_encoded.getString("text"));
}
/* -------------------------------------------------------------- */
@Test
public void testNotUtf8() throws Exception
public void testUtf8_MultiByteCodePoint()
{
String query="name=X%c0%afZ";
MultiMap<String> map = new MultiMap<>();
UrlEncoded.LOG.info("EXPECT 4 Not Valid UTF8 warnings...");
UrlEncoded.decodeUtf8To(query,0,query.length(),map);
assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0));
map.clear();
UrlEncoded.decodeUtf8To(new ByteArrayInputStream(query.getBytes(StandardCharsets.ISO_8859_1)),map,100,-1);
assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0));
String input = "text=test%C3%A4";
UrlEncoded url_encoded = new UrlEncoded();
url_encoded.decode(input);
// http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex
// Should be "testä"
// "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
String expected = "test\u00e4";
assertThat(url_encoded.getString("text"),is(expected));
}
}

View File

@ -0,0 +1,87 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.util;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class UrlEncodedInvalidEncodingTest
{
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Parameterized.Parameters(name = "{1} | {0}")
public static List<Object[]> data()
{
ArrayList<Object[]> data = new ArrayList<>();
data.add(new Object[]{ "Name=xx%zzyy", UTF_8, IllegalArgumentException.class });
data.add(new Object[]{ "Name=%FF%FF%FF", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=%EF%EF%EF", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=%E%F%F", UTF_8, IllegalArgumentException.class });
data.add(new Object[]{ "Name=x%", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=x%2", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=xxx%", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "name=X%c0%afZ", UTF_8, Utf8Appendable.NotUtf8Exception.class });
return data;
}
@Parameterized.Parameter(0)
public String inputString;
@Parameterized.Parameter(1)
public Charset charset;
@Parameterized.Parameter(2)
public Class<? extends Throwable> expectedThrowable;
@Test
public void testDecode()
{
UrlEncoded url_encoded = new UrlEncoded();
expectedException.expect(expectedThrowable);
url_encoded.decode(inputString, charset);
}
@Test
public void testDecodeUtf8ToMap()
{
MultiMap<String> map = new MultiMap<String>();
expectedException.expect(expectedThrowable);
UrlEncoded.decodeUtf8To(inputString,map);
}
@Test
public void testDecodeTo()
{
MultiMap<String> map = new MultiMap<String>();
expectedException.expect(expectedThrowable);
UrlEncoded.decodeTo(inputString,map,charset);
}
}

Some files were not shown because too many files have changed in this diff Show More