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

This commit is contained in:
Greg Wilkins 2017-03-07 07:55:36 +11:00
commit 05f05520eb
34 changed files with 1241 additions and 419 deletions

View File

@ -3,16 +3,11 @@ Eclipse Jetty Canonical Repository
This is the canonical repository for the Jetty project, feel free to fork and contribute now!
Build Status
------------
Submitting a patch or pull request?
- Master Branch - [![Build Status](http://ci.webtide.net:9099/build/job/jetty-master/badge/icon)](http://ci.webtide.net:9099/build/job/jetty-master/)
- Jetty 9.3.x Branch - [![Build Status](http://ci.webtide.net:9099/build/job/jetty-9.3.x/badge/icon)](http://ci.webtide.net:9099/build/job/jetty-9.3.x/)
- Jetty 9.2.x Branch - [![Build Status](http://ci.webtide.net:9099/build/job/jetty-9.2.x/badge/icon)](http://ci.webtide.net:9099/build/job/jetty-9.2.x/)
Make sure you have an Eclipse Contributor Agreement (ECA) on file.
Make sure you have a CLA on file!
- [https://www.eclipse.org/legal/clafaq.php](https://www.eclipse.org/legal/clafaq.php)
- [eclipse.org/legal/ecafaq](https://www.eclipse.org/legal/ecafaq.php)
Project description
-------------------
@ -34,7 +29,8 @@ Building
========
To build, use:
```shell
``` shell
mvn clean install
```

View File

@ -1,5 +1,35 @@
jetty-10.0.0-SNAPSHOT
jetty-9.4.3-SNAPSHOT
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
+ 1282 ByteArrayEndPointTest.testIdle() failure
+ 1284 IllegalStateException updating session inactive interval
+ 1290 http2-hpack not visible in OSGi
+ Allow application to hint that chunking should be used
+ 1292 jetty-home has unresolvable dependencies
+ 1296 Introduce HTTP parser "content complete" event
+ 1298 Generate gcloud-datastore.mod
+ 1300 Update to gcloud-datastore 0.8.2
+ 1307 Session scavenge needs to invalidate session
+ 1309 HttpClient GZIPContentDecoder should use the clients ByteBufferPool
+ 1313 Insufficient Bytes behavior change in jetty 9.4.x due to HTTP Trailers
support?
+ 1315 Update to gcloud datastore 0.8.3-beta
+ 1317 AsyncProxyServletLoadTest fails
+ 1318 SessionEvictionFailure test fails on Windows
+ 1326 Jetty shutdown command got NullPointerException (http2 module added to
start)
+ 1328 Response.setBufferSize(int) ISE should be more clear on reason
+ 1329 Update to gcloud-datastore 0.9.2-beta
+ 1331 NPE in ClasspathPattern.add when using module logging-log4j2.mod and
other logging modules
+ 1342 Improve ByteBufferPool scalability
jetty-9.4.1.v20170120 - 20 January 2017
+ 486 JDK 9 ALPN implementation
+ 592 Support no-value Host header in HttpParser
@ -46,25 +76,25 @@ jetty-9.4.1.v20170120 - 20 January 2017
+ 1222 Authenticated sessions throw exception on invalidate
+ 1223 Allow session workername to be null
+ 1224 HttpSessionListener.sessionDestroyed can no longer access session
+ 1226 Undefined JETTY_LOGS breaks jetty.sh
+ 1226 Undefined JETTY_LOGS breaks jetty.sh
+ 1228 Internal error during SSL handshake
+ 1229 ClassLoader constraint issue when using NativeWebSocketConfiguration
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
@ -349,7 +379,7 @@ jetty-9.4.0.M1 - 15 August 2016
+ 623 Add --gzip suffix to 304 responses with ETAGs
+ 624 AsyncContext.onCompleted called twice
+ 627 Use only start.ini or start.d, not both
+ 628 IOException: Unable to open root Jar file .
+ 628 IOException: Unable to open root Jar file
MetaInfConfiguration.getTlds(MetaInfConfiguration.java:406) with Spring boot
loader + WebAppContext + non-expanded war
+ 632 JMX tests rely on fixed port

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<version>10.0.0-SNAPSHOT</version>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>10.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
<name>Jetty Examples :: Parent</name>

View File

@ -40,6 +40,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
@ -81,6 +82,7 @@ public class HttpRequest implements Request
private List<HttpCookie> cookies;
private Map<String, Object> attributes;
private List<RequestListener> requestListeners;
private BiFunction<Request, Request, Response.CompleteListener> pushListener;
protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
{
@ -567,6 +569,26 @@ public class HttpRequest implements Request
return this;
}
/**
* <p>Sets a listener for pushed resources.</p>
* <p>When resources are pushed from the server, the given {@code listener}
* is invoked for every pushed resource.
* The parameters to the {@code BiFunction} are this request and the
* synthesized request for the pushed resource.
* The {@code BiFunction} should return a {@code CompleteListener} that
* may also implement other listener interfaces to be notified of various
* response events, or {@code null} to signal that the pushed resource
* should be canceled.</p>
*
* @param listener a listener for pushed resource events
* @return this request object
*/
public Request pushListener(BiFunction<Request, Request, Response.CompleteListener> listener)
{
this.pushListener = listener;
return this;
}
@Override
public ContentProvider getContent()
{
@ -698,6 +720,11 @@ public class HttpRequest implements Request
return responseListeners;
}
public BiFunction<Request, Request, Response.CompleteListener> getPushListener()
{
return pushListener;
}
@Override
public boolean abort(Throwable cause)
{

View File

@ -22,6 +22,7 @@ include::start-jar.adoc[]
include::startup-base-vs-home.adoc[]
include::startup-classpath.adoc[]
include::startup-modules.adoc[]
include::custom-modules.adoc[]
include::startup-xml-config.adoc[]
include::startup-unix-service.adoc[]
include::startup-windows-service.adoc[]

View File

@ -0,0 +1,219 @@
// ========================================================================
// 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.
// ========================================================================
[[custom-modules]]
=== Custom Modules
In addition to the modules that come packaged with the Jetty distribution, users are able to create and define their own custom modules for use with their Jetty implementation.
Custom modules can be used for a number of reasons - they can extend features in Jetty, add new features, manage additional libraries available to the server...etc.
At the heart of a Jetty module is the `{name}.mod` file itself.
A jetty `.mod` file defines the following:
[NOTE]
--
It is important to note that when creating your own module, none of these sections are required - simply use those which are applicable to your implementation.
--
Module Description - `[description]`::
The description of the module.
This will be showing when viewing the `.mod` file itself or using the `--list-modules` command.
List of Dependent Modules - `[depend]`::
All modules can declare that they depend on other modules with the `[depend]` section.
The list of dependencies is used to transitively resolve other modules that are deemed to be required based on the modules that you activate.
The order of modules defined in the graph of active modules is used to determine various execution order for configuration, such as Jetty IoC XML configurations, and to resolve conflicting property declarations.
Optional Modules - `[optional]`;;
Of note: there is a special section `[optional]` used to describe structurally dependent modules that are not technically required, but might be of use to your specific configuration.
List of Libraries - `[lib]`::
Modules can optionally declare that they have libraries that they need to function properly.
The `[lib]` section declares a set of pathnames that follow the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
List of Jetty IoC XML Configurations - `[xml]`::
A Module can optionally declare a list of Jetty IoC XML configurations used to wire up the functionality that this module defines.
The `[xml]` section declares a set of pathnames that follow the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
Ideally, all XML files are parameterized to accept properties to configure the various elements of the standard configuration.
Allowing for a simplified configuration of Jetty for the vast majority of deployments.
The execution order of the Jetty IoC XML configurations is determined by the graph of active module dependencies resolved via the `[depend]` sections.
If the default XML is not sufficient to satisfy your needs, you can override this XML by making your own in the `${jetty.base}/etc/` directory, with the same name.
The resolution steps for Jetty Base and Jetty Home will ensure that your copy from `${jetty.base}` will be picked up over the default one in `${jetty.home}`.
List of Module Tags - `[tags]`::
For ease of sorting, modules can be assigned tags.
When using the `--list-modules` command, modules will be groups by the first tag that exists in this section.
Modules can also be listed specifically by these tags using `--list-modules=<tag name>` on the command line.
Ini Variables - `[ini]`::
The `[ini]` section is used to add or change server parameters at startup.
The `[ini]` section can also include a the path of a file or several files which should be made available to the server only.
This is helpful when you want to control what jars are available to deployed webapps.
Jetty INI Template - `[ini-template]`::
Each module can optionally declare a startup ini template that is used to insert/append/inject sample configuration elements into the `start.ini` or `start.d/*.ini` files when using the `--add-to-start=<name>` command line argument in `start.jar`.
Commonly used to present some of the parameterized property options from the Jetty IoC XML configuration files also referenced in the same module.
Required Files and Directories - `[files]`::
If the activation of a module requires some paths to exist, the `[files]` section defines them.
There are 2 modes of operation of the entries in this section.
Ensure Directory Exists;;
If you add a pathname that ends in `"/"` (slash), such as `"webapps/"`, then that directory will be created if it does not yet exist in `${jetty.base}/<pathname>` (eg: `"webapps/"` will result in `${jetty.base}/webapps/` being created).
Download File;;
There is a special syntax to allow you to download a file into a specific location if it doesn't exist yet: `<url>:<pathname>`.
Currently, the `<url>` must be a `http://` scheme URL (please link:#bugs[let us know] if you need more schemes supported).
The `<pathname>` portion follows the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
Example: `http://repo.corp.com/maven/corp-security-policy-1.0.jar:lib/corp-security-policy.jar`
This will check for the existence of `lib/corp-security-policy.jar`, and if it doesn't exist, it will download the jar file from `http://repo.corp.com/maven/corp-security-policy-1.0.jar`
Licenses - `[license]`::
If you are implementing a software/technology that has a license, it's text can be placed here.
When a user attempts to activate the module they will be asked if they accept the license agreement.
If a user does not accept the license agreement, the module will not be activated.
Additional Startup Commands - `[exec]`::
The `[exec]` section is used to define additional parameters specific to the module.
These commands are added to the server startup.
[[custom-module-location]]
==== Location of Modules
Jetty comes with dozens of modules as part of the distribution package.
By default these are located in the `${JETTY_HOME}/modules` directory.
These modules should not be modified.
In the unlikely circumstance you need to make changes to a stock module, copy it to your `${JETTY_BASE}` in a `modules` directory.
Custom modules should also be maintained separately as part of the `${JETTY_BASE}/modules` directory, though you can optionally place them in `${JETTY_HOME}/modules` for convenience if you have several `{$JETTY_BASE}` locations in your implementation.
[[custom-module-examples]]
==== Creating Custom Modules
As shown above, there are several options that can be utilized when creating custom module files.
This may seem daunting, but the good news is that creating custom modules is actually quite easy.
For example, here is a look at the `http.mod` file which defines parameters for enabling HTTP features for the server:
[source, screen]
----
include::{SRCDIR}/jetty-server/src/main/config/modules/http.mod[]
----
You'll notice that the `http.mod` file only includes a handful of the possible sections available - `[description]`, `[tags]`, `[depend]`, `[xml]`, and `[ini-template]`.
When configuring your own modules, you are free to pick and choose what you include.
As an example, below is a module file that defines a custom XML and lib, and activates a number of additional modules.
A module like this could be used to enable a set of standard modules and resources for a new JETTY_BASE without having to define them all manually.
[source, screen]
----
[description]
Enables the standard set of modules and resources for ACME Corp servers.
[tags]
core
[depend]
server
client
http
http2
jsp
console-capture
requestlog
stats
gzip
deploy
jmx
[files]
basehome:modules/acme/acme.xml|etc/acme.xml
[lib]
lib/acme/ACMECustom.jar
----
Activating this module will activate all the dependent modules, create any required directories and copy in any required files:
[source, screen]
----
java -jar ../start.jar --add-to-start=acme
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: alpn-impl/alpn-8
+ ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ ALPN replaces/modifies OpenJDK classes in the sun.security.ssl package.
+ http://github.com/jetty-project/jetty-alpn
+ http://openjdk.java.net/legal/gplv2+ce.html
Proceed (y/N)? y
INFO : webapp transitively enabled, ini template available with --add-to-start=webapp
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : requestlog transitively enabled, ini template available with --add-to-start=requestlog
INFO : alpn transitively enabled, ini template available with --add-to-start=alpn
INFO : jsp transitively enabled
INFO : servlet transitively enabled
INFO : alpn-impl/alpn-8 dynamic dependency of alpn
INFO : annotations transitively enabled
INFO : gzip transitively enabled, ini template available with --add-to-start=gzip
INFO : ssl transitively enabled, ini template available with --add-to-start=ssl
INFO : plus transitively enabled
INFO : deploy transitively enabled, ini template available with --add-to-start=deploy
INFO : alpn-impl/alpn-1.8.0_92 dynamic dependency of alpn-impl/alpn-8
INFO : security transitively enabled
INFO : jmx transitively enabled
INFO : apache-jsp transitively enabled
INFO : stats transitively enabled, ini template available with --add-to-start=stats
INFO : acme initialized in ${jetty.base}/start.d/acme.ini
INFO : jndi transitively enabled
INFO : console-capture transitively enabled, ini template available with --add-to-start=console-capture
INFO : client transitively enabled
INFO : http transitively enabled, ini template available with --add-to-start=http
INFO : http2 transitively enabled, ini template available with --add-to-start=http2
MKDIR : ${jetty.base}/logs
MKDIR : ${jetty.base}/lib
MKDIR : ${jetty.base}/lib/alpn
MKDIR : ${jetty.base}/etc
COPY : ${jetty.home}/modules/ssl/keystore to ${jetty.base}/etc/keystore
MKDIR : ${jetty.base}/webapps
DOWNLD: http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.8.v20160420/alpn-boot-8.1.8.v20160420.jar to ${jetty.base}/lib/alpn/alpn-boot-8.1.8.v20160420.jar
COPY : ${jetty.home}/modules/acme/acme.xml to ${jetty.base}/etc/acme.xml
INFO : Base directory was modified
----
==== Dependencies
When dependent modules are enabled, they are done so transitively by default.
This means that any `ini` files for dependent modules are not created in the `${JETTY_BASE}/start.d` directory (or added to `${JETTY_BASE}/start.ini`) and are as such not configurable.
For Jetty to create/add the `ini-template` parameters to `start.d` or `start.ini` the associated module must be enabled explicitly.
For example, if I activate the `http` module, it will be enabled, and the `server` module will be enabled transitively:
[source, screen]
----
$ java -jar ../start.jar --add-to-start=http
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : http initialized in ${jetty.base}/start.d/http.ini
INFO : Base directory was modified
----
You'll notice that Jetty informs you of what modules were enabled, and where there associated ini files are located (when applicable).
It also tells the user what command they would need to run to enable any missing or desired ini files for the selected modules, in this case `--add-to-start=server`.
[source, screen]
----
$ java -jar ../start.jar --add-to-start=server
INFO : server initialized in ${jetty.base}/start.d/server.ini
INFO : Base directory was modified
----
____
[NOTE]
It is important to keep in mind that when activating a dependency, Jetty does not just go one layer down.
If a dependent module also has dependencies they too will be enabled.
____

View File

@ -36,7 +36,7 @@ At its most basic, you configure Jetty from two elements:
Instead of editing these directly, Jetty 9.1 introduced more options on how to configure Jetty (these are merely syntactic sugar that eventually resolve into the two basic configuration components).
Jetty 9.1 Startup Features include:
Jetty Startup Features include:
* A separation of the Jetty distribution binaries in `${jetty.home}` and the environment specific configurations (and binaries) found in `${jetty.base}` (detailed in link:#startup-jetty-base-and-jetty-home[Managing Jetty Base and Jetty Home.])
* You can enable a set of libraries and XML configuration files via the newly introduced link:#startup-modules[module system.]

View File

@ -20,54 +20,8 @@
The standard Jetty Distribution ships with several modules defined in `${jetty.home}/modules/`.
These modules allow flexibility for implementations and make configuration a much more plug-and-play set up.
What a Jetty Startup Module Defines:
A Module Name::
The name of the module is the keyword used by the `--module=<name>` command line argument to activate/enable modules, and also find dependent modules.
The filename of the module defines its name (eg: server.mod becomes the module named "server").
List of Dependent Modules::
All modules can declare that they depend on other modules with the `[depend]` section.
The list of dependencies is used to transitively resolve other modules that are deemed to be required based on the modules that you activate.
The order of modules defined in the graph of active modules is used to determine various execution order for configuration, such as Jetty IoC XML configurations, and to resolve conflicting property declarations.
Of note: there is a special section `[optional]` used to describe structurally dependent modules that are not technically required, but might be of use to your specific configuration.
List of Libraries::
Module can optionally declare that they have libraries that they need to function properly.
The `[lib]` section declares a set of pathnames that follow the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
List of Jetty IoC XML Configurations::
A Module can optionally declare a list of Jetty IoC XML configurations used to wire up the functionality that this module defines.
The `[xml]` section declares a set of pathnames that follow the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
Ideally, all XML files are parameterized to accept properties to configure the various elements of the standard configuration.
Allowing for a simplified configuration of Jetty for the vast majority of deployments.
The execution order of the Jetty IoC XML configurations is determined by the graph of active module dependencies resolved via the `[depend]` sections.
If the default XML is not sufficient to satisfy your needs, you can override this XML by making your own in the `${jetty.base}/etc/` directory, with the same name.
The resolution steps for Jetty Base and Jetty Home will ensure that your copy from `${jetty.base}` will be picked up over the default one in `${jetty.home}`.
List of Module Tags::
For ease of sorting, modules can be assigned tags.
When using the `--list-modules` command, modules will be groups by the first tag that exists in this section.
Modules can also be listed specifically by these tags using `--list-modules=<tag name>` on the command line.
Ini Variables::
The `[ini]` section is used to add or change server parameters at startup.
The `[ini]` section can also include a the path of a file or several files which should be made available to the server only.
This is helpful when you want to control what jars are available to deployed webapps.
Jetty INI Template::
Each module can optionally declare a startup ini template that is used to insert/append/inject sample configuration elements into the `start.ini` or `start.d/*.ini` files when using the `--add-to-start=<name>` command line argument in `start.jar`.
Commonly used to present some of the parameterized property options from the Jetty IoC XML configuration files also referenced in the same module.
The `[ini-template]` section declares this section of sample configuration.
Required Files and Directories::
If the activation of a module requires some paths to exist, the `[files]` section defines them.
There are 2 modes of operation of the entries in this section.
Ensure Directory Exists;;
If you add a pathname that ends in `"/"` (slash), such as `"webapps/"`, then that directory will be created if it does not yet exist in `${jetty.base}/<pathname>` (eg: `"webapps/"` will result in `${jetty.base}/webapps/` being created).
Download File;;
There is a special syntax to allow you to download a file into a specific location if it doesn't exist yet: `<url>:<pathname>`.
Currently, the `<url>` must be a `http://` scheme URL (please link:#bugs[let us know] if you need more schemes supported).
The `<pathname>` portion follows the link:#base-vs-home-resolution[Jetty Base and Jetty Home path resolution rules].
Example: `http://repo.corp.com/maven/corp-security-policy-1.0.jar:lib/corp-security-policy.jar`
This will check for the existence of `lib/corp-security-policy.jar`, and if it doesn't exist, it will download the jar file from `http://repo.corp.com/maven/corp-security-policy-1.0.jar`
[[enabling-modules]]
==== Enabling Modules
____
[TIP]
The default distribution has a co-mingled `${jetty.home}` and `${jetty.base}` where the directories for `${jetty.home}` and `${jetty.base}` point to the same location.

View File

@ -529,7 +529,7 @@ Certificate fingerprints:
Signature algorithm name: SHA256withRSA
Version: 3
Extensions:
Extensions:
#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
@ -571,7 +571,7 @@ Certificate fingerprints:
Signature algorithm name: SHA256withRSA
Version: 3
Extensions:
Extensions:
#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
@ -797,7 +797,8 @@ When using the `ExtendedSSlContextFactory`, the correct certificate is automatic
[[configuring-sslcontextfactory-cipherSuites]]
==== Disabling/Enabling Specific Cipher Suites
As an example, to avoid the BEAST attack it is necessary to configure a specific set of cipher suites. This can either be done via link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.html#setIncludeCipherSuites(java.lang.String...)[SslContext.setIncludeCipherSuites(java.lang.String...)] or vialink:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.html#setExcludeCipherSuites(java.lang.String...)[SslContext.setExcludeCipherSuites(java.lang.String...)].
To avoid specific attacks it is often necessary to configure a specific set of cipher suites to include or exclude.
This can either be done via link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.html#setIncludeCipherSuites(java.lang.String...)[SslContext.setIncludeCipherSuites(java.lang.String...)] or vialink:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.html#setExcludeCipherSuites(java.lang.String...)[SslContext.setExcludeCipherSuites(java.lang.String...)].
____
[NOTE]
@ -815,7 +816,7 @@ ____
Both `setIncludeCipherSuites` and `setExcludeCipherSuites` can be fed by the exact cipher suite name used in the JDK or by using regular expressions.
If you have a need to adjust the Includes or Excludes, then this is best done with a custom blow-in XML that configures the `SslContextFactory` to suit your needs.
If you have a need to adjust the Includes or Excludes, then this is best done with a custom XML that configures the `SslContextFactory` to suit your needs.
To do this, first create a new `${jetty.base}/etc/tweak-ssl.xml` file (this can be any name, just avoid prefixing it with "jetty-").
@ -930,3 +931,97 @@ ____
----
<Set name="renegotiationAllowed">FALSE</Set>
----
[[ssl-dump-ciphers]]
You can view what cipher suites are enabled and disabled by performing a server dump.
To perform a server dump upon server startup, add `jetty.server.dumpAfterStart=true` to the command line when starting the server.
You can also dump the server when shutting down the server instance by adding `jetty.server.dumpBeforeStop`.
Specifically, you will want to look for the `SslConnectionFactory` portion of the dump.
[source, screen, subs="{sub-order}"]
----
[my-base]$ java -jar ${JETTY_HOME}/start.jar jetty.server.dumpAfterStart=true
...
| += SslConnectionFactory@18be83e4{SSL->http/1.1} - STARTED
| | += SslContextFactory@42530531(null,null) trustAll=false
| | +- Protocol Selections
| | | +- Enabled (size=3)
| | | | +- TLSv1
| | | | +- TLSv1.1
| | | | +- TLSv1.2
| | | +- Disabled (size=2)
| | | +- SSLv2Hello - ConfigExcluded:'SSLv2Hello'
| | | +- SSLv3 - JreDisabled:java.security, ConfigExcluded:'SSLv3'
| | +- Cipher Suite Selections
| | +- Enabled (size=15)
| | | +- TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
| | | +- TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
| | | +- TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
| | | +- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
| | | +- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
| | | +- TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
| | | +- TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
| | | +- TLS_EMPTY_RENEGOTIATION_INFO_SCSV
| | | +- TLS_RSA_WITH_AES_128_CBC_SHA256
| | | +- TLS_RSA_WITH_AES_128_GCM_SHA256
| | +- Disabled (size=42)
| | +- SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DHE_DSS_WITH_DES_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DHE_RSA_WITH_DES_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DH_anon_WITH_3DES_EDE_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_DH_anon_WITH_DES_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_RSA_EXPORT_WITH_DES40_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_RSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_RSA_WITH_DES_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_RSA_WITH_NULL_MD5 - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- SSL_RSA_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_DHE_DSS_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_DHE_RSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_DH_anon_WITH_AES_128_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_DH_anon_WITH_AES_128_CBC_SHA256 - JreDisabled:java.security
| | +- TLS_DH_anon_WITH_AES_128_GCM_SHA256 - JreDisabled:java.security
| | +- TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDHE_ECDSA_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDHE_RSA_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_ECDSA_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_RSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_RSA_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_anon_WITH_AES_128_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_ECDH_anon_WITH_NULL_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_WITH_3DES_EDE_CBC_MD5 - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_WITH_3DES_EDE_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_WITH_DES_CBC_MD5 - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_KRB5_WITH_DES_CBC_SHA - JreDisabled:java.security, ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_RSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^.*_(MD5|SHA|SHA1)$'
| | +- TLS_RSA_WITH_NULL_SHA256 - JreDisabled:java.security
...
----
In the example above you can see both the enabled/disabled protocols and included/excluded ciper suites.
For disabled or excluded protocols and ciphers, the reason they are disabled is given - either due to JVM restrictions, configuration or both.
As a reminder, when configuring your includes/excludes, *excludes always win*.
Dumps can be configured as part of the `jetty.xml` configuration for your server.
Please see the documentation on the link:#jetty-dump-tool[Jetty Dump Tool] for more information.

View File

@ -31,18 +31,15 @@ This release script is for jetty-9 (to release jetty-7 or jetty-8 see older docu
These follow a strict format and will be used when prompted during step link:#prepare-release-step[listitem_title] below.
+
....
Release Version : 9.0.0.v20130322 (v[year][month][day])
Next Development Version : 9.0.1-SNAPSHOT
Tag Name : jetty-9.9.0.v20130322
....
2. We use the 'release-9' branch to avoid problems with other developers actively working on the master branch.
+
[source, screen, subs="{sub-order}"]
....
// Get all of the remotes
$ git pull origin
// Create a local tracking branch (if you haven't already)
@ -52,62 +49,57 @@ $ git checkout release-9
// Merge from master into the branch (this becomes your point in time
// from master that you will be releasing from)
$ git merge --no-ff master
....
3. Update the VERSION.txt with changes from the git logs, this populates the resolves issues since the last release.
+
[source, screen, subs="{sub-order}"]
....
$ mvn -N -Pupdate-version
$ mvn -N -Pupdate-version
....
4. Edit the VERSION.txt file to set the 'Release Version' at the top alongside the Date of this release.
+
[source, screen, subs="{sub-order}"]
....
$ vi VERSION.txt
$ vi VERSION.txt
....
5. Make sure everything is commit'd and pushed to github.com/eclipse/jetty.project
+
[source, screen, subs="{sub-order}"]
....
$ git commit -m "Updating VERSION.txt top section" VERSION.txt
$ git push origin release-9
$ git push origin release-9
....
6. Prepare the Release
+
NOTE: This step updates the <version> elements in the pom.xml files, does a test build with these new versions, and then commits the pom.xml changes to your local git repo.
____
[NOTE]
This step updates the <version> elements in the pom.xml files, does a test build with these new versions, and then commits the pom.xml changes to your local git repo.
The `eclipse-release` profile is required on the prepare in order to bring in the jetty aggregates as that profile defines a module which is ignored otherwise.
____
+
[source, screen, subs="{sub-order}"]
....
$ mvn release:prepare -DreleaseVersion=9.0.0.v20130322 \
-DdevelopmentVersion=9.0.1-SNAPSHOT \
-Dtag=jetty-9.0.0.v20130322 \
-Peclipse-release
-Peclipse-release
....
7. Perform the Release
+
NOTE: This step performs the release and deploys it to a oss.sonatype.org staging repository.
____
[NOTE]
This step performs the release and deploys it to a oss.sonatype.org staging repository.
____
+
[source, screen, subs="{sub-order}"]
....
$ mvn release:perform
....
8. Set up files for next development versions.
+
@ -118,15 +110,15 @@ Make sure everything is commit'd and pushed to github.com/eclipse/jetty.project
+
[source, screen, subs="{sub-order}"]
....
$ vi VERSION.txt
$ git commit -m "Updating VERSION.txt top section" VERSION.txt
$ git push origin release-9
....
9. Close the staging repository on oss.sonatype.org
10. Announce stage to the mailing list for testing.
11. Once the staged repository has been approved by the rest of the committers.
+
* Release the staging repository to maven central on oss.sonatype.org
@ -134,12 +126,9 @@ $ git push origin release-9
+
[source, screen, subs="{sub-order}"]
....
$ git checkout master
$ git merge --no-ff release-9
$ git push origin master
....
[[releasing-aggregates]]
@ -153,12 +142,9 @@ To build and deploy the aggregate javadoc and jxr bits:
[source, screen, subs="{sub-order}"]
....
$ cd target/checkout
$ mvn -Paggregate-site javadoc:aggregate jxr:jxr
$ mvn -N site:deploy
$ mvn -N site:deploy
....
This will generate the aggregate docs and deploy them to the `/home/www/jetty/<project version>/jetty-project` directory on download.eclipse.org.
@ -181,9 +167,7 @@ Once these are setup you can deploy a release to eclipse with the following inca
[source, screen, subs="{sub-order}"]
....
$ ./promote-to-eclipse.sh 9.0.0.v20130322
....
Each of these scripts will download all of the relevant files from maven central and then copy them into the correct location on eclipse infrastructure.
@ -197,26 +181,20 @@ Maintaining the conventions we use on the site will allow all 'stable' links to
[source, screen, subs="{sub-order}"]
....
$ ssh <user>@build.eclipse.org
$ cd ~downloads/jetty/
$ rm -Rf stable-9
$ cp -r <version> stable-9
$ ./index.sh
$ ./index.sh
....
This needs to be done for all Eclipse Jetty releases (regardless of version). In addition we have to work to reduce the footprint of jetty on the primary eclipse download resources so we want to move older releases to the eclipse archive site.
[source, screen, subs="{sub-order}"]
....
$ cd ~/downloads/jetty
$ mv <old release> /home/data/httpd/archive.eclipse.org/jetty/
$ ./index.sh
$ mv <old release> /home/data/httpd/archive.eclipse.org/jetty/
$ ./index.sh
....
Periodically we need to do the same for the osgi P2 repositories to keep the size of our downloads directory at a reasonable size.
@ -224,7 +202,7 @@ Periodically we need to do the same for the osgi P2 repositories to keep the siz
==== Building an OSGi P2 Repository
Most of the jetty jars are also osgi bundles, plus we release some specific bundles that link:#framework-jetty-osgi[integrate jetty closely with osgi].
To do this, we use a Hudson job on the eclipse infrastructure. You will need to have permission to access https://hudson.eclipse.org/hudson/view/Jetty-RT/
To do this, we use a Hudson job on the eclipse infrastructure. You will need to have permission to access https://ci.eclipse.org/shared/view/Jetty-RT/
There are Hudson jobs that build osgi p2 repos for each of the major releases of jetty:7 (jetty-rt-bundles-7), 8 (jetty-rt-bundles-8) and 9 (jetty-rt-bundles-9).
You will need to start a manual build of the job that matches the version of jetty that you are releasing.

View File

@ -40,6 +40,20 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
private final static Double ZERO=new Double(0.0);
private final static Double ONE=new Double(1.0);
/**
* Function to apply a most specific MIME encoding secondary ordering
*/
public static Function<String, Integer> MOST_SPECIFIC = new Function<String, Integer>()
{
@Override
public Integer apply(String s)
{
String[] elements = s.split("/");
return 1000000*elements.length+1000*elements[0].length()+elements[elements.length-1].length();
}
};
private final List<Double> _quality = new ArrayList<>();
private boolean _sorted = false;
private final Function<String, Integer> _secondaryOrdering;
@ -50,7 +64,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
*/
public QuotedQualityCSV()
{
this((s) -> s.length());
this((s) -> 0);
}
/* ------------------------------------------------------------ */
@ -101,7 +115,14 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
@Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
if (buffer.charAt(paramName)=='q' && paramValue>paramName && buffer.charAt(paramName+1)=='=')
if (paramName<0)
{
if (buffer.charAt(buffer.length()-1)==';')
buffer.setLength(buffer.length()-1);
}
if (paramValue>=0 &&
buffer.charAt(paramName)=='q' && paramValue>paramName &&
buffer.length()>=paramName && buffer.charAt(paramName+1)=='=')
{
Double q;
try
@ -142,7 +163,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
_sorted=true;
Double last = ZERO;
int lastOrderIndex = Integer.MIN_VALUE;
int lastSecondaryOrder = Integer.MIN_VALUE;
for (int i = _values.size(); i-- > 0;)
{
@ -150,20 +171,20 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
Double q = _quality.get(i);
int compare=last.compareTo(q);
if (compare>0 || (compare==0 && _secondaryOrdering.apply(v)<lastOrderIndex))
if (compare>0 || (compare==0 && _secondaryOrdering.apply(v)<lastSecondaryOrder))
{
_values.set(i, _values.get(i + 1));
_values.set(i + 1, v);
_quality.set(i, _quality.get(i + 1));
_quality.set(i + 1, q);
last = ZERO;
lastOrderIndex=0;
lastSecondaryOrder=0;
i = _values.size();
continue;
}
last=q;
lastOrderIndex=_secondaryOrdering.apply(v);
lastSecondaryOrder=_secondaryOrdering.apply(v);
}
int last_element=_quality.size();

View File

@ -433,15 +433,41 @@ public class HttpFieldsTest
fields.add("name", "nothing;q=0");
fields.add("name", "one;q=0.4");
fields.add("name", "three;x=y;q=0.2;a=b,two;q=0.3");
fields.add("name", "first;");
List<String> list = fields.getQualityCSV("name");
assertEquals("zero",HttpFields.valueParameters(list.get(0),null));
assertEquals("one",HttpFields.valueParameters(list.get(1),null));
assertEquals("two",HttpFields.valueParameters(list.get(2),null));
assertEquals("three",HttpFields.valueParameters(list.get(3),null));
assertEquals("four",HttpFields.valueParameters(list.get(4),null));
assertEquals("first",HttpFields.valueParameters(list.get(0),null));
assertEquals("zero",HttpFields.valueParameters(list.get(1),null));
assertEquals("one",HttpFields.valueParameters(list.get(2),null));
assertEquals("two",HttpFields.valueParameters(list.get(3),null));
assertEquals("three",HttpFields.valueParameters(list.get(4),null));
assertEquals("four",HttpFields.valueParameters(list.get(5),null));
}
@Test
public void testGetQualityCSVHeader() throws Exception
{
HttpFields fields = new HttpFields();
fields.put("some", "value");
fields.add("Accept", "zero;q=0.9,four;q=0.1");
fields.put("other", "value");
fields.add("Accept", "nothing;q=0");
fields.add("Accept", "one;q=0.4");
fields.add("Accept", "three;x=y;q=0.2;a=b,two;q=0.3");
fields.add("Accept", "first;");
List<String> list = fields.getQualityCSV(HttpHeader.ACCEPT);
assertEquals("first",HttpFields.valueParameters(list.get(0),null));
assertEquals("zero",HttpFields.valueParameters(list.get(1),null));
assertEquals("one",HttpFields.valueParameters(list.get(2),null));
assertEquals("two",HttpFields.valueParameters(list.get(3),null));
assertEquals("three",HttpFields.valueParameters(list.get(4),null));
assertEquals("four",HttpFields.valueParameters(list.get(5),null));
}
@Test
public void testDateFields() throws Exception

View File

@ -50,6 +50,17 @@ public class QuotedQualityCSVTest
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("text/*, text/plain, text/plain;format=flowed, */*");
// Note this sort is only on quality and not the most specific type as per 5.3.2
Assert.assertThat(values,Matchers.contains("text/*","text/plain","text/plain;format=flowed","*/*"));
}
@Test
public void test7231_5_3_2_example3_most_specific()
{
QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC);
values.addValue("text/*, text/plain, text/plain;format=flowed, */*");
Assert.assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*"));
}
@ -81,9 +92,9 @@ public class QuotedQualityCSVTest
Assert.assertThat(values,Matchers.contains(
"compress",
"gzip",
"gzip",
"gzip",
"*",
"gzip",
"gzip",
"compress",
"identity"
));
@ -217,4 +228,22 @@ public class QuotedQualityCSVTest
values.addValue("gzip, *");
assertThat(values, contains("*", "gzip"));
}
@Test
public void testSameQuality()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("one;q=0.5,two;q=0.5,three;q=0.5");
Assert.assertThat(values.getValues(),Matchers.contains("one","two","three"));
}
@Test
public void testNoQuality()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("one,two;,three;x=y");
Assert.assertThat(values.getValues(),Matchers.contains("one","two","three;x=y"));
}
}

View File

@ -34,19 +34,26 @@ public class HttpChannelOverHTTP2 extends HttpChannel
{
private final HttpConnectionOverHTTP2 connection;
private final Session session;
private final boolean push;
private final HttpSenderOverHTTP2 sender;
private final HttpReceiverOverHTTP2 receiver;
private Stream stream;
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session)
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session, boolean push)
{
super(destination);
this.connection = connection;
this.session = session;
this.push = push;
this.sender = new HttpSenderOverHTTP2(this);
this.receiver = new HttpReceiverOverHTTP2(this);
}
protected HttpConnectionOverHTTP2 getHttpConnection()
{
return connection;
}
public Session getSession()
{
return session;
@ -110,6 +117,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
public void exchangeTerminated(HttpExchange exchange, Result result)
{
super.exchangeTerminated(exchange, result);
release();
if (!push)
release();
}
}

View File

@ -61,15 +61,15 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
normalizeRequest(exchange.getRequest());
// One connection maps to N channels, so for each exchange we create a new channel.
HttpChannel channel = newHttpChannel();
HttpChannel channel = newHttpChannel(false);
channels.add(channel);
return send(channel, exchange);
}
protected HttpChannelOverHTTP2 newHttpChannel()
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
{
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession());
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push);
}
protected void release(HttpChannel channel)

