Merged branch 'jetty-9.4.x' into 'jetty-9.4.x-3601-http2_stall_on_reset_stream'.
This commit is contained in:
commit
753024af54
|
@ -73,6 +73,7 @@ pipeline {
|
||||||
agent { node { label 'linux' } }
|
agent { node { label 'linux' } }
|
||||||
options { timeout(time: 30, unit: 'MINUTES') }
|
options { timeout(time: 30, unit: 'MINUTES') }
|
||||||
steps {
|
steps {
|
||||||
|
mavenBuild("jdk11", "install -f build-resources", "maven3", true)
|
||||||
mavenBuild("jdk11", "install checkstyle:check -DskipTests", "maven3", true)
|
mavenBuild("jdk11", "install checkstyle:check -DskipTests", "maven3", true)
|
||||||
recordIssues(
|
recordIssues(
|
||||||
enabledForFailure: true, aggregatingResults: true,
|
enabledForFailure: true, aggregatingResults: true,
|
||||||
|
@ -137,15 +138,13 @@ def slackNotif() {
|
||||||
* @return the Jenkinsfile step representing a maven build
|
* @return the Jenkinsfile step representing a maven build
|
||||||
*/
|
*/
|
||||||
def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) {
|
def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) {
|
||||||
def localRepo = "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}" // ".repository" //
|
def localRepo = ".repository"
|
||||||
def settingsName = 'oss-settings.xml'
|
|
||||||
def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true'
|
def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true'
|
||||||
|
|
||||||
withMaven(
|
withMaven(
|
||||||
maven: mvnName,
|
maven: mvnName,
|
||||||
jdk: "$jdk",
|
jdk: "$jdk",
|
||||||
publisherStrategy: 'EXPLICIT',
|
publisherStrategy: 'EXPLICIT',
|
||||||
globalMavenSettingsConfig: settingsName,
|
|
||||||
options: [junitPublisher(disabled: junitPublishDisabled),mavenLinkerPublisher(disabled: false),pipelineGraphPublisher(disabled: false)],
|
options: [junitPublisher(disabled: junitPublishDisabled),mavenLinkerPublisher(disabled: false),pipelineGraphPublisher(disabled: false)],
|
||||||
mavenOpts: mavenOpts,
|
mavenOpts: mavenOpts,
|
||||||
mavenLocalRepo: localRepo) {
|
mavenLocalRepo: localRepo) {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class HttpClientCorrelationDataTest extends AbstractHttpClientServerTest
|
||||||
|
{
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(ScenarioProvider.class)
|
||||||
|
public void testCorrelationData(Scenario scenario) throws Exception
|
||||||
|
{
|
||||||
|
String correlationName = "X-Correlation-Data";
|
||||||
|
String correlationData = "123456";
|
||||||
|
ThreadLocal<String> correlation = new ThreadLocal<>();
|
||||||
|
|
||||||
|
start(scenario, new EmptyServerHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
assertEquals(correlationData, request.getHeader(correlationName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.getRequestListeners().add(new Request.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onQueued(Request request)
|
||||||
|
{
|
||||||
|
request.header(correlationName, correlation.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
correlation.set(correlationData);
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scenario.getScheme())
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,7 +125,7 @@ public class ResourceHttpContent implements HttpContent
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getDirectBuffer()
|
public ByteBuffer getDirectBuffer()
|
||||||
{
|
{
|
||||||
if (_resource.length() <= 0 || _maxBuffer > 0 && _maxBuffer < _resource.length())
|
if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer)
|
||||||
return null;
|
return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -152,7 +152,7 @@ public class ResourceHttpContent implements HttpContent
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getIndirectBuffer()
|
public ByteBuffer getIndirectBuffer()
|
||||||
{
|
{
|
||||||
if (_resource.length() <= 0 || _maxBuffer > 0 && _maxBuffer < _resource.length())
|
if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer)
|
||||||
return null;
|
return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -443,7 +443,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug(String.format("HTTP2 Response #%d/%h idle timeout", stream.getId(), stream.getSession()), failure);
|
LOG.debug(String.format("HTTP2 Response #%d/%h idle timeout %s", stream.getId(), stream.getSession(), result ? "expired" : "ignored"), failure);
|
||||||
if (result)
|
if (result)
|
||||||
callback.failed(failure);
|
callback.failed(failure);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -144,6 +144,16 @@
|
||||||
<artifactId>maven-war-plugin</artifactId>
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
<version>@maven.war.plugin.version@</version>
|
<version>@maven.war.plugin.version@</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
|
<version>@maven.install.plugin.version@</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>@maven.deploy.plugin.version@</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
</build>
|
</build>
|
||||||
|
|
|
@ -20,7 +20,7 @@ File buildLog = new File( basedir, 'build.log' )
|
||||||
assert buildLog.text.contains( 'Started Jetty Server' )
|
assert buildLog.text.contains( 'Started Jetty Server' )
|
||||||
|
|
||||||
assert buildLog.text.contains( '(1a) >> javax.servlet.ServletContextListener loaded from jar:' )
|
assert buildLog.text.contains( '(1a) >> javax.servlet.ServletContextListener loaded from jar:' )
|
||||||
assert buildLog.text.contains( 'local-repo/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar!/javax/servlet/ServletContextListener.class << (1b)' )
|
assert buildLog.text.contains( 'javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar!/javax/servlet/ServletContextListener.class << (1b)' )
|
||||||
|
|
||||||
assert buildLog.text.contains( '(2a) >> mca.common.CommonService loaded from file:' )
|
assert buildLog.text.contains( '(2a) >> mca.common.CommonService loaded from file:' )
|
||||||
assert buildLog.text.contains( 'common/target/classes/mca/common/CommonService.class << (2b)' )
|
assert buildLog.text.contains( 'common/target/classes/mca/common/CommonService.class << (2b)' )
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<settings>
|
<settings>
|
||||||
|
<mirrors>
|
||||||
|
<mirror>
|
||||||
|
<id>local.mirror</id>
|
||||||
|
<url>file://@localRepo@</url>
|
||||||
|
<mirrorOf>central</mirrorOf>
|
||||||
|
</mirror>
|
||||||
|
</mirrors>
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>it-repo</id>
|
<id>it-repo</id>
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
<bundle-symbolic-name>${project.groupId}.boot.test.osgi</bundle-symbolic-name>
|
<bundle-symbolic-name>${project.groupId}.boot.test.osgi</bundle-symbolic-name>
|
||||||
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
|
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
|
||||||
<assembly-directory>target/distribution</assembly-directory>
|
<assembly-directory>target/distribution</assembly-directory>
|
||||||
<exam.version>4.12.0</exam.version>
|
<exam.version>4.13.1</exam.version>
|
||||||
<url.version>2.5.2</url.version>
|
<url.version>2.6.1</url.version>
|
||||||
<injection.bundle.version>1.0</injection.bundle.version>
|
<injection.bundle.version>1.0</injection.bundle.version>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
</properties>
|
</properties>
|
||||||
|
@ -449,6 +449,7 @@
|
||||||
<skipTests>${skipTests}</skipTests>
|
<skipTests>${skipTests}</skipTests>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<mavenRepoPath>${settings.localRepository}</mavenRepoPath>
|
<mavenRepoPath>${settings.localRepository}</mavenRepoPath>
|
||||||
|
<settingsFilePath>${env.GLOBAL_MVN_SETTINGS}</settingsFilePath>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
<!-- paxexam still using junit 4 so we have to force the provider here -->
|
<!-- paxexam still using junit 4 so we have to force the provider here -->
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
|
import org.ops4j.pax.url.mvn.internal.AetherBasedResolver;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
import org.osgi.framework.ServiceReference;
|
||||||
|
@ -106,11 +107,22 @@ public class TestOSGiUtil
|
||||||
|
|
||||||
public static List<Option> coreJettyDependencies()
|
public static List<Option> coreJettyDependencies()
|
||||||
{
|
{
|
||||||
|
AetherBasedResolver l;
|
||||||
List<Option> res = new ArrayList<>();
|
List<Option> res = new ArrayList<>();
|
||||||
res.add(systemProperty("bundle.debug").value(Boolean.toString(Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))));
|
res.add(systemProperty("bundle.debug").value(Boolean.toString(Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))));
|
||||||
String mavenRepoPath = System.getProperty("mavenRepoPath");
|
String mavenRepoPath = System.getProperty("mavenRepoPath");
|
||||||
if (!StringUtil.isBlank(mavenRepoPath))
|
if (!StringUtil.isBlank(mavenRepoPath))
|
||||||
res.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(mavenRepoPath));
|
{
|
||||||
|
res.add( systemProperty( "org.ops4j.pax.url.mvn.localRepository" ).value( mavenRepoPath ) );
|
||||||
|
res.add( systemProperty( "org.ops4j.pax.url.mvn.defaultRepositories" ).value( "file://" + mavenRepoPath + "@id=local.repo") );
|
||||||
|
res.add( systemProperty( "org.ops4j.pax.url.mvn.useFallbackRepositories").value( Boolean.FALSE.toString() ) );
|
||||||
|
res.add( systemProperty( "org.ops4j.pax.url.mvn.repositories").value( "+https://repo1.maven.org/maven2@id=maven.central.repo" ) );
|
||||||
|
}
|
||||||
|
String settingsFilePath = System.getProperty("settingsFilePath");
|
||||||
|
if (!StringUtil.isBlank(settingsFilePath))
|
||||||
|
{
|
||||||
|
res.add( systemProperty( "org.ops4j.pax.url.mvn.settings" ).value( System.getProperty( "settingsFilePath" ) ) );
|
||||||
|
}
|
||||||
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm").versionAsInProject().start());
|
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm").versionAsInProject().start());
|
||||||
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-commons").versionAsInProject().start());
|
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-commons").versionAsInProject().start());
|
||||||
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-tree").versionAsInProject().start());
|
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-tree").versionAsInProject().start());
|
||||||
|
|
|
@ -335,13 +335,14 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return BufferUtil.toBuffer(resource, true);
|
return BufferUtil.toBuffer(resource, false);
|
||||||
}
|
}
|
||||||
catch (IOException | IllegalArgumentException e)
|
catch (IOException | IllegalArgumentException e)
|
||||||
{
|
{
|
||||||
LOG.warn(e);
|
if (LOG.isDebugEnabled())
|
||||||
return null;
|
LOG.debug(e);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ByteBuffer getMappedBuffer(Resource resource)
|
protected ByteBuffer getMappedBuffer(Resource resource)
|
||||||
|
@ -355,7 +356,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
}
|
}
|
||||||
catch (IOException | IllegalArgumentException e)
|
catch (IOException | IllegalArgumentException e)
|
||||||
{
|
{
|
||||||
LOG.warn(e);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug(e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +370,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
}
|
}
|
||||||
catch (IOException | IllegalArgumentException e)
|
catch (IOException | IllegalArgumentException e)
|
||||||
{
|
{
|
||||||
LOG.warn(e);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug(e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -386,7 +389,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
{
|
{
|
||||||
private final String _key;
|
private final String _key;
|
||||||
private final Resource _resource;
|
private final Resource _resource;
|
||||||
private final int _contentLengthValue;
|
private final long _contentLengthValue;
|
||||||
private final HttpField _contentType;
|
private final HttpField _contentType;
|
||||||
private final String _characterEncoding;
|
private final String _characterEncoding;
|
||||||
private final MimeTypes.Type _mimeType;
|
private final MimeTypes.Type _mimeType;
|
||||||
|
@ -415,7 +418,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
_lastModified = _lastModifiedValue == -1 ? null
|
_lastModified = _lastModifiedValue == -1 ? null
|
||||||
: new PreEncodedHttpField(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(_lastModifiedValue));
|
: new PreEncodedHttpField(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(_lastModifiedValue));
|
||||||
|
|
||||||
_contentLengthValue = exists ? (int)resource.length() : 0;
|
_contentLengthValue = exists ? resource.length() : 0;
|
||||||
_contentLength = new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH, Long.toString(_contentLengthValue));
|
_contentLength = new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH, Long.toString(_contentLengthValue));
|
||||||
|
|
||||||
if (_cachedFiles.incrementAndGet() > _maxCachedFiles)
|
if (_cachedFiles.incrementAndGet() > _maxCachedFiles)
|
||||||
|
@ -552,25 +555,34 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getIndirectBuffer()
|
public ByteBuffer getIndirectBuffer()
|
||||||
{
|
{
|
||||||
|
if (_resource.length() > _maxCachedFileSize)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
ByteBuffer buffer = _indirectBuffer.get();
|
ByteBuffer buffer = _indirectBuffer.get();
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
ByteBuffer buffer2 = CachedContentFactory.this.getIndirectBuffer(_resource);
|
ByteBuffer buffer2 = CachedContentFactory.this.getIndirectBuffer(_resource);
|
||||||
|
|
||||||
if (buffer2 == null)
|
if (buffer2 == null)
|
||||||
LOG.warn("Could not load " + this);
|
{
|
||||||
else if (_indirectBuffer.compareAndSet(null, buffer2))
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Could not load indirect buffer from " + this);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_indirectBuffer.compareAndSet(null, buffer2))
|
||||||
{
|
{
|
||||||
buffer = buffer2;
|
buffer = buffer2;
|
||||||
if (_cachedSize.addAndGet(BufferUtil.length(buffer)) > _maxCacheSize)
|
if (_cachedSize.addAndGet(BufferUtil.length(buffer)) > _maxCacheSize)
|
||||||
shrinkCache();
|
shrinkCache();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
buffer = _indirectBuffer.get();
|
buffer = _indirectBuffer.get();
|
||||||
}
|
}
|
||||||
if (buffer == null)
|
}
|
||||||
return null;
|
return buffer == null ? null : buffer.asReadOnlyBuffer();
|
||||||
return buffer.slice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -589,7 +601,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
else
|
else
|
||||||
buffer = _mappedBuffer.get();
|
buffer = _mappedBuffer.get();
|
||||||
}
|
}
|
||||||
else
|
// Since MappedBuffers don't use heap, we don't care about the resource.length
|
||||||
|
else if (_resource.length() < _maxCachedFileSize)
|
||||||
{
|
{
|
||||||
ByteBuffer direct = CachedContentFactory.this.getDirectBuffer(_resource);
|
ByteBuffer direct = CachedContentFactory.this.getDirectBuffer(_resource);
|
||||||
if (direct != null)
|
if (direct != null)
|
||||||
|
@ -607,7 +620,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG.warn("Could not load " + this);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Could not load " + this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,11 +318,23 @@ public class HttpConfiguration implements Dumpable
|
||||||
return _sendXPoweredBy;
|
return _sendXPoweredBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the {@code Date} header should be sent in responses.
|
||||||
|
*
|
||||||
|
* @param sendDateHeader true if the {@code Date} header should be sent in responses
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">HTTP/1.1 Standard Header: Date</a>
|
||||||
|
* @see #getSendDateHeader()
|
||||||
|
*/
|
||||||
public void setSendDateHeader(boolean sendDateHeader)
|
public void setSendDateHeader(boolean sendDateHeader)
|
||||||
{
|
{
|
||||||
_sendDateHeader = sendDateHeader;
|
_sendDateHeader = sendDateHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the {@code Date} header will be sent in responses.
|
||||||
|
*
|
||||||
|
* @return true by default
|
||||||
|
*/
|
||||||
@ManagedAttribute("Whether to send the Date header in responses")
|
@ManagedAttribute("Whether to send the Date header in responses")
|
||||||
public boolean getSendDateHeader()
|
public boolean getSendDateHeader()
|
||||||
{
|
{
|
||||||
|
|
|
@ -682,9 +682,21 @@ public class Request implements HttpServletRequest
|
||||||
MetaData.Request metadata = _metaData;
|
MetaData.Request metadata = _metaData;
|
||||||
if (metadata == null)
|
if (metadata == null)
|
||||||
return -1;
|
return -1;
|
||||||
if (metadata.getContentLength() != Long.MIN_VALUE)
|
|
||||||
return (int)metadata.getContentLength();
|
long contentLength = metadata.getContentLength();
|
||||||
return (int)metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.toString());
|
if (contentLength != Long.MIN_VALUE)
|
||||||
|
{
|
||||||
|
if (contentLength > Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
// Per ServletRequest#getContentLength() javadoc this must return -1 for values exceeding Integer.MAX_VALUE
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (int)contentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (int)metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -698,7 +710,7 @@ public class Request implements HttpServletRequest
|
||||||
return -1L;
|
return -1L;
|
||||||
if (metadata.getContentLength() != Long.MIN_VALUE)
|
if (metadata.getContentLength() != Long.MIN_VALUE)
|
||||||
return metadata.getContentLength();
|
return metadata.getContentLength();
|
||||||
return metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.toString());
|
return metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getContentRead()
|
public long getContentRead()
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -47,9 +46,10 @@ import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||||
import org.eclipse.jetty.http.QuotedCSV;
|
import org.eclipse.jetty.http.QuotedCSV;
|
||||||
import org.eclipse.jetty.http.QuotedQualityCSV;
|
import org.eclipse.jetty.http.QuotedQualityCSV;
|
||||||
import org.eclipse.jetty.io.WriterOutputStream;
|
import org.eclipse.jetty.io.WriterOutputStream;
|
||||||
|
import org.eclipse.jetty.server.resource.HttpContentRangeWriter;
|
||||||
|
import org.eclipse.jetty.server.resource.RangeWriter;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IO;
|
|
||||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -779,9 +779,6 @@ public class ResourceService
|
||||||
ctp = "multipart/byteranges; boundary=";
|
ctp = "multipart/byteranges; boundary=";
|
||||||
response.setContentType(ctp + multi.getBoundary());
|
response.setContentType(ctp + multi.getBoundary());
|
||||||
|
|
||||||
InputStream in = content.getResource().getInputStream();
|
|
||||||
long pos = 0;
|
|
||||||
|
|
||||||
// calculate the content-length
|
// calculate the content-length
|
||||||
int length = 0;
|
int length = 0;
|
||||||
String[] header = new String[ranges.size()];
|
String[] header = new String[ranges.size()];
|
||||||
|
@ -801,39 +798,17 @@ public class ResourceService
|
||||||
length += 2 + 2 + multi.getBoundary().length() + 2 + 2;
|
length += 2 + 2 + multi.getBoundary().length() + 2 + 2;
|
||||||
response.setContentLength(length);
|
response.setContentLength(length);
|
||||||
|
|
||||||
|
try (RangeWriter rangeWriter = HttpContentRangeWriter.newRangeWriter(content))
|
||||||
|
{
|
||||||
i = 0;
|
i = 0;
|
||||||
for (InclusiveByteRange ibr : ranges)
|
for (InclusiveByteRange ibr : ranges)
|
||||||
{
|
{
|
||||||
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
|
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
|
||||||
|
rangeWriter.writeTo(multi, ibr.getFirst(), ibr.getSize());
|
||||||
long start = ibr.getFirst();
|
|
||||||
long size = ibr.getSize();
|
|
||||||
if (in != null)
|
|
||||||
{
|
|
||||||
// Handle non cached resource
|
|
||||||
if (start < pos)
|
|
||||||
{
|
|
||||||
in.close();
|
|
||||||
in = content.getResource().getInputStream();
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
if (pos < start)
|
|
||||||
{
|
|
||||||
in.skip(start - pos);
|
|
||||||
pos = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
IO.copy(in, multi, size);
|
|
||||||
pos += size;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// Handle cached resource
|
|
||||||
content.getResource().writeTo(multi, start, size);
|
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
if (in != null)
|
}
|
||||||
in.close();
|
|
||||||
multi.close();
|
multi.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ByteBuffer based RangeWriter
|
||||||
|
*/
|
||||||
|
public class ByteBufferRangeWriter implements RangeWriter
|
||||||
|
{
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
private boolean closed = false;
|
||||||
|
|
||||||
|
public ByteBufferRangeWriter(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
this.buffer = buffer.asReadOnlyBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||||
|
{
|
||||||
|
if (skipTo > Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Unsupported skipTo " + skipTo + " > " + Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Unsupported length " + skipTo + " > " + Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer src = buffer.slice();
|
||||||
|
src.position((int)skipTo);
|
||||||
|
src.limit(Math.addExact((int)skipTo, (int)length));
|
||||||
|
BufferUtil.writeTo(src, outputStream);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpContent;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range Writer selection for HttpContent
|
||||||
|
*/
|
||||||
|
public class HttpContentRangeWriter
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(HttpContentRangeWriter.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a new RangeWriter for the supplied HttpContent.
|
||||||
|
*
|
||||||
|
* @param content the HttpContent to base RangeWriter on
|
||||||
|
* @return the RangeWriter best suited for the supplied HttpContent
|
||||||
|
*/
|
||||||
|
public static RangeWriter newRangeWriter(HttpContent content)
|
||||||
|
{
|
||||||
|
Objects.requireNonNull(content, "HttpContent");
|
||||||
|
|
||||||
|
// Try direct buffer
|
||||||
|
ByteBuffer buffer = content.getDirectBuffer();
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
buffer = content.getIndirectBuffer();
|
||||||
|
}
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
return new ByteBufferRangeWriter(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ReadableByteChannel channel = content.getReadableByteChannel();
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel instanceof SeekableByteChannel)
|
||||||
|
{
|
||||||
|
SeekableByteChannel seekableByteChannel = (SeekableByteChannel)channel;
|
||||||
|
return new SeekableByteChannelRangeWriter(seekableByteChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Skipping non-SeekableByteChannel option " + channel + " from content " + content);
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Skipping ReadableByteChannel option", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InputStreamRangeWriter(() -> content.getInputStream());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Range Writer for InputStream
|
||||||
|
*/
|
||||||
|
public class InputStreamRangeWriter implements RangeWriter
|
||||||
|
{
|
||||||
|
|
||||||
|
public static final int NO_PROGRESS_LIMIT = 3;
|
||||||
|
|
||||||
|
public interface InputStreamSupplier
|
||||||
|
{
|
||||||
|
InputStream newInputStream() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final InputStreamSupplier inputStreamSupplier;
|
||||||
|
private boolean closed = false;
|
||||||
|
private InputStream inputStream;
|
||||||
|
private long pos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create InputStremRangeWriter
|
||||||
|
*
|
||||||
|
* @param inputStreamSupplier Supplier of the InputStream. If the stream needs to be regenerated, such as the next
|
||||||
|
* requested range being before the current position, then the current InputStream is closed and a new one obtained
|
||||||
|
* from this supplier.
|
||||||
|
*/
|
||||||
|
public InputStreamRangeWriter(InputStreamSupplier inputStreamSupplier)
|
||||||
|
{
|
||||||
|
this.inputStreamSupplier = inputStreamSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
if (inputStream != null)
|
||||||
|
{
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||||
|
{
|
||||||
|
if (closed)
|
||||||
|
{
|
||||||
|
throw new IOException("RangeWriter is closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
inputStream = inputStreamSupplier.newInputStream();
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipTo < pos)
|
||||||
|
{
|
||||||
|
inputStream.close();
|
||||||
|
inputStream = inputStreamSupplier.newInputStream();
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
if (pos < skipTo)
|
||||||
|
{
|
||||||
|
long skipSoFar = pos;
|
||||||
|
long actualSkipped;
|
||||||
|
int noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||||
|
// loop till we reach desired point, break out on lack of progress.
|
||||||
|
while (noProgressLoopLimit > 0 && skipSoFar < skipTo)
|
||||||
|
{
|
||||||
|
actualSkipped = inputStream.skip(skipTo - skipSoFar);
|
||||||
|
if (actualSkipped == 0)
|
||||||
|
{
|
||||||
|
noProgressLoopLimit--;
|
||||||
|
}
|
||||||
|
else if (actualSkipped > 0)
|
||||||
|
{
|
||||||
|
skipSoFar += actualSkipped;
|
||||||
|
noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// negative values means the stream was closed or reached EOF
|
||||||
|
// either way, we've hit a state where we can no longer
|
||||||
|
// fulfill the requested range write.
|
||||||
|
throw new IOException("EOF reached before InputStream skip destination");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noProgressLoopLimit <= 0)
|
||||||
|
{
|
||||||
|
throw new IOException("No progress made to reach InputStream skip position " + (skipTo - pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = skipTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
IO.copy(inputStream, outputStream, length);
|
||||||
|
pos += length;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for writing sections (ranges) of a single resource (SeekableByteChannel, Resource, etc) to an outputStream.
|
||||||
|
*/
|
||||||
|
public interface RangeWriter extends Closeable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Write the specific range (start, size) to the outputStream.
|
||||||
|
*
|
||||||
|
* @param outputStream the stream to write to
|
||||||
|
* @param skipTo the offset / skip-to / seek-to / position in the resource to start the write from
|
||||||
|
* @param length the size of the section to write
|
||||||
|
*/
|
||||||
|
void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
|
||||||
|
public class SeekableByteChannelRangeWriter implements RangeWriter
|
||||||
|
{
|
||||||
|
private final SeekableByteChannel channel;
|
||||||
|
private final int bufSize;
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
public SeekableByteChannelRangeWriter(SeekableByteChannel seekableByteChannel)
|
||||||
|
{
|
||||||
|
this.channel = seekableByteChannel;
|
||||||
|
this.bufSize = IO.bufferSize;
|
||||||
|
this.buffer = BufferUtil.allocate(this.bufSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
this.channel.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||||
|
{
|
||||||
|
this.channel.position(skipTo);
|
||||||
|
|
||||||
|
// copy from channel to output stream
|
||||||
|
long readTotal = 0;
|
||||||
|
while (readTotal < length)
|
||||||
|
{
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
int size = (int)Math.min(bufSize, length - readTotal);
|
||||||
|
buffer.limit(size);
|
||||||
|
int readLen = channel.read(buffer);
|
||||||
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
|
BufferUtil.writeTo(buffer, outputStream);
|
||||||
|
readTotal += readLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import java.sql.Driver;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.naming.InitialContext;
|
import javax.naming.InitialContext;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
@ -164,9 +165,17 @@ public class DatabaseAdaptor
|
||||||
return new ByteArrayInputStream(bytes);
|
return new ByteArrayInputStream(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
Blob blob = result.getBlob(columnName);
|
Blob blob = result.getBlob(columnName);
|
||||||
return blob.getBinaryStream();
|
return blob.getBinaryStream();
|
||||||
}
|
}
|
||||||
|
catch (SQLFeatureNotSupportedException ex)
|
||||||
|
{
|
||||||
|
byte[] bytes = result.getBytes(columnName);
|
||||||
|
return new ByteArrayInputStream(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEmptyStringNull()
|
public boolean isEmptyStringNull()
|
||||||
{
|
{
|
||||||
|
|
|
@ -639,6 +639,56 @@ public class RequestTest
|
||||||
assertThat(responses, startsWith("HTTP/1.1 200"));
|
assertThat(responses, startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContentLength_ExceedsMaxInteger() throws Exception
|
||||||
|
{
|
||||||
|
final long HUGE_LENGTH = (long) Integer.MAX_VALUE * 10L;
|
||||||
|
|
||||||
|
_handler._checker = (request, response) ->
|
||||||
|
request.getContentLength() == (-1) // per HttpServletRequest javadoc this must return (-1);
|
||||||
|
&& request.getContentLengthLong() == HUGE_LENGTH;
|
||||||
|
|
||||||
|
//Send a request with encoded form content
|
||||||
|
String request="POST / HTTP/1.1\r\n"+
|
||||||
|
"Host: whatever\r\n"+
|
||||||
|
"Content-Type: application/octet-stream\n"+
|
||||||
|
"Content-Length: " + HUGE_LENGTH + "\n"+
|
||||||
|
"Connection: close\n"+
|
||||||
|
"\n"+
|
||||||
|
"<insert huge amount of content here>\n";
|
||||||
|
|
||||||
|
System.out.println(request);
|
||||||
|
|
||||||
|
String responses=_connector.getResponse(request);
|
||||||
|
assertThat(responses,startsWith("HTTP/1.1 200"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Servlet spec and API cannot parse Content-Length that exceeds Long.MAX_VALUE
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testContentLength_ExceedsMaxLong() throws Exception
|
||||||
|
{
|
||||||
|
String HUGE_LENGTH = Long.MAX_VALUE + "0";
|
||||||
|
|
||||||
|
_handler._checker = (request, response) ->
|
||||||
|
request.getHeader("Content-Length").equals(HUGE_LENGTH)
|
||||||
|
&& request.getContentLength() == (-1) // per HttpServletRequest javadoc this must return (-1);
|
||||||
|
&& request.getContentLengthLong() == (-1); // exact behavior here not specified in Servlet javadoc
|
||||||
|
|
||||||
|
//Send a request with encoded form content
|
||||||
|
String request="POST / HTTP/1.1\r\n"+
|
||||||
|
"Host: whatever\r\n"+
|
||||||
|
"Content-Type: application/octet-stream\n"+
|
||||||
|
"Content-Length: " + HUGE_LENGTH + "\n"+
|
||||||
|
"Connection: close\n"+
|
||||||
|
"\n"+
|
||||||
|
"<insert huge amount of content here>\n";
|
||||||
|
|
||||||
|
String responses=_connector.getResponse(request);
|
||||||
|
assertThat(responses, startsWith("HTTP/1.1 400"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdentityParamExtraction() throws Exception
|
public void testIdentityParamExtraction() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,34 +18,106 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.CompressedContentFormat;
|
import org.eclipse.jetty.http.CompressedContentFormat;
|
||||||
import org.eclipse.jetty.http.HttpContent;
|
import org.eclipse.jetty.http.HttpContent;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.http.ResourceHttpContent;
|
import org.eclipse.jetty.http.ResourceHttpContent;
|
||||||
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.resource.PathResource;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(WorkDirExtension.class)
|
||||||
public class ResourceCacheTest
|
public class ResourceCacheTest
|
||||||
{
|
{
|
||||||
|
public WorkDir workDir;
|
||||||
|
|
||||||
|
public Path createUtilTestResources(Path basePath) throws IOException
|
||||||
|
{
|
||||||
|
// root
|
||||||
|
makeFile(basePath.resolve("resource.txt"), "this is test data");
|
||||||
|
|
||||||
|
// - one/
|
||||||
|
Path one = basePath.resolve("one");
|
||||||
|
FS.ensureDirExists(one);
|
||||||
|
makeFile(one.resolve("1.txt"), "1 - one");
|
||||||
|
|
||||||
|
// - one/dir/
|
||||||
|
Path oneDir = one.resolve("dir");
|
||||||
|
FS.ensureDirExists(oneDir);
|
||||||
|
makeFile(oneDir.resolve("1.txt"), "1 - one");
|
||||||
|
|
||||||
|
// - two/
|
||||||
|
Path two = basePath.resolve("two");
|
||||||
|
FS.ensureDirExists(two);
|
||||||
|
makeFile(two.resolve("1.txt"), "1 - two");
|
||||||
|
makeFile(two.resolve("2.txt"), "2 - two");
|
||||||
|
|
||||||
|
// - two/dir/
|
||||||
|
Path twoDir = two.resolve("dir");
|
||||||
|
FS.ensureDirExists(twoDir);
|
||||||
|
makeFile(twoDir.resolve("2.txt"), "2 - two");
|
||||||
|
|
||||||
|
// - three/
|
||||||
|
Path three = basePath.resolve("three");
|
||||||
|
FS.ensureDirExists(three);
|
||||||
|
makeFile(three.resolve("2.txt"), "2 - three");
|
||||||
|
makeFile(three.resolve("3.txt"), "3 - three");
|
||||||
|
|
||||||
|
// - three/dir/
|
||||||
|
Path threeDir = three.resolve("dir");
|
||||||
|
FS.ensureDirExists(threeDir);
|
||||||
|
makeFile(threeDir.resolve("3.txt"), "3 - three");
|
||||||
|
|
||||||
|
// - four/
|
||||||
|
Path four = basePath.resolve("four");
|
||||||
|
FS.ensureDirExists(four);
|
||||||
|
makeFile(four.resolve("four"), "4 - four (no extension)");
|
||||||
|
makeFile(four.resolve("four.txt"), "4 - four");
|
||||||
|
|
||||||
|
return basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeFile(Path file, String contents) throws IOException
|
||||||
|
{
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(file, UTF_8, StandardOpenOption.CREATE_NEW))
|
||||||
|
{
|
||||||
|
writer.write(contents);
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMutlipleSources1() throws Exception
|
public void testMutlipleSources1() throws Exception
|
||||||
{
|
{
|
||||||
ResourceCollection rc = new ResourceCollection(new String[]{
|
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/",
|
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
|
ResourceCollection rc = new ResourceCollection(
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
|
new PathResource(basePath.resolve("one")),
|
||||||
});
|
new PathResource(basePath.resolve("two")),
|
||||||
|
new PathResource(basePath.resolve("three")));
|
||||||
|
|
||||||
Resource[] r = rc.getResources();
|
Resource[] r = rc.getResources();
|
||||||
MimeTypes mime = new MimeTypes();
|
MimeTypes mime = new MimeTypes();
|
||||||
|
@ -70,11 +142,12 @@ public class ResourceCacheTest
|
||||||
@Test
|
@Test
|
||||||
public void testUncacheable() throws Exception
|
public void testUncacheable() throws Exception
|
||||||
{
|
{
|
||||||
ResourceCollection rc = new ResourceCollection(new String[]{
|
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/",
|
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
|
ResourceCollection rc = new ResourceCollection(
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
|
new PathResource(basePath.resolve("one")),
|
||||||
});
|
new PathResource(basePath.resolve("two")),
|
||||||
|
new PathResource(basePath.resolve("three")));
|
||||||
|
|
||||||
Resource[] r = rc.getResources();
|
Resource[] r = rc.getResources();
|
||||||
MimeTypes mime = new MimeTypes();
|
MimeTypes mime = new MimeTypes();
|
||||||
|
@ -112,19 +185,21 @@ public class ResourceCacheTest
|
||||||
String[] names = new String[files.length];
|
String[] names = new String[files.length];
|
||||||
CachedContentFactory cache;
|
CachedContentFactory cache;
|
||||||
|
|
||||||
|
Path basePath = workDir.getEmptyPathDir();
|
||||||
|
|
||||||
for (int i = 0; i < files.length; i++)
|
for (int i = 0; i < files.length; i++)
|
||||||
{
|
{
|
||||||
files[i] = File.createTempFile("R-" + i + "-", ".txt");
|
Path tmpFile = basePath.resolve("R-" + i + ".txt");
|
||||||
files[i].deleteOnExit();
|
try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, UTF_8, StandardOpenOption.CREATE_NEW))
|
||||||
names[i] = files[i].getName();
|
|
||||||
try (OutputStream out = new FileOutputStream(files[i]))
|
|
||||||
{
|
{
|
||||||
for (int j = 0; j < (i * 10 - 1); j++)
|
for (int j = 0; j < (i * 10 - 1); j++)
|
||||||
{
|
{
|
||||||
out.write(' ');
|
writer.write(' ');
|
||||||
}
|
}
|
||||||
out.write('\n');
|
writer.write('\n');
|
||||||
}
|
}
|
||||||
|
files[i] = tmpFile.toFile();
|
||||||
|
names[i] = tmpFile.getFileName().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
directory = Resource.newResource(files[0].getParentFile().getAbsolutePath());
|
directory = Resource.newResource(files[0].getParentFile().getAbsolutePath());
|
||||||
|
@ -141,7 +216,7 @@ public class ResourceCacheTest
|
||||||
|
|
||||||
HttpContent content;
|
HttpContent content;
|
||||||
content = cache.getContent(names[8], 4096);
|
content = cache.getContent(names[8], 4096);
|
||||||
assertTrue(content != null);
|
assertThat(content, is(not(nullValue())));
|
||||||
assertEquals(80, content.getContentLengthValue());
|
assertEquals(80, content.getContentLengthValue());
|
||||||
assertEquals(0, cache.getCachedSize());
|
assertEquals(0, cache.getCachedSize());
|
||||||
|
|
||||||
|
@ -274,36 +349,20 @@ public class ResourceCacheTest
|
||||||
@Test
|
@Test
|
||||||
public void testNoextension() throws Exception
|
public void testNoextension() throws Exception
|
||||||
{
|
{
|
||||||
ResourceCollection rc = new ResourceCollection(new String[]{
|
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
|
||||||
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/"
|
|
||||||
});
|
|
||||||
|
|
||||||
Resource[] resources = rc.getResources();
|
Resource resource = new PathResource(basePath.resolve("four"));
|
||||||
MimeTypes mime = new MimeTypes();
|
MimeTypes mime = new MimeTypes();
|
||||||
|
|
||||||
CachedContentFactory cache = new CachedContentFactory(null, resources[0], mime, false, false, CompressedContentFormat.NONE);
|
CachedContentFactory cache = new CachedContentFactory(null, resource, mime, false, false, CompressedContentFormat.NONE);
|
||||||
|
|
||||||
assertEquals(getContent(cache, "four.txt"), "4 - four");
|
assertEquals(getContent(cache, "four.txt"), "4 - four");
|
||||||
assertEquals(getContent(cache, "four"), "4 - four (no extension)");
|
assertEquals(getContent(cache, "four"), "4 - four (no extension)");
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getContent(Resource r, String path) throws Exception
|
|
||||||
{
|
|
||||||
StringBuilder buffer = new StringBuilder();
|
|
||||||
String line = null;
|
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream())))
|
|
||||||
{
|
|
||||||
while ((line = br.readLine()) != null)
|
|
||||||
{
|
|
||||||
buffer.append(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getContent(CachedContentFactory rc, String path) throws Exception
|
static String getContent(CachedContentFactory rc, String path) throws Exception
|
||||||
{
|
{
|
||||||
HttpContent content = rc.lookup(path);
|
HttpContent content = rc.getContent(path, rc.getMaxCachedFileSize());
|
||||||
if (content == null)
|
if (content == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.resource.PathResource;
|
||||||
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
public class RangeWriterTest
|
||||||
|
{
|
||||||
|
public static final String DATA = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ!@#$%^&*()_+/.,[]";
|
||||||
|
|
||||||
|
public static Path initDataFile() throws IOException
|
||||||
|
{
|
||||||
|
Path testDir = MavenTestingUtils.getTargetTestingPath(RangeWriterTest.class.getSimpleName());
|
||||||
|
FS.ensureEmpty(testDir);
|
||||||
|
|
||||||
|
Path dataFile = testDir.resolve("data.dat");
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(dataFile, UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))
|
||||||
|
{
|
||||||
|
writer.write(DATA);
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> impls() throws IOException
|
||||||
|
{
|
||||||
|
Resource resource = new PathResource(initDataFile());
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, true))),
|
||||||
|
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, false))),
|
||||||
|
Arguments.of(new SeekableByteChannelRangeWriter((SeekableByteChannel)resource.getReadableByteChannel())),
|
||||||
|
Arguments.of(new InputStreamRangeWriter(() -> resource.getInputStream()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("impls")
|
||||||
|
public void testSimpleRange(RangeWriter rangeWriter) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 50);
|
||||||
|
assertThat("Range: 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("impls")
|
||||||
|
public void testSameRange_MultipleTimes(RangeWriter rangeWriter) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 50);
|
||||||
|
assertThat("Range(a): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 50);
|
||||||
|
assertThat("Range(b): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("impls")
|
||||||
|
public void testMultipleRanges_Ordered(RangeWriter rangeWriter) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 20);
|
||||||
|
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 35, 10);
|
||||||
|
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 55, 10);
|
||||||
|
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("impls")
|
||||||
|
public void testMultipleRanges_Overlapping(RangeWriter rangeWriter) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 20);
|
||||||
|
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 15, 20);
|
||||||
|
assertThat("Range(b): 15 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(15, 15 + 20)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 20, 20);
|
||||||
|
assertThat("Range(b): 20 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(20, 20 + 20)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("impls")
|
||||||
|
public void testMultipleRanges_ReverseOrder(RangeWriter rangeWriter) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 55, 10);
|
||||||
|
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 35, 10);
|
||||||
|
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
|
||||||
|
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
rangeWriter.writeTo(outputStream, 10, 20);
|
||||||
|
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -971,11 +971,19 @@ public class BufferUtil
|
||||||
|
|
||||||
public static ByteBuffer toBuffer(Resource resource, boolean direct) throws IOException
|
public static ByteBuffer toBuffer(Resource resource, boolean direct) throws IOException
|
||||||
{
|
{
|
||||||
int len = (int)resource.length();
|
long len = resource.length();
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
throw new IllegalArgumentException("invalid resource: " + resource + " len=" + len);
|
throw new IllegalArgumentException("invalid resource: " + resource + " len=" + len);
|
||||||
|
|
||||||
ByteBuffer buffer = direct ? BufferUtil.allocateDirect(len) : BufferUtil.allocate(len);
|
if (len > Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
// This method cannot handle resources of this size.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ilen = (int)len;
|
||||||
|
|
||||||
|
ByteBuffer buffer = direct ? BufferUtil.allocateDirect(ilen) : BufferUtil.allocate(ilen);
|
||||||
|
|
||||||
int pos = BufferUtil.flipToFill(buffer);
|
int pos = BufferUtil.flipToFill(buffer);
|
||||||
if (resource.getFile() != null)
|
if (resource.getFile() != null)
|
||||||
|
@ -984,7 +992,7 @@ public class BufferUtil
|
||||||
{
|
{
|
||||||
try (InputStream is = resource.getInputStream())
|
try (InputStream is = resource.getInputStream())
|
||||||
{
|
{
|
||||||
BufferUtil.readFrom(is, len, buffer);
|
BufferUtil.readFrom(is, ilen, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BufferUtil.flipToFlush(buffer, pos);
|
BufferUtil.flipToFlush(buffer, pos);
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class Jetty
|
||||||
pkg.getImplementationVersion() != null)
|
pkg.getImplementationVersion() != null)
|
||||||
VERSION = pkg.getImplementationVersion();
|
VERSION = pkg.getImplementationVersion();
|
||||||
else
|
else
|
||||||
VERSION = System.getProperty("jetty.version", "9.4.z-SNAPSHOT");
|
VERSION = System.getProperty("jetty.version", __buildProperties.getProperty("version", "9.4.z-SNAPSHOT"));
|
||||||
|
|
||||||
POWERED_BY = "<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// " + VERSION + "</a>";
|
POWERED_BY = "<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// " + VERSION + "</a>";
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,19 @@
|
||||||
package org.eclipse.jetty.util.resource;
|
package org.eclipse.jetty.util.resource;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
import java.nio.file.DirectoryIteratorException;
|
import java.nio.file.DirectoryIteratorException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
|
@ -40,6 +42,7 @@ import java.nio.file.attribute.FileTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
@ -58,6 +61,7 @@ public class PathResource extends Resource
|
||||||
private final Path path;
|
private final Path path;
|
||||||
private final Path alias;
|
private final Path alias;
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
|
private final boolean belongsToDefaultFileSystem;
|
||||||
|
|
||||||
private final Path checkAliasPath()
|
private final Path checkAliasPath()
|
||||||
{
|
{
|
||||||
|
@ -196,6 +200,7 @@ public class PathResource extends Resource
|
||||||
assertValidPath(path);
|
assertValidPath(path);
|
||||||
this.uri = this.path.toUri();
|
this.uri = this.path.toUri();
|
||||||
this.alias = checkAliasPath();
|
this.alias = checkAliasPath();
|
||||||
|
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,6 +221,7 @@ public class PathResource extends Resource
|
||||||
childPath += "/";
|
childPath += "/";
|
||||||
this.uri = URIUtil.addPath(parent.uri, childPath);
|
this.uri = URIUtil.addPath(parent.uri, childPath);
|
||||||
this.alias = checkAliasPath();
|
this.alias = checkAliasPath();
|
||||||
|
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -256,6 +262,7 @@ public class PathResource extends Resource
|
||||||
this.path = path.toAbsolutePath();
|
this.path = path.toAbsolutePath();
|
||||||
this.uri = path.toUri();
|
this.uri = path.toUri();
|
||||||
this.alias = checkAliasPath();
|
this.alias = checkAliasPath();
|
||||||
|
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -365,6 +372,8 @@ public class PathResource extends Resource
|
||||||
@Override
|
@Override
|
||||||
public File getFile() throws IOException
|
public File getFile() throws IOException
|
||||||
{
|
{
|
||||||
|
if (!belongsToDefaultFileSystem)
|
||||||
|
return null;
|
||||||
return path.toFile();
|
return path.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,9 +388,8 @@ public class PathResource extends Resource
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream() throws IOException
|
public InputStream getInputStream() throws IOException
|
||||||
{
|
{
|
||||||
// Use a FileInputStream rather than Files.newInputStream(path)
|
// TODO: investigate if SPARSE use for default FileSystem usages is worth it
|
||||||
// since it produces a stream with a fast skip implementation
|
return Files.newInputStream(path, StandardOpenOption.READ);
|
||||||
return new FileInputStream(getFile());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -393,7 +401,8 @@ public class PathResource extends Resource
|
||||||
@Override
|
@Override
|
||||||
public ReadableByteChannel getReadableByteChannel() throws IOException
|
public ReadableByteChannel getReadableByteChannel() throws IOException
|
||||||
{
|
{
|
||||||
return FileChannel.open(path, StandardOpenOption.READ);
|
// TODO: investigate if SPARSE use for default FileSystem usages is worth it
|
||||||
|
return Files.newByteChannel(path, StandardOpenOption.READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -559,6 +568,43 @@ public class PathResource extends Resource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param outputStream the output stream to write to
|
||||||
|
* @param start First byte to write
|
||||||
|
* @param count Bytes to write or -1 for all of them.
|
||||||
|
* @throws IOException if unable to copy the Resource to the output
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outputStream, long start, long count)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
long length = count;
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
length = Files.size(path) - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ))
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(IO.bufferSize);
|
||||||
|
channel.position(start);
|
||||||
|
|
||||||
|
// copy from channel to output stream
|
||||||
|
long readTotal = 0;
|
||||||
|
while (readTotal < length)
|
||||||
|
{
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
int size = (int)Math.min(IO.bufferSize, length - readTotal);
|
||||||
|
buffer.limit(size);
|
||||||
|
int readLen = channel.read(buffer);
|
||||||
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
|
BufferUtil.writeTo(buffer, outputStream);
|
||||||
|
readTotal += readLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -74,6 +75,7 @@ import javax.net.ssl.StandardConstants;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
@ -98,21 +100,41 @@ import org.eclipse.jetty.util.security.Password;
|
||||||
public class SslContextFactory extends AbstractLifeCycle implements Dumpable
|
public class SslContextFactory extends AbstractLifeCycle implements Dumpable
|
||||||
{
|
{
|
||||||
public static final TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{
|
public static final TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{
|
||||||
new X509TrustManager()
|
new X509ExtendedTrustManager()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public java.security.cert.X509Certificate[] getAcceptedIssuers()
|
public X509Certificate[] getAcceptedIssuers()
|
||||||
{
|
{
|
||||||
return new java.security.cert.X509Certificate[]{};
|
return new X509Certificate[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
|
public void checkClientTrusted(X509Certificate[] certs, String authType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
|
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] certs, String authType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.resource;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
|
public class PathResourceTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testNonDefaultFileSystem_GetInputStream() throws URISyntaxException, IOException
|
||||||
|
{
|
||||||
|
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
|
||||||
|
|
||||||
|
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
|
||||||
|
System.err.println("URI = " + uri);
|
||||||
|
|
||||||
|
Map<String, Object> env = new HashMap<>();
|
||||||
|
env.put("multi-release", "runtime");
|
||||||
|
|
||||||
|
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||||
|
{
|
||||||
|
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
|
||||||
|
assertThat(manifestPath, is(not(nullValue())));
|
||||||
|
|
||||||
|
PathResource resource = new PathResource(manifestPath);
|
||||||
|
|
||||||
|
try (InputStream inputStream = resource.getInputStream())
|
||||||
|
{
|
||||||
|
assertThat("InputStream", inputStream, is(not(nullValue())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonDefaultFileSystem_GetReadableByteChannel() throws URISyntaxException, IOException
|
||||||
|
{
|
||||||
|
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
|
||||||
|
|
||||||
|
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
|
||||||
|
System.err.println("URI = " + uri);
|
||||||
|
|
||||||
|
Map<String, Object> env = new HashMap<>();
|
||||||
|
env.put("multi-release", "runtime");
|
||||||
|
|
||||||
|
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||||
|
{
|
||||||
|
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
|
||||||
|
assertThat(manifestPath, is(not(nullValue())));
|
||||||
|
|
||||||
|
PathResource resource = new PathResource(manifestPath);
|
||||||
|
|
||||||
|
try (ReadableByteChannel channel = resource.getReadableByteChannel())
|
||||||
|
{
|
||||||
|
assertThat("ReadableByteChannel", channel, is(not(nullValue())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonDefaultFileSystem_GetFile() throws URISyntaxException, IOException
|
||||||
|
{
|
||||||
|
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
|
||||||
|
|
||||||
|
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
|
||||||
|
System.err.println("URI = " + uri);
|
||||||
|
|
||||||
|
Map<String, Object> env = new HashMap<>();
|
||||||
|
env.put("multi-release", "runtime");
|
||||||
|
|
||||||
|
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||||
|
{
|
||||||
|
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
|
||||||
|
assertThat(manifestPath, is(not(nullValue())));
|
||||||
|
|
||||||
|
PathResource resource = new PathResource(manifestPath);
|
||||||
|
File file = resource.getFile();
|
||||||
|
assertThat("File should be null for non-default FileSystem", file, is(nullValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultFileSystem_GetFile() throws Exception
|
||||||
|
{
|
||||||
|
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
|
||||||
|
PathResource resource = new PathResource(exampleJar);
|
||||||
|
|
||||||
|
File file = resource.getFile();
|
||||||
|
assertThat("File for default FileSystem", file, is(exampleJar.toFile()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,11 +52,6 @@
|
||||||
<artifactId>jetty-xml</artifactId>
|
<artifactId>jetty-xml</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-servlet</artifactId>
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
@ -68,6 +63,17 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
|
|
|
@ -0,0 +1,339 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2019 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.webapp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.servlet.MultipartConfigElement;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
||||||
|
import org.eclipse.jetty.client.util.MultiPartContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.PathContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerList;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.eclipse.jetty.util.resource.PathResource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
public class HugeResourceTest
|
||||||
|
{
|
||||||
|
private static final long KB = 1024;
|
||||||
|
private static final long MB = 1024 * KB;
|
||||||
|
private static final long GB = 1024 * MB;
|
||||||
|
public static Path staticBase;
|
||||||
|
public static Path outputDir;
|
||||||
|
public static Path multipartTempDir;
|
||||||
|
|
||||||
|
public Server server;
|
||||||
|
public HttpClient client;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void prepareStaticFiles() throws IOException
|
||||||
|
{
|
||||||
|
staticBase = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-static-base");
|
||||||
|
FS.ensureDirExists(staticBase);
|
||||||
|
|
||||||
|
makeStaticFile(staticBase.resolve("test-1g.dat"), 1 * GB);
|
||||||
|
makeStaticFile(staticBase.resolve("test-4g.dat"), 4 * GB);
|
||||||
|
makeStaticFile(staticBase.resolve("test-10g.dat"), 10 * GB);
|
||||||
|
|
||||||
|
outputDir = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-outputdir");
|
||||||
|
FS.ensureEmpty(outputDir);
|
||||||
|
|
||||||
|
multipartTempDir = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-multipart-tmp");
|
||||||
|
FS.ensureEmpty(multipartTempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> staticFiles()
|
||||||
|
{
|
||||||
|
ArrayList<Arguments> ret = new ArrayList<>();
|
||||||
|
|
||||||
|
ret.add(Arguments.of("test-1g.dat", 1 * GB));
|
||||||
|
ret.add(Arguments.of("test-4g.dat", 4 * GB));
|
||||||
|
ret.add(Arguments.of("test-10g.dat", 10 * GB));
|
||||||
|
|
||||||
|
return ret.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void cleanupStaticFiles()
|
||||||
|
{
|
||||||
|
FS.ensureDeleted(staticBase);
|
||||||
|
FS.ensureDeleted(outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void makeStaticFile(Path staticFile, long size) throws IOException
|
||||||
|
{
|
||||||
|
byte[] buf = new byte[(int)(1 * MB)];
|
||||||
|
Arrays.fill(buf, (byte)'x');
|
||||||
|
ByteBuffer src = ByteBuffer.wrap(buf);
|
||||||
|
|
||||||
|
if (Files.exists(staticFile) && Files.size(staticFile) == size)
|
||||||
|
{
|
||||||
|
// all done, nothing left to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.printf("Creating %,d byte file: %s ...%n", size, staticFile.getFileName());
|
||||||
|
try (SeekableByteChannel channel = Files.newByteChannel(staticFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING))
|
||||||
|
{
|
||||||
|
long remaining = size;
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
ByteBuffer slice = src.slice();
|
||||||
|
int len = buf.length;
|
||||||
|
if (remaining < Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
len = Math.min(buf.length, (int)remaining);
|
||||||
|
slice.limit(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.write(slice);
|
||||||
|
remaining -= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.err.println(" Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void startServer() throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
ServerConnector connector = new ServerConnector(server);
|
||||||
|
connector.setPort(0);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
WebAppContext context = new WebAppContext();
|
||||||
|
context.setContextPath("/");
|
||||||
|
context.setBaseResource(new PathResource(staticBase));
|
||||||
|
|
||||||
|
context.addServlet(PostServlet.class, "/post");
|
||||||
|
|
||||||
|
String location = multipartTempDir.toString();
|
||||||
|
long maxFileSize = Long.MAX_VALUE;
|
||||||
|
long maxRequestSize = Long.MAX_VALUE;
|
||||||
|
int fileSizeThreshold = (int)(2 * MB);
|
||||||
|
|
||||||
|
MultipartConfigElement multipartConfig = new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold);
|
||||||
|
ServletHolder holder = context.addServlet(MultipartServlet.class, "/multipart");
|
||||||
|
holder.getRegistration().setMultipartConfig(multipartConfig);
|
||||||
|
|
||||||
|
HandlerList handlers = new HandlerList();
|
||||||
|
handlers.addHandler(context);
|
||||||
|
handlers.addHandler(new DefaultHandler());
|
||||||
|
|
||||||
|
server.setHandler(handlers);
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void stopServer() throws Exception
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void startClient() throws Exception
|
||||||
|
{
|
||||||
|
client = new HttpClient();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void stopClient() throws Exception
|
||||||
|
{
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("staticFiles")
|
||||||
|
public void testDownload(String filename, long expectedSize) throws Exception
|
||||||
|
{
|
||||||
|
URI destUri = server.getURI().resolve("/" + filename);
|
||||||
|
InputStreamResponseListener responseListener = new InputStreamResponseListener();
|
||||||
|
|
||||||
|
Request request = client.newRequest(destUri)
|
||||||
|
.method(HttpMethod.GET);
|
||||||
|
request.send(responseListener);
|
||||||
|
Response response = responseListener.get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
assertThat("HTTP Response Code", response.getStatus(), is(200));
|
||||||
|
dumpResponse(response);
|
||||||
|
|
||||||
|
String contentLength = response.getHeaders().get(HttpHeader.CONTENT_LENGTH);
|
||||||
|
long contentLengthLong = Long.parseLong(contentLength);
|
||||||
|
assertThat("Http Response Header: \"Content-Length: " + contentLength + "\"", contentLengthLong, is(expectedSize));
|
||||||
|
|
||||||
|
Path outputFile = outputDir.resolve(filename);
|
||||||
|
try (OutputStream out = Files.newOutputStream(outputFile);
|
||||||
|
InputStream in = responseListener.getInputStream())
|
||||||
|
{
|
||||||
|
IO.copy(in, out);
|
||||||
|
}
|
||||||
|
assertThat("Downloaded Files Size: " + filename, Files.size(outputFile), is(expectedSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("staticFiles")
|
||||||
|
public void testUpload(String filename, long expectedSize) throws Exception
|
||||||
|
{
|
||||||
|
Path inputFile = staticBase.resolve(filename);
|
||||||
|
|
||||||
|
PathContentProvider pathContentProvider = new PathContentProvider(inputFile);
|
||||||
|
URI destUri = server.getURI().resolve("/post");
|
||||||
|
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(pathContentProvider);
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
assertThat("HTTP Response Code", response.getStatus(), is(200));
|
||||||
|
dumpResponse(response);
|
||||||
|
|
||||||
|
String responseBody = response.getContentAsString();
|
||||||
|
assertThat("Response", responseBody, containsString("bytes-received=" + expectedSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("staticFiles")
|
||||||
|
public void testUpload_Multipart(String filename, long expectedSize) throws Exception
|
||||||
|
{
|
||||||
|
MultiPartContentProvider multipart = new MultiPartContentProvider();
|
||||||
|
Path inputFile = staticBase.resolve(filename);
|
||||||
|
String name = String.format("file-%d", expectedSize);
|
||||||
|
multipart.addFilePart(name, filename, new PathContentProvider(inputFile), null);
|
||||||
|
|
||||||
|
URI destUri = server.getURI().resolve("/multipart");
|
||||||
|
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(multipart);
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
assertThat("HTTP Response Code", response.getStatus(), is(200));
|
||||||
|
dumpResponse(response);
|
||||||
|
|
||||||
|
String responseBody = response.getContentAsString();
|
||||||
|
String expectedResponse = String.format("part[%s].size=%d", name, expectedSize);
|
||||||
|
assertThat("Response", responseBody, containsString(expectedResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpResponse(Response response)
|
||||||
|
{
|
||||||
|
System.out.printf(" %s %d %s%n", response.getVersion(), response.getStatus(), response.getReason());
|
||||||
|
response.getHeaders().forEach((field) -> System.out.printf(" %s%n", field));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ByteCountingOutputStream extends OutputStream
|
||||||
|
{
|
||||||
|
private long count = 0;
|
||||||
|
|
||||||
|
public long getCount()
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b)
|
||||||
|
{
|
||||||
|
count += b.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len)
|
||||||
|
{
|
||||||
|
count += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PostServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
ByteCountingOutputStream byteCounting = new ByteCountingOutputStream();
|
||||||
|
IO.copy(req.getInputStream(), byteCounting);
|
||||||
|
resp.setContentType("text/plain");
|
||||||
|
resp.setCharacterEncoding("utf-8");
|
||||||
|
resp.getWriter().printf("bytes-received=%d%n", byteCounting.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultipartServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
resp.setContentType("text/plain");
|
||||||
|
resp.setCharacterEncoding("utf-8");
|
||||||
|
PrintWriter out = resp.getWriter();
|
||||||
|
|
||||||
|
req.getParts().forEach((part) ->
|
||||||
|
{
|
||||||
|
out.printf("part[%s].filename=%s%n", part.getName(), part.getSubmittedFileName());
|
||||||
|
out.printf("part[%s].size=%d%n", part.getName(), part.getSize());
|
||||||
|
try (InputStream inputStream = part.getInputStream();
|
||||||
|
ByteCountingOutputStream byteCounting = new ByteCountingOutputStream())
|
||||||
|
{
|
||||||
|
IO.copy(inputStream, byteCounting);
|
||||||
|
out.printf("part[%s].inputStream.length=%d%n", part.getName(), byteCounting.getCount());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace(out);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
pom.xml
26
pom.xml
|
@ -33,7 +33,7 @@
|
||||||
<jmhjar.name>benchmarks</jmhjar.name>
|
<jmhjar.name>benchmarks</jmhjar.name>
|
||||||
<tycho-version>1.2.0</tycho-version>
|
<tycho-version>1.2.0</tycho-version>
|
||||||
<cbi-plugins.version>1.1.5</cbi-plugins.version>
|
<cbi-plugins.version>1.1.5</cbi-plugins.version>
|
||||||
<junit.version>5.4.0</junit.version>
|
<junit.version>5.5.1</junit.version>
|
||||||
<maven.version>3.6.0</maven.version>
|
<maven.version>3.6.0</maven.version>
|
||||||
<maven.resolver.version>1.3.1</maven.resolver.version>
|
<maven.resolver.version>1.3.1</maven.resolver.version>
|
||||||
<javax.servlet.api.version>3.1.0</javax.servlet.api.version>
|
<javax.servlet.api.version>3.1.0</javax.servlet.api.version>
|
||||||
|
@ -52,6 +52,8 @@
|
||||||
<maven.source.plugin.version>3.0.1</maven.source.plugin.version>
|
<maven.source.plugin.version>3.0.1</maven.source.plugin.version>
|
||||||
<maven.war.plugin.version>3.2.2</maven.war.plugin.version>
|
<maven.war.plugin.version>3.2.2</maven.war.plugin.version>
|
||||||
<maven.plugin-tools.version>3.5.2</maven.plugin-tools.version>
|
<maven.plugin-tools.version>3.5.2</maven.plugin-tools.version>
|
||||||
|
<maven.install.plugin.version>2.5.2</maven.install.plugin.version>
|
||||||
|
<maven.deploy.plugin.version>2.8.2</maven.deploy.plugin.version>
|
||||||
|
|
||||||
<!-- testing -->
|
<!-- testing -->
|
||||||
<it.debug>false</it.debug>
|
<it.debug>false</it.debug>
|
||||||
|
@ -59,6 +61,7 @@
|
||||||
<spring-boot.version>2.1.1.RELEASE</spring-boot.version>
|
<spring-boot.version>2.1.1.RELEASE</spring-boot.version>
|
||||||
<jsr250-api.version>1.0</jsr250-api.version>
|
<jsr250-api.version>1.0</jsr250-api.version>
|
||||||
<javax.annotation-api>1.3</javax.annotation-api>
|
<javax.annotation-api>1.3</javax.annotation-api>
|
||||||
|
<localRepoPath>${settings.localRepository}</localRepoPath>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
|
@ -490,7 +493,7 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
<version>2.8.2</version>
|
<version>${maven.deploy.plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<retryFailedDeploymentCount>10</retryFailedDeploymentCount>
|
<retryFailedDeploymentCount>10</retryFailedDeploymentCount>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -525,18 +528,21 @@
|
||||||
<projectsDirectory>src/it</projectsDirectory>
|
<projectsDirectory>src/it</projectsDirectory>
|
||||||
<timeoutInSeconds>300</timeoutInSeconds>
|
<timeoutInSeconds>300</timeoutInSeconds>
|
||||||
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
|
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
|
||||||
<localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
|
<localRepositoryPath>${localRepoPath}</localRepositoryPath>
|
||||||
<settingsFile>src/it/settings.xml</settingsFile>
|
<!--settingsFile>src/it/settings.xml</settingsFile-->
|
||||||
<skipInvocation>${skipTests}</skipInvocation>
|
<skipInvocation>${skipTests}</skipInvocation>
|
||||||
<pomIncludes>
|
<pomIncludes>
|
||||||
<pomInclude>*/pom.xml</pomInclude>
|
<pomInclude>*/pom.xml</pomInclude>
|
||||||
</pomIncludes>
|
</pomIncludes>
|
||||||
|
<filterProperties>
|
||||||
|
<localRepo>${localRepoPath}</localRepo>
|
||||||
|
</filterProperties>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-install-plugin</artifactId>
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
<version>2.5.2</version>
|
<version>${maven.install.plugin.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
@ -924,6 +930,11 @@
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -1040,6 +1051,11 @@
|
||||||
<version>${maven.plugin-tools.version}</version>
|
<version>${maven.plugin-tools.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- Old Deps -->
|
<!-- Old Deps -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
|
|
@ -92,6 +92,10 @@
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-unixsocket</artifactId>
|
<artifactId>jetty-unixsocket</artifactId>
|
||||||
|
|
|
@ -34,5 +34,10 @@
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
Loading…
Reference in New Issue