View File

@ -21,13 +21,19 @@ package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.function.BiFunction;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
@ -91,7 +97,32 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
{
// Not supported.
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return null;
HttpRequest request = exchange.getRequest();
MetaData.Request metaData = (MetaData.Request)frame.getMetaData();
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
BiFunction<Request, Request, Response.CompleteListener> pushListener = request.getPushListener();
if (pushListener != null)
{
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
if (listener != null)
{
HttpChannelOverHTTP2 pushChannel = getHttpChannel().getHttpConnection().newHttpChannel(true);
List<Response.ResponseListener> listeners = Collections.singletonList(listener);
HttpExchange pushExchange = new HttpExchange(getHttpDestination(), pushRequest, listeners);
pushChannel.associate(pushExchange);
pushChannel.setStream(stream);
// TODO: idle timeout ?
pushExchange.requestComplete(null);
pushExchange.terminateRequest();
return pushChannel.getStreamListener();
}
}
stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
return null;
}

View File

@ -247,9 +247,9 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
return new HttpConnectionOverHTTP2(destination, session)
{
@Override
protected HttpChannelOverHTTP2 newHttpChannel()
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
{
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession())
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push)
{
@Override
public void setStream(Stream stream)

View File

@ -0,0 +1,213 @@
//
// ========================================================================
// 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.http2.client.http;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
public class PushedResourcesTest extends AbstractTest
{
@Test
public void testPushedResourceCancelled() throws Exception
{
String pushPath = "/secondary";
CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
HttpURI pushURI = new HttpURI("http://localhost:" + connector.getLocalPort() + pushPath);
MetaData.Request pushRequest = new MetaData.Request(HttpMethod.GET.asString(), pushURI, HttpVersion.HTTP_2, new HttpFields());
stream.push(new PushPromiseFrame(stream.getId(), 0, pushRequest), new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream pushStream)
{
// Just send the normal response and wait for the reset.
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}, new Stream.Listener.Adapter()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
{
latch.countDown();
}
});
return null;
}
});
HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
ContentResponse response = request
.pushListener((mainRequest, pushedRequest) -> null)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testPushedResources() throws Exception
{
Random random = new Random();
byte[] bytes = new byte[512];
random.nextBytes(bytes);
byte[] pushBytes1 = new byte[1024];
random.nextBytes(pushBytes1);
byte[] pushBytes2 = new byte[2048];
random.nextBytes(pushBytes2);
String path1 = "/secondary1";
String path2 = "/secondary2";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (target.equals(path1))
{
response.getOutputStream().write(pushBytes1);
}
else if (target.equals(path2))
{
response.getOutputStream().write(pushBytes2);
}
else
{
baseRequest.getPushBuilder()
.path(path1)
.push();
baseRequest.getPushBuilder()
.path(path2)
.push();
response.getOutputStream().write(bytes);
}
}
});
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
ContentResponse response = request
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isSucceeded());
if (pushedRequest.getPath().equals(path1))
{
Assert.assertArrayEquals(pushBytes1, getContent());
latch1.countDown();
}
else if (pushedRequest.getPath().equals(path2))
{
Assert.assertArrayEquals(pushBytes2, getContent());
latch2.countDown();
}
}
})
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertArrayEquals(bytes, response.getContent());
Assert.assertTrue(latch1.await(5, TimeUnit.SECONDS));
Assert.assertTrue(latch2.await(5, TimeUnit.SECONDS));
}
@Test
public void testPushedResourceRedirect() throws Exception
{
Random random = new Random();
byte[] pushBytes = new byte[512];
random.nextBytes(pushBytes);
String oldPath = "/old";
String newPath = "/new";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (target.equals(oldPath))
response.sendRedirect(newPath);
else if (target.equals(newPath))
response.getOutputStream().write(pushBytes);
else
baseRequest.getPushBuilder().path(oldPath).push();
}
});
CountDownLatch latch = new CountDownLatch(1);
HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
ContentResponse response = request
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isSucceeded());
Assert.assertEquals(oldPath, pushedRequest.getPath());
Assert.assertEquals(newPath, result.getRequest().getPath());
Assert.assertArrayEquals(pushBytes, getContent());
latch.countDown();
}
})
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -111,7 +111,6 @@ public class HttpChannelState
{
_interested = interest;
}
private boolean isInterested() { return _interested;}
}
@ -235,7 +234,7 @@ public class HttpChannelState
return Action.TERMINATED;
case ASYNC_WOKEN:
if (_asyncRead.isInterested() && _asyncReadPossible)
if (_asyncReadPossible && _asyncRead.isInterested())
{
_state=State.ASYNC_IO;
_asyncRead=Interest.NONE;
@ -428,9 +427,7 @@ public class HttpChannelState
break;
case STARTED:
// If a read is possible and either we are interested in reads or we have
// to call onAllDataRead, then we need a READ_CALLBACK
if (_asyncReadPossible && (_asyncRead.isInterested() || _channel.getRequest().getHttpInput().isAsyncEOF()))
if (_asyncReadPossible && _asyncRead.isInterested())
{
_state=State.ASYNC_IO;
_asyncRead=Interest.NONE;
@ -1146,7 +1143,9 @@ public class HttpChannelState
_asyncRead=Interest.REGISTERED;
}
else
{
_asyncRead=Interest.NEEDED;
}
}
}
@ -1216,14 +1215,15 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
if (LOG.isDebugEnabled())
LOG.debug("onReadEof {}",toStringLocked());
LOG.debug("onEof {}",toStringLocked());
// Force read interest so onAllDataRead can be called
_asyncRead=Interest.REGISTERED;
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT)
{
woken=true;
_state=State.ASYNC_WOKEN;
_asyncRead=Interest.REGISTERED;
_asyncReadPossible=true;
}
}
return woken;
@ -1239,7 +1239,7 @@ public class HttpChannelState
public boolean onWritePossible()
{
boolean handle=false;
boolean wake=false;
try(Locker.Lock lock= _locker.lock())
{
@ -1250,10 +1250,10 @@ public class HttpChannelState
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
handle=true;
wake=true;
}
}
return handle;
return wake;
}
}

View File

@ -271,7 +271,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
}
// Caclulate minimum request rate for DOS protection
// Calculate minimum request rate for DOS protection
long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate();
if (minRequestDataRate > 0 && _firstByteTimeStamp != -1)
{
@ -296,7 +296,7 @@ public class HttpInput extends ServletInputStream implements Runnable
// Consume any following poison pills
if (item.isEmpty())
pollReadableContent();
nextInterceptedContent();
break;
}
@ -339,11 +339,11 @@ public class HttpInput extends ServletInputStream implements Runnable
*/
protected Content nextContent() throws IOException
{
Content content = pollNonEmptyContent();
Content content = nextNonSentinelContent();
if (content == null && !isFinished())
{
produceContent();
content = pollNonEmptyContent();
content = nextNonSentinelContent();
}
return content;
}
@ -353,47 +353,51 @@ public class HttpInput extends ServletInputStream implements Runnable
*
* @return Content or null
*/
protected Content pollNonEmptyContent()
protected Content nextNonSentinelContent()
{
while (true)
{
// Get the next content (or EOF)
Content content = pollReadableContent();
Content content = nextInterceptedContent();
// If it is EOF, consume it here
if (content instanceof SentinelContent)
{
if (content instanceof EofContent)
{
if (content == EARLY_EOF_CONTENT)
_state = EARLY_EOF;
else if (_listener == null)
_state = EOF;
else
_state = AEOF;
}
// Consume the EOF content, either if it was original content
// or if it was produced by interception
content.succeeded();
if (_content==content)
_content = null;
else if (_intercepted==content)
_intercepted = null;
consume(content);
continue;
}
return content;
}
}
/**
* Get the next readable from the inputQ, calling {@link #produceContent()} if need be. EOF is NOT processed and state is not changed.
*
* @return the content or EOF or null if none available.
* @throws IOException
* if retrieving the content fails
*/
protected Content produceNextContext() throws IOException
{
Content content = nextInterceptedContent();
if (content == null && !isFinished())
{
produceContent();
content = nextInterceptedContent();
}
return content;
}
/**
* Poll the inputQ for Content or EOF. Consumed buffers and non EOF {@link SentinelContent}s are removed. EOF state is not updated.
* Interception is done within this method.
* @return Content with remaining, a {@link SentinelContent}, or null
*/
protected Content pollReadableContent()
protected Content nextInterceptedContent()
{
// If we have a chunk produced by interception
if (_intercepted!=null)
@ -450,29 +454,29 @@ public class HttpInput extends ServletInputStream implements Runnable
}
return null;
}
/**
* Get the next readable from the inputQ, calling {@link #produceContent()} if need be. EOF is NOT processed and state is not changed.
*
* @return the content or EOF or null if none available.
* @throws IOException
* if retrieving the content fails
*/
protected Content nextReadable() throws IOException
private void consume(Content content)
{
Content content = pollReadableContent();
if (content == null && !isFinished())
if (content instanceof EofContent)
{
produceContent();
content = pollReadableContent();
if (content == EARLY_EOF_CONTENT)
_state = EARLY_EOF;
else if (_listener == null)
_state = EOF;
else
_state = AEOF;
}
return content;
// Consume the content, either if it was original content
// or if it was produced by interception
content.succeeded();
if (_content==content)
_content = null;
else if (_intercepted==content)
_intercepted = null;
}
/**
* Copies the given content into the given byte buffer.
*
@ -507,7 +511,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_contentConsumed += l;
if (l > 0 && content.isEmpty())
pollNonEmptyContent(); // hungry succeed
nextNonSentinelContent(); // hungry succeed
}
@ -603,7 +607,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("{} addContent {}",this,content);
if (pollReadableContent()!=null)
if (nextInterceptedContent()!=null)
{
if (_listener == null)
_inputQ.notify();
@ -728,7 +732,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return true;
if (_state instanceof EOFState)
return true;
if (nextReadable() != null)
if (produceNextContext() != null)
return true;
_channelState.onReadUnready();
@ -756,7 +760,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_listener = readListener;
Content content = nextReadable();
Content content = produceNextContext();
if (content!=null)
{
_state = ASYNC;
@ -765,7 +769,7 @@ public class HttpInput extends ServletInputStream implements Runnable
else if (_state == EOF)
{
_state = AEOF;
woken = _channelState.onReadReady();
woken = _channelState.onReadEof();
}
else
{
@ -830,27 +834,23 @@ public class HttpInput extends ServletInputStream implements Runnable
if (!aeof && error==null)
{
Content content = pollReadableContent();
// Consume EOF
Content content = nextInterceptedContent();
if (content==null)
return;
// Consume a directly received EOF without first calling onDataAvailable
// So -1 will never be read and only onAddDataRread or onError will be called
if (content instanceof EofContent)
{
content.succeeded();
if (_content==content)
_content = null;
if (content == EARLY_EOF_CONTENT)
{
_state = EARLY_EOF;
consume(content);
if (_state == EARLY_EOF)
error = _state.getError();
}
else
else if (_state == AEOF)
{
_state = EOF;
aeof = true;
_state = EOF;
}
}
else if (content==null)
return;
}
}
@ -858,6 +858,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (error != null)
{
// TODO is this necessary to add here?
_channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE);
listener.onError(error);
}
@ -868,16 +869,8 @@ public class HttpInput extends ServletInputStream implements Runnable
else
{
listener.onDataAvailable();
synchronized (_inputQ)
{
if (_state == AEOF)
{
_state = EOF;
aeof = !_channelState.isAsyncComplete();
}
}
if (aeof)
listener.onAllDataRead();
// If -1 was read, then HttpChannelState#onEOF will have been called and a subsequent
// unhandle will call run again so onAllDataRead() can be called.
}
}
catch (Throwable e)

View File

@ -160,14 +160,16 @@ public class ErrorHandler extends AbstractHandler
List<String> acceptable=baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT);
if (acceptable.isEmpty() && !baseRequest.getHttpFields().contains(HttpHeader.ACCEPT))
{
generateAcceptableResponse(baseRequest,request,response,code,message,MimeTypes.Type.TEXT_HTML.asString());
}
else
{
for (String mimeType:acceptable)
{
generateAcceptableResponse(baseRequest,request,response,code,message,mimeType);
if (baseRequest.isHandled())
return;
break;
}
}
baseRequest.setHandled(true);

View File

@ -0,0 +1,136 @@
//
// ========================================================================
// 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.ssl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
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.Test;
public class SlowClientsTest
{
private Logger logger = Log.getLogger(getClass());
@Test
public void testSlowClientsWithSmallThreadPool() throws Exception
{
File keystore = MavenTestingUtils.getTestResourceFile("keystore");
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
int maxThreads = 8;
int contentLength = 32 * 1024 * 1024;
QueuedThreadPool serverThreads = new QueuedThreadPool(maxThreads);
serverThreads.setDetailedDump(true);
Server server = new Server(serverThreads);
try
{
ServerConnector connector = new ServerConnector(server, 1, 1, sslContextFactory);
connector.setPort(8888);
server.addConnector(connector);
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
logger.info("SERVING {}", target);
// Write some big content.
response.getOutputStream().write(new byte[contentLength]);
logger.info("SERVED {}", target);
}
});
server.start();
SSLContext sslContext = sslContextFactory.getSslContext();
CompletableFuture[] futures = new CompletableFuture[2 * maxThreads];
ExecutorService executor = Executors.newFixedThreadPool(futures.length);
for (int i = 0; i < futures.length; i++)
{
int k = i;
futures[i] = CompletableFuture.runAsync(() ->
{
try (SSLSocket socket = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", connector.getLocalPort()))
{
socket.setSoTimeout(contentLength / 1024);
OutputStream output = socket.getOutputStream();
String target = "/" + k;
String request = "GET " + target + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
while (serverThreads.getIdleThreads() > 0)
Thread.sleep(50);
InputStream input = socket.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
logger.info("FINISHED {}", target);
}
catch (IOException x)
{
throw new UncheckedIOException(x);
}
catch (InterruptedException x)
{
throw new UncheckedIOException(new InterruptedIOException());
}
}, executor);
}
CompletableFuture.allOf(futures).join();
}
finally
{
server.stop();
}
}
}

View File

@ -630,15 +630,16 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
public boolean put(String s, V v)
{
boolean added = _trie.put(s,v);
while (!added)
while (!added && _growby>0)
{
ArrayTernaryTrie<V> bigger = new ArrayTernaryTrie<>(_trie._key.length+_growby);
for (Map.Entry<String,V> entry : _trie.entrySet())
bigger.put(entry.getKey(),entry.getValue());
_trie = bigger;
added = _trie.put(s,v);
}
return true;
return added;
}
public V get(String s, int offset, int len)

View File

@ -24,6 +24,7 @@ import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
@ -176,20 +177,43 @@ public class RolloverFileOutputStream extends FilterOutputStream
_rollTask=new RollTask();
midnight = ZonedDateTime.now().toLocalDate().atStartOfDay(zone.toZoneId());
midnight = toMidnight(ZonedDateTime.now(), zone.toZoneId());
scheduleNextRollover();
}
}
private void scheduleNextRollover()
/**
* Get the "start of day" for the provided DateTime at the zone specified.
*
* @param dateTime the date time to calculate from
* @param zone the zone to return the date in
* @return start of the day of the date provided
*/
public static ZonedDateTime toMidnight(ZonedDateTime dateTime, ZoneId zone)
{
return dateTime.toLocalDate().atStartOfDay(zone);
}
/**
* Get the next "start of day" for the provided date.
*
* @param dateTime the date to calculate from
* @return the start of the next day
*/
public static ZonedDateTime nextMidnight(ZonedDateTime dateTime)
{
// Increment to next day.
// Using Calendar.add(DAY, 1) takes in account Daylights Savings
// differences, and still maintains the "midnight" settings for
// Hour, Minute, Second, Milliseconds
midnight = midnight.toLocalDate().plus(1, ChronoUnit.DAYS).atStartOfDay(midnight.getZone());
__rollover.schedule(_rollTask,midnight.toInstant().toEpochMilli());
return dateTime.toLocalDate().plus(1, ChronoUnit.DAYS).atStartOfDay(dateTime.getZone());
}
private void scheduleNextRollover()
{
midnight = nextMidnight(midnight);
__rollover.schedule(_rollTask,midnight.toInstant().toEpochMilli() - System.currentTimeMillis());
}
/* ------------------------------------------------------------ */

View File

@ -0,0 +1,147 @@
//
// ========================================================================
// 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 org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.TimeZone;
import org.junit.Test;
public class RolloverFileOutputStreamTest
{
private static ZoneId toZoneId(String timezoneId)
{
ZoneId zone = TimeZone.getTimeZone(timezoneId).toZoneId();
// System.out.printf(".toZoneId(\"%s\") = [id=%s,normalized=%s]%n", timezoneId, zone.getId(), zone.normalized());
return zone;
}
private static ZonedDateTime toDateTime(String timendate, ZoneId zone)
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd-hh:mm:ss.S a z")
.withZone(zone);
return ZonedDateTime.parse(timendate, formatter);
}
private static String toString(TemporalAccessor date)
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd-hh:mm:ss.S a z");
return formatter.format(date);
}
private void assertSequence(ZonedDateTime midnight, Object[][] expected)
{
ZonedDateTime nextEvent = midnight;
for (int i = 0; i < expected.length; i++)
{
long lastMs = nextEvent.toInstant().toEpochMilli();
nextEvent = RolloverFileOutputStream.nextMidnight(nextEvent);
assertThat("Next Event", toString(nextEvent), is(expected[i][0]));
long duration = (nextEvent.toInstant().toEpochMilli() - lastMs);
assertThat("Duration to next event", duration, is((long) expected[i][1]));
}
}
@Test
public void testMidnightRolloverCalc_PST_DST_Start()
{
ZoneId zone = toZoneId("PST");
ZonedDateTime initialDate = toDateTime("2016.03.11-01:23:45.0 PM PST", zone);
ZonedDateTime midnight = RolloverFileOutputStream.toMidnight(initialDate, zone);
assertThat("Midnight", toString(midnight), is("2016.03.11-12:00:00.0 AM PST"));
Object expected[][] = {
{"2016.03.12-12:00:00.0 AM PST", 86_400_000L},
{"2016.03.13-12:00:00.0 AM PST", 86_400_000L},
{"2016.03.14-12:00:00.0 AM PDT", 82_800_000L}, // the short day
{"2016.03.15-12:00:00.0 AM PDT", 86_400_000L},
{"2016.03.16-12:00:00.0 AM PDT", 86_400_000L},
};
assertSequence(midnight, expected);
}
@Test
public void testMidnightRolloverCalc_PST_DST_End()
{
ZoneId zone = toZoneId("PST");
ZonedDateTime initialDate = toDateTime("2016.11.04-11:22:33.0 AM PDT", zone);
ZonedDateTime midnight = RolloverFileOutputStream.toMidnight(initialDate, zone);
assertThat("Midnight", toString(midnight), is("2016.11.04-12:00:00.0 AM PDT"));
Object expected[][] = {
{"2016.11.05-12:00:00.0 AM PDT", 86_400_000L},
{"2016.11.06-12:00:00.0 AM PDT", 86_400_000L},
{"2016.11.07-12:00:00.0 AM PST", 90_000_000L}, // the long day
{"2016.11.08-12:00:00.0 AM PST", 86_400_000L},
{"2016.11.09-12:00:00.0 AM PST", 86_400_000L},
};
assertSequence(midnight, expected);
}
@Test
public void testMidnightRolloverCalc_Sydney_DST_Start()
{
ZoneId zone = toZoneId("Australia/Sydney");
ZonedDateTime initialDate = toDateTime("2016.10.01-01:23:45.0 PM AEST", zone);
ZonedDateTime midnight = RolloverFileOutputStream.toMidnight(initialDate, zone);
assertThat("Midnight", toString(midnight), is("2016.10.01-12:00:00.0 AM AEST"));
Object expected[][] = {
{"2016.10.02-12:00:00.0 AM AEST", 86_400_000L},
{"2016.10.03-12:00:00.0 AM AEDT", 82_800_000L}, // the short day
{"2016.10.04-12:00:00.0 AM AEDT", 86_400_000L},
{"2016.10.05-12:00:00.0 AM AEDT", 86_400_000L},
{"2016.10.06-12:00:00.0 AM AEDT", 86_400_000L},
};
assertSequence(midnight, expected);
}
@Test
public void testMidnightRolloverCalc_Sydney_DST_End()
{
ZoneId zone = toZoneId("Australia/Sydney");
ZonedDateTime initialDate = toDateTime("2016.04.02-11:22:33.0 AM AEDT", zone);
ZonedDateTime midnight = RolloverFileOutputStream.toMidnight(initialDate, zone);
assertThat("Midnight", toString(midnight), is("2016.04.02-12:00:00.0 AM AEDT"));
Object expected[][] = {
{"2016.04.03-12:00:00.0 AM AEDT", 86_400_000L},
{"2016.04.04-12:00:00.0 AM AEST", 90_000_000L}, // The long day
{"2016.04.05-12:00:00.0 AM AEST", 86_400_000L},
{"2016.04.06-12:00:00.0 AM AEST", 86_400_000L},
{"2016.04.07-12:00:00.0 AM AEST", 86_400_000L},
};
assertSequence(midnight, expected);
}
}

View File

@ -180,8 +180,7 @@ public class ClasspathPattern extends AbstractSet<String>
if (_entries.get(name)!=null)
return false;
_entries.put(name,entry);
return true;
return _entries.put(name,entry);
}
@Override

View File

@ -228,4 +228,21 @@ public class ClasspathPatternTest
assertThat(pattern.match(JDK.class),is(true));
assertThat(pattern.match(ClasspathPatternTest.class),is(false));
}
@Test
public void testLarge()
{
ClasspathPattern pattern = new ClasspathPattern();
for (int i=0; i<500; i++)
{
assertTrue(pattern.add("n"+i+"."+Integer.toHexString(100+i)+".Name"));
}
for (int i=0; i<500; i++)
{
assertTrue(pattern.match("n"+i+"."+Integer.toHexString(100+i)+".Name"));
}
}
}

View File

@ -21,14 +21,15 @@ package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
@ -40,6 +41,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
@ -72,7 +74,6 @@ import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
import org.eclipse.jetty.websocket.common.test.RawFrameBuilder;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@ -91,14 +92,15 @@ public class ClientCloseTest
public CountDownLatch closeLatch = new CountDownLatch(1);
public AtomicInteger closeCount = new AtomicInteger(0);
public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch errorLatch = new CountDownLatch(1);
public EventQueue<String> messageQueue = new EventQueue<>();
public EventQueue<Throwable> errorQueue = new EventQueue<>();
public AtomicReference<Throwable> error = new AtomicReference<>();
public void assertNoCloseEvent()
{
Assert.assertThat("Client Close Event",closeLatch.getCount(),is(1L));
Assert.assertThat("Client Close Event Status Code ",closeCode,is(-1));
assertThat("Client Close Event",closeLatch.getCount(),is(1L));
assertThat("Client Close Event Status Code ",closeCode,is(-1));
}
public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher<Integer> statusCodeMatcher, Matcher<String> reasonMatcher)
@ -106,39 +108,22 @@ public class ClientCloseTest
{
long maxTimeout = clientTimeoutMs * 2;
Assert.assertThat("Client Close Event Occurred",closeLatch.await(maxTimeout,TimeUnit.MILLISECONDS),is(true));
Assert.assertThat("Client Close Event Count",closeCount.get(),is(1));
Assert.assertThat("Client Close Event Status Code",closeCode,statusCodeMatcher);
assertThat("Client Close Event Occurred",closeLatch.await(maxTimeout,TimeUnit.MILLISECONDS),is(true));
assertThat("Client Close Event Count",closeCount.get(),is(1));
assertThat("Client Close Event Status Code",closeCode,statusCodeMatcher);
if (reasonMatcher == null)
{
Assert.assertThat("Client Close Event Reason",closeReason,nullValue());
assertThat("Client Close Event Reason",closeReason,nullValue());
}
else
{
Assert.assertThat("Client Close Event Reason",closeReason,reasonMatcher);
}
}
public void assertReceivedError(Class<? extends Throwable> expectedThrownClass, Matcher<String> messageMatcher) throws TimeoutException,
InterruptedException
{
errorQueue.awaitEventCount(1,30,TimeUnit.SECONDS);
Throwable actual = errorQueue.poll();
Assert.assertThat("Client Error Event",actual,instanceOf(expectedThrownClass));
if (messageMatcher == null)
{
Assert.assertThat("Client Error Event Message",actual.getMessage(),nullValue());
}
else
{
Assert.assertThat("Client Error Event Message",actual.getMessage(),messageMatcher);
assertThat("Client Close Event Reason",closeReason,reasonMatcher);
}
}
public void clearQueues()
{
messageQueue.clear();
errorQueue.clear();
}
@Override
@ -164,7 +149,8 @@ public class ClientCloseTest
public void onWebSocketError(Throwable cause)
{
LOG.debug("onWebSocketError",cause);
Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
assertThat("Unique Error Event", error.compareAndSet(null, cause), is(true));
errorLatch.countDown();
}
@Override
@ -177,15 +163,15 @@ public class ClientCloseTest
public EndPoint getEndPoint() throws Exception
{
Session session = getSession();
Assert.assertThat("Session type",session,instanceOf(WebSocketSession.class));
assertThat("Session type",session,instanceOf(WebSocketSession.class));
WebSocketSession wssession = (WebSocketSession)session;
Field fld = wssession.getClass().getDeclaredField("connection");
fld.setAccessible(true);
Assert.assertThat("Field: connection",fld,notNullValue());
assertThat("Field: connection",fld,notNullValue());
Object val = fld.get(wssession);
Assert.assertThat("Connection type",val,instanceOf(AbstractWebSocketConnection.class));
assertThat("Connection type",val,instanceOf(AbstractWebSocketConnection.class));
@SuppressWarnings("resource")
AbstractWebSocketConnection wsconn = (AbstractWebSocketConnection)val;
return wsconn.getEndPoint();
@ -204,7 +190,7 @@ public class ClientCloseTest
clientFuture.get(30,TimeUnit.SECONDS);
// Wait for client connect via client websocket
Assert.assertThat("Client WebSocket is Open",clientSocket.openLatch.await(30,TimeUnit.SECONDS),is(true));
assertThat("Client WebSocket is Open",clientSocket.openLatch.await(30,TimeUnit.SECONDS),is(true));
try
{
@ -220,8 +206,8 @@ public class ClientCloseTest
serverCapture.assertNoErrors();
serverCapture.assertFrameCount(1);
WebSocketFrame frame = serverCapture.getFrames().poll();
Assert.assertThat("Server received frame",frame.getOpCode(),is(OpCode.TEXT));
Assert.assertThat("Server received frame payload",frame.getPayloadAsUTF8(),is(echoMsg));
assertThat("Server received frame",frame.getOpCode(),is(OpCode.TEXT));
assertThat("Server received frame payload",frame.getPayloadAsUTF8(),is(echoMsg));
// Server send echo reply
serverConns.write(new TextFrame().setPayload(echoMsg));
@ -231,10 +217,10 @@ public class ClientCloseTest
// Verify received message
String recvMsg = clientSocket.messageQueue.poll();
Assert.assertThat("Received message",recvMsg,is(echoMsg));
assertThat("Received message",recvMsg,is(echoMsg));
// Verify that there are no errors
Assert.assertThat("Error events",clientSocket.errorQueue,empty());
assertThat("Error events",clientSocket.error.get(),nullValue());
}
finally
{
@ -250,16 +236,16 @@ public class ClientCloseTest
serverCapture.assertFrameCount(1);
serverCapture.assertHasFrame(OpCode.CLOSE,1);
WebSocketFrame frame = serverCapture.getFrames().poll();
Assert.assertThat("Server received close frame",frame.getOpCode(),is(OpCode.CLOSE));
assertThat("Server received close frame",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo closeInfo = new CloseInfo(frame);
Assert.assertThat("Server received close code",closeInfo.getStatusCode(),is(expectedCloseCode));
assertThat("Server received close code",closeInfo.getStatusCode(),is(expectedCloseCode));
if (closeReasonMatcher == null)
{
Assert.assertThat("Server received close reason",closeInfo.getReason(),nullValue());
assertThat("Server received close reason",closeInfo.getReason(),nullValue());
}
else
{
Assert.assertThat("Server received close reason",closeInfo.getReason(),closeReasonMatcher);
assertThat("Server received close reason",closeInfo.getReason(),closeReasonMatcher);
}
}
@ -364,12 +350,12 @@ public class ClientCloseTest
// Verify received messages
String recvMsg = clientSocket.messageQueue.poll();
Assert.assertThat("Received message 1",recvMsg,is("Hello"));
assertThat("Received message 1",recvMsg,is("Hello"));
recvMsg = clientSocket.messageQueue.poll();
Assert.assertThat("Received message 2",recvMsg,is("World"));
assertThat("Received message 2",recvMsg,is("World"));
// Verify that there are no errors
Assert.assertThat("Error events",clientSocket.errorQueue,empty());
assertThat("Error events",clientSocket.error.get(),nullValue());
// client close event on ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server"));
@ -399,7 +385,7 @@ public class ClientCloseTest
// when write is congested, client enqueue close frame
// client initiate write, but write never completes
EndPoint endp = clientSocket.getEndPoint();
Assert.assertThat("EndPoint is testable",endp,instanceOf(TestEndPoint.class));
assertThat("EndPoint is testable",endp,instanceOf(TestEndPoint.class));
TestEndPoint testendp = (TestEndPoint)endp;
char msg[] = new char[10240];
@ -418,7 +404,7 @@ public class ClientCloseTest
LOG.debug("Wrote {} frames totalling {} bytes of payload before congestion kicked in",writeCount,writeSize);
// Verify that there are no errors
Assert.assertThat("Error events",clientSocket.errorQueue,empty());
assertThat("Error events",clientSocket.error.get(),nullValue());
// client idle timeout triggers close event on client ws-endpoint
// client close event on ws-endpoint
@ -462,7 +448,9 @@ public class ClientCloseTest
serverConn.write(bad);
// client should have noticed the error
clientSocket.assertReceivedError(ProtocolException.class,containsString("Invalid control frame"));
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(ProtocolException.class));
assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Invalid control frame"));
// client parse invalid frame, notifies server of close (protocol error)
confirmServerReceivedCloseFrame(serverConn,StatusCode.PROTOCOL,allOf(containsString("Invalid control frame"),containsString("length")));
@ -512,8 +500,6 @@ public class ClientCloseTest
}
@Test
// TODO work out why this test is failing
@Ignore
public void testServerNoCloseHandshake() throws Exception
{
// Set client timeout
@ -545,7 +531,9 @@ public class ClientCloseTest
// server sits idle
// client idle timeout triggers close event on client ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout"));
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(SocketTimeoutException.class));
assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Timeout on Read"));
}
@Test(timeout = 5000L)
@ -617,8 +605,9 @@ public class ClientCloseTest
// client write failure
final String origCloseReason = "Normal Close";
clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason);
clientSocket.assertReceivedError(EofException.class,null);
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(EofException.class));
// client triggers close event on client ws-endpoint
// assert - close code==1006 (abnormal)

View File

@ -1,58 +0,0 @@
//
// ========================================================================
// 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.websocket.common.io;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.api.WriteCallback;
/**
* Wraps the exposed {@link WriteCallback} WebSocket API with a Jetty {@link Callback}.
* <p>
* We don't expose the jetty {@link Callback} object to the webapp, as that makes things complicated for the WebAppContext's Classloader.
*/
public class WriteCallbackWrapper implements Callback
{
public static Callback wrap(WriteCallback callback)
{
if (callback == null)
{
return null;
}
return new WriteCallbackWrapper(callback);
}
private final WriteCallback callback;
public WriteCallbackWrapper(WriteCallback callback)
{
this.callback = callback;
}
@Override
public void failed(Throwable x)
{
callback.writeFailed(x);
}
@Override
public void succeeded()
{
callback.writeSuccess();
}
}

View File

@ -1,81 +0,0 @@
//
// ========================================================================
// 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.websocket.common.io;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.api.WriteCallback;
/**
* Tracking Callback for testing how the callbacks are used.
*/
public class TrackingCallback implements Callback, WriteCallback
{
private AtomicInteger called = new AtomicInteger();
private boolean success = false;
private Throwable failure = null;
@Override
public void failed(Throwable x)
{
this.called.incrementAndGet();
this.success = false;
this.failure = x;
}
@Override
public void succeeded()
{
this.called.incrementAndGet();
this.success = false;
}
public Throwable getFailure()
{
return failure;
}
public boolean isSuccess()
{
return success;
}
public boolean isCalled()
{
return called.get() >= 1;
}
public int getCallCount()
{
return called.get();
}
@Override
public void writeFailed(Throwable x)
{
failed(x);
}
@Override
public void writeSuccess()
{
succeeded();
}
}

View File

@ -7,6 +7,7 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>10.0.0-SNAPSHOT</version>
<name>Jetty :: Project</name>
<description>The Eclipse Jetty Project</description>
<packaging>pom</packaging>

View File

@ -146,9 +146,9 @@ public class HttpChannelAssociationTest extends AbstractTest
return new HttpConnectionOverHTTP2(destination, session)
{
@Override
protected HttpChannelOverHTTP2 newHttpChannel()
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
{
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession())
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push)
{
@Override
public boolean associate(HttpExchange exchange)

View File

@ -42,6 +42,7 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.FlowControlStrategy;
@ -545,6 +546,29 @@ public class HttpClientTest extends AbstractTest
Assert.assertEquals(0, response.getContent().length);
}
@Test
public void testHEADWithAcceptHeaderAndSendError() throws Exception
{
int status = HttpStatus.BAD_REQUEST_400;
start(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.sendError(status);
}
});
ContentResponse response = client.newRequest(newURI())
.method(HttpMethod.HEAD)
.path(servletPath)
.header(HttpHeader.ACCEPT, "*/*")
.send();
Assert.assertEquals(status, response.getStatus());
Assert.assertEquals(0, response.getContent().length);
}
private void sleep(long time) throws IOException
{
try