Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-10295-formAuthDispatch
This commit is contained in:
commit
a418e0db71
|
@ -10,6 +10,9 @@ labels: Bug
|
|||
**Jetty version(s)**
|
||||
<!--[Jetty 9.x is now at End of Community Support](https://github.com/eclipse/jetty.project/issues/7958) -->
|
||||
|
||||
**Jetty Environment**
|
||||
<!-- Applicable for jetty-12 only, choose: core, ee8, ee9, ee10 -->
|
||||
|
||||
**Java version/vendor** `(use: java -version)`
|
||||
|
||||
**OS type/version**
|
||||
|
|
|
@ -7,9 +7,12 @@ assignees: ''
|
|||
|
||||
---
|
||||
|
||||
**Jetty version**
|
||||
**Jetty Version**
|
||||
|
||||
**Java version**
|
||||
**Jetty Environment**
|
||||
<!-- Applicable only for jetty-12, choose: core, ee8, ee9, ee10 -->
|
||||
|
||||
**Java Version**
|
||||
|
||||
**Question**
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ This release process will produce releases:
|
|||
- [ ] Promote staged releases.
|
||||
- [ ] Merge release branches back to main branches and delete release branches.
|
||||
- [ ] Verify release existence in Maven Central by triggering the Jenkins builds of CometD.
|
||||
- [ ] Update Jetty versions on the web sites.
|
||||
- [ ] Update Jetty versions on the website ( follow instructions in [jetty-website](https://github.com/eclipse/jetty-website/blob/master/README.md) ).
|
||||
+ [ ] Update (or check) [Download](https://eclipse.dev/jetty/download.php) page is updated.
|
||||
+ [ ] Update (or check) documentation page(s) are updated.
|
||||
- [ ] Publish GitHub Releases in the order of oldest (eg: 9) to newest (eg: 11) (to ensure that "latest" in github is truly the latest)
|
||||
|
|
|
@ -18,8 +18,14 @@ pipeline {
|
|||
}
|
||||
steps {
|
||||
timeout( time: 120, unit: 'MINUTES' ) {
|
||||
mavenBuild( "jdk11", "-T3 clean install -Djacoco.skip=true -Pautobahn", "maven3", true ) //
|
||||
mavenBuild( "jdk11", "-T3 clean install -Djacoco.skip=true -pl :test-websocket-autobahn -am -Pautobahn -Dtest=AutobahnTests", "maven3" ) //
|
||||
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml'
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/core/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Core Server', reportTitles: ''])
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/core/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Core Client', reportTitles: ''])
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/javax/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Javax Server', reportTitles: ''])
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/javax/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Javax Client', reportTitles: ''])
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/jetty/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Jetty Server', reportTitles: ''])
|
||||
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/jetty/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Jetty Client', reportTitles: ''])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,8 +97,52 @@
|
|||
[[pg-migration-11-to-12-servlet-to-handler]]
|
||||
==== Migrate Servlets to Jetty Handlers
|
||||
|
||||
Web applications written using the Servlet APIs may be re-written using the Jetty `Handler` APIs.
|
||||
The sections below outline the Jetty `Handler` APIs that correspond to the Servlet APIs.
|
||||
|
||||
To replace the functionalities of Servlet Filters, refer to xref:pg-server-http-handler[this section].
|
||||
|
||||
===== Handler Request APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=request]
|
||||
----
|
||||
|
||||
===== Handler Request Content APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-string]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-buffer]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-stream]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-source]
|
||||
----
|
||||
|
||||
===== Handler Response APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=response]
|
||||
----
|
||||
|
||||
===== Handler Response Content APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-implicit]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-implicit-status]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-explicit]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-content]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-string]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-echo]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-trailers]
|
||||
----
|
||||
|
||||
[[pg-migration-11-to-12-api-changes]]
|
||||
==== APIs Changes
|
||||
|
|
|
@ -325,20 +325,28 @@ Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not used.
|
|||
|
||||
`ServletContextHandler` is a `ContextHandler` that provides support for the Servlet APIs and implements the behaviors required by the Servlet specification.
|
||||
|
||||
The Maven artifact coordinates are:
|
||||
The Maven artifact coordinates depend on the version of Jakarta EE you want to use, and they are:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<groupId>org.eclipse.jetty.{ee-all}</groupId>
|
||||
<artifactId>jetty-{ee-all}-servlet</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
For example, for Jakarta {ee-current-caps} the coordinates are: `org.eclipse.jetty.ee10:jetty-ee10-servlet:{version}`.
|
||||
|
||||
Below you can find an example of how to setup a Jakarta {ee-current-caps} `ServletContextHandler`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=servletContextHandler]
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=servletContextHandler-servlet]
|
||||
----
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=servletContextHandler-setup]
|
||||
----
|
||||
|
||||
The `Handler` and Servlet components tree structure looks like the following:
|
||||
|
@ -368,6 +376,17 @@ Server applications must be careful when creating the `Handler` tree to put ``Se
|
|||
|
||||
`WebAppContext` is a `ServletContextHandler` that auto configures itself by reading a `web.xml` Servlet configuration file.
|
||||
|
||||
The Maven artifact coordinates depend on the version of Jakarta EE you want to use, and they are:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.{ee-all}</groupId>
|
||||
<artifactId>jetty-{ee-all}-webapp</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
Server applications can specify a `+*.war+` file or a directory with the structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet web application packaged as a `war` (as defined by the Servlet specification).
|
||||
|
||||
Where server applications using `ServletContextHandler` must manually invoke methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads `WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s, and also enforces a number of restrictions defined by the Servlet specification, in particular related to class loading.
|
||||
|
@ -412,6 +431,19 @@ However, Jetty picks good defaults and allows server applications to customize _
|
|||
If you have a xref:pg-server-http-handler-use-servlet-context[Servlet web application], you may want to use a `DefaultServlet` instead of `ResourceHandler`.
|
||||
The features are similar, but `DefaultServlet` is more commonly used to serve static files for Servlet web applications.
|
||||
|
||||
The Maven artifact coordinates depend on the version of Jakarta EE you want to use, and they are:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.{ee-all}</groupId>
|
||||
<artifactId>jetty-{ee-all}-servlet</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
Below you can find an example of how to setup `DefaultServlet`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=defaultServlet]
|
||||
|
|
|
@ -0,0 +1,643 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.docs.programming.migration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.Trailers;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.CompletableTask;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ServletToHandlerDocs
|
||||
{
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::request[]
|
||||
public class RequestAPIs extends Handler.Abstract
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Gets the request method.
|
||||
// Replaces:
|
||||
// - servletRequest.getMethod();
|
||||
String method = request.getMethod();
|
||||
|
||||
// Gets the request protocol name and version.
|
||||
// Replaces:
|
||||
// - servletRequest.getProtocol();
|
||||
String protocol = request.getConnectionMetaData().getProtocol();
|
||||
|
||||
// Gets the full request URI.
|
||||
// Replaces:
|
||||
// - servletRequest.getRequestURL();
|
||||
String fullRequestURI = request.getHttpURI().asString();
|
||||
|
||||
// Gets the request context.
|
||||
// Replaces:
|
||||
// - servletRequest.getServletContext()
|
||||
Context context = request.getContext();
|
||||
|
||||
// Gets the context path.
|
||||
// Replaces:
|
||||
// - servletRequest.getContextPath()
|
||||
String contextPath = context.getContextPath();
|
||||
|
||||
// Gets the request path.
|
||||
// Replaces:
|
||||
// - servletRequest.getRequestURI();
|
||||
String requestPath = request.getHttpURI().getPath();
|
||||
|
||||
// Gets the request path after the context path.
|
||||
// Replaces:
|
||||
// - servletRequest.getServletPath() + servletRequest.getPathInfo()
|
||||
String pathInContext = Request.getPathInContext(request);
|
||||
|
||||
// Gets the request query.
|
||||
// Replaces:
|
||||
// - servletRequest.getQueryString()
|
||||
String queryString = request.getHttpURI().getQuery();
|
||||
|
||||
// Gets request parameters.
|
||||
// Replaces:
|
||||
// - servletRequest.getParameterNames();
|
||||
// - servletRequest.getParameter(name);
|
||||
// - servletRequest.getParameterValues(name);
|
||||
// - servletRequest.getParameterMap();
|
||||
Fields queryParameters = Request.extractQueryParameters(request, UTF_8);
|
||||
Fields allParameters = Request.getParameters(request);
|
||||
|
||||
// Gets cookies.
|
||||
// Replaces:
|
||||
// - servletRequest.getCookies();
|
||||
List<HttpCookie> cookies = Request.getCookies(request);
|
||||
|
||||
// Gets request HTTP headers.
|
||||
// Replaces:
|
||||
// - servletRequest.getHeaderNames()
|
||||
// - servletRequest.getHeader(name)
|
||||
// - servletRequest.getHeaders(name)
|
||||
// - servletRequest.getDateHeader(name)
|
||||
// - servletRequest.getIntHeader(name)
|
||||
HttpFields requestHeaders = request.getHeaders();
|
||||
|
||||
// Gets the request Content-Type.
|
||||
// Replaces:
|
||||
// - servletRequest.getContentType()
|
||||
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
|
||||
|
||||
// Gets the request Content-Length.
|
||||
// Replaces:
|
||||
// - servletRequest.getContentLength()
|
||||
// - servletRequest.getContentLengthLong()
|
||||
long contentLength = request.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH);
|
||||
|
||||
// Gets the request locales.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocale()
|
||||
// - servletRequest.getLocales()
|
||||
List<Locale> locales = Request.getLocales(request);
|
||||
|
||||
// Gets the request scheme.
|
||||
// Replaces:
|
||||
// - servletRequest.getScheme()
|
||||
String scheme = request.getHttpURI().getScheme();
|
||||
|
||||
// Gets the server name.
|
||||
// Replaces:
|
||||
// - servletRequest.getServerName()
|
||||
String serverName = Request.getServerName(request);
|
||||
|
||||
// Gets the server port.
|
||||
// Replaces:
|
||||
// - servletRequest.getServerPort()
|
||||
int serverPort = Request.getServerPort(request);
|
||||
|
||||
// Gets the remote host/address.
|
||||
// Replaces:
|
||||
// - servletRequest.getRemoteAddr()
|
||||
// - servletRequest.getRemoteHost()
|
||||
String remoteAddress = Request.getRemoteAddr(request);
|
||||
|
||||
// Gets the remote port.
|
||||
// Replaces:
|
||||
// - servletRequest.getRemotePort()
|
||||
int remotePort = Request.getRemotePort(request);
|
||||
|
||||
// Gets the local host/address.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocalAddr()
|
||||
// - servletRequest.getLocalHost()
|
||||
String localAddress = Request.getLocalAddr(request);
|
||||
|
||||
// Gets the local port.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocalPort()
|
||||
int localPort = Request.getLocalPort(request);
|
||||
|
||||
// Gets the request attributes.
|
||||
// Replaces:
|
||||
// - servletRequest.getAttributeNames()
|
||||
// - servletRequest.getAttribute(name)
|
||||
// - servletRequest.setAttribute(name, value)
|
||||
// - servletRequest.removeAttribute(name)
|
||||
String name = "name";
|
||||
Object value = "value";
|
||||
Set<String> names = request.getAttributeNameSet();
|
||||
Object attribute = request.getAttribute(name);
|
||||
request.setAttribute(name, value);
|
||||
request.removeAttribute(name);
|
||||
request.clearAttributes();
|
||||
Map<String, Object> map = request.asAttributeMap();
|
||||
|
||||
// Gets the request trailers.
|
||||
// Replaces:
|
||||
// - servletRequest.getTrailerFields()
|
||||
HttpFields trailers = request.getTrailers();
|
||||
|
||||
// Gets the HTTP session.
|
||||
// Replaces:
|
||||
// - servletRequest.getSession()
|
||||
// - servletRequest.getSession(create)
|
||||
boolean create = true;
|
||||
Session session = request.getSession(create);
|
||||
|
||||
callback.succeeded();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// end::request[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsString extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-string[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Non-blocking read the request content as a String.
|
||||
// Use with caution as the request content may be large.
|
||||
Promise.Completable<String> completable = new Promise.Completable<>();
|
||||
Content.Source.asString(request, UTF_8, completable);
|
||||
|
||||
completable.whenComplete((requestContent, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Process the request content here.
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-string[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsByteBuffer extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-buffer[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Non-blocking read the request content as a ByteBuffer.
|
||||
// Use with caution as the request content may be large.
|
||||
Promise.Completable<ByteBuffer> completable = new Promise.Completable<>();
|
||||
Content.Source.asByteBuffer(request, completable);
|
||||
|
||||
completable.whenComplete((requestContent, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Process the request content here.
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-buffer[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsInputStream extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-stream[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Read the request content as an InputStream.
|
||||
// Note that InputStream.read() may block.
|
||||
try (InputStream inputStream = Content.Source.asInputStream(request))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int read = inputStream.read();
|
||||
|
||||
// EOF was reached, stop reading.
|
||||
if (read < 0)
|
||||
break;
|
||||
|
||||
// Process the read byte here.
|
||||
}
|
||||
}
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-stream[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsSource extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-source[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
CompletableTask<Void> reader = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Read in a loop.
|
||||
while (true)
|
||||
{
|
||||
// Read a chunk of content.
|
||||
Content.Chunk chunk = request.read();
|
||||
|
||||
// If there is no content, demand to be
|
||||
// called back when more content is available.
|
||||
if (chunk == null)
|
||||
{
|
||||
request.demand(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a failure is read, complete with a failure.
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Throwable failure = chunk.getFailure();
|
||||
completeExceptionally(failure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Trailers trailers)
|
||||
{
|
||||
// Possibly process the request trailers here.
|
||||
// Trailers have an empty ByteBuffer and are a last chunk.
|
||||
}
|
||||
|
||||
// Process the request content chunk here.
|
||||
// After the processing, the chunk MUST be released.
|
||||
chunk.release();
|
||||
|
||||
// If the last chunk is read, complete normally.
|
||||
if (chunk.isLast())
|
||||
{
|
||||
complete(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Not the last chunk of content, loop around to read more.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initiate the read of the request content.
|
||||
reader.start();
|
||||
|
||||
// When the read is complete, complete the Handler callback.
|
||||
callback.completeWith(reader);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-source[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::response[]
|
||||
public class ResponseAPIs extends Handler.Abstract
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Sets/Gets the response HTTP status.
|
||||
// Replaces:
|
||||
// - servletResponse.setStatus(code);
|
||||
// - servletResponse.getStatus();
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
int status = response.getStatus();
|
||||
|
||||
// Gets the response HTTP headers.
|
||||
// Replaces:
|
||||
// - servletResponse.setHeader(name, value);
|
||||
// - servletResponse.addHeader(name, value);
|
||||
// - servletResponse.setDateHeader(name, date);
|
||||
// - servletResponse.addDateHeader(name, date);
|
||||
// - servletResponse.setIntHeader(name, value);
|
||||
// - servletResponse.addIntHeader(name, value);
|
||||
// - servletResponse.getHeaderNames()
|
||||
// - servletResponse.getHeader(name)
|
||||
// - servletResponse.getHeaders(name)
|
||||
// - servletResponse.containsHeader(name)
|
||||
HttpFields.Mutable responseHeaders = response.getHeaders();
|
||||
|
||||
// Sets an HTTP cookie.
|
||||
// Replaces:
|
||||
// - servletResponse.addCookie(cookie);
|
||||
HttpCookie cookie = HttpCookie.build("name", "value")
|
||||
.domain("example.org")
|
||||
.path("/path")
|
||||
.maxAge(Duration.ofDays(1).toSeconds())
|
||||
.build();
|
||||
Response.addCookie(response, cookie);
|
||||
|
||||
// Sets the response Content-Type.
|
||||
// Replaces:
|
||||
// - servletResponse.setContentType(type)
|
||||
responseHeaders.put(HttpHeader.CONTENT_TYPE, "text/plain; charset=UTF-8");
|
||||
|
||||
// Sets the response Content-Length.
|
||||
// Replaces:
|
||||
// - servletResponse.setContentLength(length)
|
||||
// - servletResponse.setContentLengthLong(length)
|
||||
responseHeaders.put(HttpHeader.CONTENT_LENGTH, 1024L);
|
||||
|
||||
// Sets/Gets the response trailers.
|
||||
// Replaces:
|
||||
// - servletResponse.setTrailerFields(() -> trailers)
|
||||
// - servletResponse.getTrailerFields()
|
||||
HttpFields trailers = HttpFields.build().put("checksum", 0xCAFE);
|
||||
response.setTrailersSupplier(trailers);
|
||||
Supplier<HttpFields> trailersSupplier = response.getTrailersSupplier();
|
||||
|
||||
// Gets whether the response is committed.
|
||||
// Replaces:
|
||||
// - servletResponse.isCommitted()
|
||||
boolean committed = response.isCommitted();
|
||||
|
||||
// Resets the response.
|
||||
// Replaces:
|
||||
// - servletResponse.reset();
|
||||
response.reset();
|
||||
|
||||
// Sends a redirect response.
|
||||
// Replaces:
|
||||
// - servletResponse.encodeRedirectURL(location)
|
||||
// - servletResponse.sendRedirect(location)
|
||||
String location = Request.toRedirectURI(request, "/redirect");
|
||||
Response.sendRedirect(request, response, callback, location);
|
||||
|
||||
// Sends an error response.
|
||||
// Replaces:
|
||||
// - servletResponse.sendError(code);
|
||||
// - servletResponse.sendError(code, message);
|
||||
Response.writeError(request, response, callback, HttpStatus.SERVICE_UNAVAILABLE_503, "Request Cannot be Processed");
|
||||
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// end::response[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsImplicit extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-implicit[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an implicit response with status code 200
|
||||
// with no content when returning from this method.
|
||||
|
||||
// The Handler callback must be completed when returning true.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-implicit[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsImplicitWithStatus extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-implicit-status[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an implicit response with status 204
|
||||
// with no content when returning from this method.
|
||||
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||
|
||||
// The Handler callback must be completed when returning true.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-implicit-status[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsExplicit extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-explicit[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an explicit response with status 204 with no content.
|
||||
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||
|
||||
// This explicit first write() writes the response status code and headers.
|
||||
// It is also the last write (as specified by the first parameter)
|
||||
// and writes an empty content (the second parameter, a null ByteBuffer).
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, null, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-explicit[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPISimpleContent extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-content[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
|
||||
// Explicit first write that writes the response status code, headers and content.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, content, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-content[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIFlush extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-content[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, content.remaining());
|
||||
|
||||
// Flush the response status code and the headers (no content).
|
||||
// This is the fist but non-last write.
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
response.write(false, null, completable);
|
||||
|
||||
// When the first write completes, perform the second (and last) write.
|
||||
completable.whenComplete((ignored, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Now explicitly write the content as the last write.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, content, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-content[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIString extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-string[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// Utility method to write UTF-8 string content.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
Content.Sink.write(response, true, "Hello World", callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-string[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIEcho extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-echo[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// Utility method to echo the content from the request to the response.
|
||||
// When the echo completes, the Handler callback is completed.
|
||||
Content.copy(request, response, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-echo[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPITrailers extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-trailers[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// The trailers must be set on the response before the first write.
|
||||
HttpFields.Mutable trailers = HttpFields.build();
|
||||
response.setTrailersSupplier(trailers);
|
||||
|
||||
// Explicit first write that writes the response status code, headers and content.
|
||||
// The trailers have not been written yet; they will be written with the last write.
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
response.write(false, content, completable);
|
||||
|
||||
completable.whenComplete((ignored, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Update the trailers
|
||||
trailers.put("Content-Checksum", 0xCAFE);
|
||||
|
||||
// Explicit last write to write the trailers
|
||||
// and complete the Handler callback.
|
||||
response.write(true, null, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-trailers[]
|
||||
}
|
||||
}
|
|
@ -658,10 +658,9 @@ public class HTTPServerDocs
|
|||
// end::contextHandlerCollection[]
|
||||
}
|
||||
|
||||
public void servletContextHandler() throws Exception
|
||||
{
|
||||
// tag::servletContextHandler[]
|
||||
class ShopCartServlet extends HttpServlet
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::servletContextHandler-servlet[]
|
||||
public class ShopCartServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
|
@ -669,7 +668,11 @@ public class HTTPServerDocs
|
|||
// Implement the shop cart functionality.
|
||||
}
|
||||
}
|
||||
// end::servletContextHandler-servlet[]
|
||||
|
||||
public void servletContextHandler() throws Exception
|
||||
{
|
||||
// tag::servletContextHandler-setup[]
|
||||
Server server = new Server();
|
||||
Connector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
@ -692,7 +695,7 @@ public class HTTPServerDocs
|
|||
server.setHandler(context);
|
||||
|
||||
server.start();
|
||||
// end::servletContextHandler[]
|
||||
// end::servletContextHandler-setup[]
|
||||
}
|
||||
|
||||
public void webAppContextHandler() throws Exception
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.client;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.eclipse.jetty.client.transport.HttpDestination;
|
||||
|
@ -23,6 +24,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
|
@ -33,6 +35,7 @@ import org.eclipse.jetty.server.Response;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -46,15 +49,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
public class HttpClientProxyProtocolTest
|
||||
{
|
||||
private ArrayByteBufferPool.Tracking serverBufferPool;
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private ArrayByteBufferPool.Tracking clientBufferPool;
|
||||
private HttpClient client;
|
||||
|
||||
private void startServer(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||
serverThreads.setName("server");
|
||||
server = new Server(serverThreads);
|
||||
serverBufferPool = new ArrayByteBufferPool.Tracking();
|
||||
server = new Server(serverThreads, null, serverBufferPool);
|
||||
HttpConnectionFactory http = new HttpConnectionFactory();
|
||||
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http.getProtocol());
|
||||
connector = new ServerConnector(server, 1, 1, proxy, http);
|
||||
|
@ -67,18 +73,22 @@ public class HttpClientProxyProtocolTest
|
|||
{
|
||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||
clientThreads.setName("client");
|
||||
clientBufferPool = new ArrayByteBufferPool.Tracking();
|
||||
client = new HttpClient();
|
||||
client.setExecutor(clientThreads);
|
||||
client.setByteBufferPool(clientBufferPool);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (server != null)
|
||||
server.stop();
|
||||
if (client != null)
|
||||
client.stop();
|
||||
LifeCycle.stop(client);
|
||||
LifeCycle.stop(server);
|
||||
Set<ArrayByteBufferPool.Tracking.Buffer> serverLeaks = serverBufferPool.getLeaks();
|
||||
assertEquals(0, serverLeaks.size(), serverBufferPool.dumpLeaks());
|
||||
Set<ArrayByteBufferPool.Tracking.Buffer> clientLeaks = clientBufferPool.getLeaks();
|
||||
assertEquals(0, clientLeaks.size(), clientBufferPool.dumpLeaks());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ArrayByteBufferPool$Tracking.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.SocketChannelEndPoint.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http.LEVEL=DEBUG
|
||||
|
|
|
@ -197,8 +197,7 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
|||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} ignored {}", this, app);
|
||||
LOG.warn("{} no environment for {}, ignoring", this, app);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,45 +13,33 @@
|
|||
|
||||
package org.eclipse.jetty.fcgi.client.transport.internal;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.client.Result;
|
||||
import org.eclipse.jetty.client.transport.HttpChannel;
|
||||
import org.eclipse.jetty.client.transport.HttpExchange;
|
||||
import org.eclipse.jetty.client.transport.HttpReceiver;
|
||||
import org.eclipse.jetty.client.transport.HttpSender;
|
||||
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.IdleTimeout;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HttpChannelOverFCGI extends HttpChannel
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class);
|
||||
|
||||
private final HttpConnectionOverFCGI connection;
|
||||
private final Flusher flusher;
|
||||
private final HttpSenderOverFCGI sender;
|
||||
private final HttpReceiverOverFCGI receiver;
|
||||
private final FCGIIdleTimeout idle;
|
||||
private int request;
|
||||
private HttpVersion version;
|
||||
|
||||
public HttpChannelOverFCGI(HttpConnectionOverFCGI connection, Flusher flusher, long idleTimeout)
|
||||
public HttpChannelOverFCGI(HttpConnectionOverFCGI connection)
|
||||
{
|
||||
super(connection.getHttpDestination());
|
||||
this.connection = connection;
|
||||
this.flusher = flusher;
|
||||
this.sender = new HttpSenderOverFCGI(this);
|
||||
this.receiver = new HttpReceiverOverFCGI(this);
|
||||
this.idle = new FCGIIdleTimeout(connection, idleTimeout);
|
||||
}
|
||||
|
||||
public HttpConnectionOverFCGI getHttpConnection()
|
||||
|
@ -81,28 +69,21 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
return receiver;
|
||||
}
|
||||
|
||||
public boolean isFailed()
|
||||
{
|
||||
return sender.isFailed() || receiver.isFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(HttpExchange exchange)
|
||||
{
|
||||
version = exchange.getRequest().getVersion();
|
||||
idle.onOpen();
|
||||
sender.send(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release()
|
||||
{
|
||||
connection.release(this);
|
||||
connection.release();
|
||||
}
|
||||
|
||||
protected void responseBegin(int code, String reason)
|
||||
{
|
||||
idle.notIdle();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return;
|
||||
|
@ -119,7 +100,6 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
|
||||
protected void responseHeaders()
|
||||
{
|
||||
idle.notIdle();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseHeaders(exchange);
|
||||
|
@ -127,7 +107,6 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
|
||||
protected void content(Content.Chunk chunk)
|
||||
{
|
||||
idle.notIdle();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.content(chunk);
|
||||
|
@ -135,7 +114,6 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
|
||||
protected void end()
|
||||
{
|
||||
idle.notIdle();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.end(exchange);
|
||||
|
@ -150,67 +128,33 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
promise.succeeded(false);
|
||||
}
|
||||
|
||||
void eof()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||
{
|
||||
super.exchangeTerminated(exchange, result);
|
||||
idle.onClose();
|
||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||
if (result.isFailed())
|
||||
connection.close(result.getFailure());
|
||||
else if (!connection.closeByHTTP(responseHeaders))
|
||||
else if (connection.isShutdown() || connection.isCloseByHTTP(responseHeaders))
|
||||
connection.close();
|
||||
else
|
||||
release();
|
||||
}
|
||||
|
||||
protected void flush(ByteBufferPool.Accumulator accumulator, Callback callback)
|
||||
{
|
||||
flusher.flush(accumulator, callback);
|
||||
connection.getFlusher().flush(accumulator, callback);
|
||||
}
|
||||
|
||||
void receive()
|
||||
{
|
||||
receiver.receive();
|
||||
}
|
||||
|
||||
private class FCGIIdleTimeout extends IdleTimeout
|
||||
{
|
||||
private final HttpConnectionOverFCGI connection;
|
||||
private boolean open;
|
||||
|
||||
public FCGIIdleTimeout(HttpConnectionOverFCGI connection, long idleTimeout)
|
||||
{
|
||||
super(connection.getHttpDestination().getHttpClient().getScheduler());
|
||||
this.connection = connection;
|
||||
setIdleTimeout(idleTimeout >= 0 ? idleTimeout : connection.getEndPoint().getIdleTimeout());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
open = true;
|
||||
notIdle();
|
||||
super.onOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
super.onClose();
|
||||
open = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIdleExpired(TimeoutException timeout)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Idle timeout for request {}", request);
|
||||
connection.abort(timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen()
|
||||
{
|
||||
return open;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,13 @@
|
|||
|
||||
package org.eclipse.jetty.fcgi.client.transport.internal;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.client.Connection;
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
|
@ -49,7 +48,6 @@ import org.eclipse.jetty.io.RetainableByteBuffer;
|
|||
import org.eclipse.jetty.util.Attachable;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -58,18 +56,19 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionOverFCGI.class);
|
||||
|
||||
private final ByteBufferPool networkByteBufferPool;
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final LinkedList<Integer> requests = new LinkedList<>();
|
||||
private final AtomicInteger requests = new AtomicInteger();
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final HttpDestination destination;
|
||||
private final Promise<Connection> promise;
|
||||
private final Flusher flusher;
|
||||
private final Delegate delegate;
|
||||
private final ClientParser parser;
|
||||
private HttpChannelOverFCGI channel;
|
||||
private final HttpChannelOverFCGI channel;
|
||||
private RetainableByteBuffer networkBuffer;
|
||||
private Object attachment;
|
||||
private Runnable action;
|
||||
private long idleTimeout;
|
||||
private boolean shutdown;
|
||||
|
||||
public HttpConnectionOverFCGI(EndPoint endPoint, Destination destination, Promise<Connection> promise)
|
||||
{
|
||||
|
@ -79,7 +78,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
this.flusher = new Flusher(endPoint);
|
||||
this.delegate = new Delegate(destination);
|
||||
this.parser = new ClientParser(new ResponseListener());
|
||||
requests.addLast(0);
|
||||
this.channel = newHttpChannel();
|
||||
HttpClient client = destination.getHttpClient();
|
||||
this.networkByteBufferPool = client.getByteBufferPool();
|
||||
}
|
||||
|
@ -207,13 +206,18 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
|
||||
private void shutdown()
|
||||
{
|
||||
// Close explicitly only if we are idle, since the request may still
|
||||
// be in progress, otherwise close only if we can fail the responses.
|
||||
HttpChannelOverFCGI channel = this.channel;
|
||||
if (channel == null || channel.getRequest() == 0)
|
||||
close();
|
||||
else
|
||||
failAndClose(new EOFException(String.valueOf(getEndPoint())));
|
||||
// Mark this receiver as shutdown, so that we can
|
||||
// close the connection when the exchange terminates.
|
||||
// We cannot close the connection from here because
|
||||
// the request may still be in process.
|
||||
shutdown = true;
|
||||
if (!parser.eof())
|
||||
channel.eof();
|
||||
}
|
||||
|
||||
boolean isShutdown()
|
||||
{
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -226,28 +230,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
return false;
|
||||
}
|
||||
|
||||
protected void release(HttpChannelOverFCGI channel)
|
||||
protected void release()
|
||||
{
|
||||
HttpChannelOverFCGI existing = this.channel;
|
||||
if (existing == channel)
|
||||
{
|
||||
channel.setRequest(0);
|
||||
// Recycle only non-failed channels.
|
||||
if (channel.isFailed())
|
||||
{
|
||||
channel.destroy();
|
||||
this.channel = null;
|
||||
}
|
||||
// Restore idle timeout
|
||||
getEndPoint().setIdleTimeout(idleTimeout);
|
||||
destination.release(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existing == null)
|
||||
channel.destroy();
|
||||
else
|
||||
throw new UnsupportedOperationException("FastCGI Multiplex");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
|
@ -260,9 +248,8 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
if (closed.compareAndSet(false, true))
|
||||
{
|
||||
getHttpDestination().remove(this);
|
||||
|
||||
abort(failure);
|
||||
|
||||
channel.destroy();
|
||||
getEndPoint().shutdownOutput();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Shutdown {}", this);
|
||||
|
@ -290,62 +277,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
return attachment;
|
||||
}
|
||||
|
||||
protected boolean closeByHTTP(HttpFields fields)
|
||||
protected boolean isCloseByHTTP(HttpFields fields)
|
||||
{
|
||||
if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
|
||||
return false;
|
||||
close();
|
||||
return true;
|
||||
return fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
}
|
||||
|
||||
protected void abort(Throwable failure)
|
||||
{
|
||||
HttpChannelOverFCGI channel = this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
HttpExchange exchange = channel.getHttpExchange();
|
||||
if (exchange != null)
|
||||
exchange.getRequest().abort(failure);
|
||||
channel.destroy();
|
||||
this.channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void failAndClose(Throwable failure)
|
||||
{
|
||||
HttpChannelOverFCGI channel = this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
channel.responseFailure(failure, Promise.from(failed ->
|
||||
{
|
||||
channel.destroy();
|
||||
if (failed)
|
||||
close(failure);
|
||||
}, x ->
|
||||
{
|
||||
channel.destroy();
|
||||
close(failure);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private int acquireRequest()
|
||||
{
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
int last = requests.getLast();
|
||||
int request = last + 1;
|
||||
requests.addLast(request);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseRequest(int request)
|
||||
{
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
requests.removeFirstOccurrence(request);
|
||||
}
|
||||
}, x -> close(failure)));
|
||||
}
|
||||
|
||||
private Runnable getAndSetAction(Runnable action)
|
||||
|
@ -355,17 +305,9 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
return r;
|
||||
}
|
||||
|
||||
protected HttpChannelOverFCGI acquireHttpChannel(int id, Request request)
|
||||
protected HttpChannelOverFCGI newHttpChannel()
|
||||
{
|
||||
if (channel == null)
|
||||
channel = newHttpChannel(request);
|
||||
channel.setRequest(id);
|
||||
return channel;
|
||||
}
|
||||
|
||||
protected HttpChannelOverFCGI newHttpChannel(Request request)
|
||||
{
|
||||
return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout());
|
||||
return new HttpChannelOverFCGI(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -388,8 +330,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
@Override
|
||||
protected Iterator<HttpChannel> getHttpChannels()
|
||||
{
|
||||
HttpChannel channel = HttpConnectionOverFCGI.this.channel;
|
||||
return channel == null ? Collections.emptyIterator() : Collections.singleton(channel).iterator();
|
||||
return Collections.<HttpChannel>singleton(channel).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -398,9 +339,14 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
HttpRequest request = exchange.getRequest();
|
||||
normalizeRequest(request);
|
||||
|
||||
int id = acquireRequest();
|
||||
HttpChannelOverFCGI channel = acquireHttpChannel(id, request);
|
||||
// Save the old idle timeout to restore it.
|
||||
EndPoint endPoint = getEndPoint();
|
||||
idleTimeout = endPoint.getIdleTimeout();
|
||||
long requestIdleTimeout = request.getIdleTimeout();
|
||||
if (requestIdleTimeout >= 0)
|
||||
endPoint.setIdleTimeout(requestIdleTimeout);
|
||||
|
||||
channel.setRequest(requests.incrementAndGet());
|
||||
return send(channel, exchange);
|
||||
}
|
||||
|
||||
|
@ -431,11 +377,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onBegin r={},c={},reason={}", request, code, reason);
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
channel.responseBegin(code, reason);
|
||||
else
|
||||
noChannel(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -443,11 +385,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onHeader r={},f={}", request, field);
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
channel.responseHeader(field);
|
||||
else
|
||||
noChannel(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -455,16 +393,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onHeaders r={} {}", request, networkBuffer);
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
if (getAndSetAction(channel::responseHeaders) != null)
|
||||
throw new IllegalStateException();
|
||||
return true;
|
||||
}
|
||||
noChannel(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||
|
@ -474,9 +406,6 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
switch (stream)
|
||||
{
|
||||
case STD_OUT ->
|
||||
{
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
// No need to call networkBuffer.retain() here, since we know
|
||||
// that the action will be run before releasing the networkBuffer.
|
||||
|
@ -486,11 +415,6 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
return true;
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
else
|
||||
{
|
||||
noChannel(request);
|
||||
}
|
||||
}
|
||||
case STD_ERR -> LOG.info(BufferUtil.toUTF8String(buffer));
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
|
@ -502,42 +426,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onEnd r={}", request);
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
releaseRequest(request);
|
||||
channel.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
noChannel(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int request, Throwable failure)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onFailure request={}", request, failure);
|
||||
HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel;
|
||||
if (channel != null)
|
||||
{
|
||||
channel.responseFailure(failure, Promise.from(failed ->
|
||||
{
|
||||
if (failed)
|
||||
releaseRequest(request);
|
||||
}, x -> releaseRequest(request)));
|
||||
}
|
||||
else
|
||||
{
|
||||
noChannel(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void noChannel(int request)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Channel not found for request {}", request);
|
||||
failAndClose(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,17 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
if (chunk != null)
|
||||
{
|
||||
chunk.release();
|
||||
chunk = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk read(boolean fillInterestIfNeeded)
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.fcgi.parser;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
|
@ -60,7 +61,7 @@ public abstract class Parser
|
|||
|
||||
protected final HeaderParser headerParser = new HeaderParser();
|
||||
private final Listener listener;
|
||||
private State state = State.HEADER;
|
||||
private State state = State.INITIAL;
|
||||
private int padding;
|
||||
|
||||
protected Parser(Listener listener)
|
||||
|
@ -80,6 +81,12 @@ public abstract class Parser
|
|||
{
|
||||
switch (state)
|
||||
{
|
||||
case INITIAL ->
|
||||
{
|
||||
if (!buffer.hasRemaining())
|
||||
return false;
|
||||
state = State.HEADER;
|
||||
}
|
||||
case HEADER ->
|
||||
{
|
||||
if (!headerParser.parse(buffer))
|
||||
|
@ -145,10 +152,19 @@ public abstract class Parser
|
|||
|
||||
protected abstract ContentParser findContentParser(FCGI.FrameType frameType);
|
||||
|
||||
public boolean eof()
|
||||
{
|
||||
if (state == State.INITIAL)
|
||||
return false;
|
||||
Throwable failure = new EOFException();
|
||||
listener.onFailure(headerParser.getRequest(), failure);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headerParser.reset();
|
||||
state = State.HEADER;
|
||||
state = State.INITIAL;
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
|
@ -190,6 +206,6 @@ public abstract class Parser
|
|||
|
||||
private enum State
|
||||
{
|
||||
HEADER, CONTENT, PADDING
|
||||
INITIAL, HEADER, CONTENT, PADDING
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.eclipse.jetty.server.Connector;
|
|||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -156,12 +155,6 @@ public class ServerFCGIConnection extends AbstractConnection implements Connecti
|
|||
return getEndPoint().getLocalSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostPort getServerAuthority()
|
||||
{
|
||||
return ConnectionMetaData.getServerAuthority(configuration, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
|
@ -270,6 +263,8 @@ public class ServerFCGIConnection extends AbstractConnection implements Connecti
|
|||
|
||||
private void releaseInputBuffer()
|
||||
{
|
||||
if (networkBuffer == null)
|
||||
return;
|
||||
boolean released = networkBuffer.release();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("releaseInputBuffer {} {}", released, this);
|
||||
|
@ -327,6 +322,9 @@ public class ServerFCGIConnection extends AbstractConnection implements Connecti
|
|||
@Override
|
||||
public boolean onIdleExpired(TimeoutException timeoutException)
|
||||
{
|
||||
HttpStreamOverFCGI stream = this.stream;
|
||||
if (stream == null)
|
||||
return true;
|
||||
Runnable task = stream.getHttpChannel().onIdleTimeout(timeoutException);
|
||||
if (task != null)
|
||||
getExecutor().execute(task);
|
||||
|
|
|
@ -32,7 +32,9 @@ import java.util.zip.GZIPOutputStream;
|
|||
|
||||
import org.eclipse.jetty.client.AsyncRequestContent;
|
||||
import org.eclipse.jetty.client.BytesRequestContent;
|
||||
import org.eclipse.jetty.client.ConnectionPool;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
import org.eclipse.jetty.client.FutureResponseListener;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
|
@ -515,6 +517,35 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
@Test
|
||||
public void testConnectionIdleTimeout() throws Exception
|
||||
{
|
||||
long idleTimeout = 1000;
|
||||
start(new Handler.Abstract()
|
||||
{
|
||||
@Override
|
||||
public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
|
||||
{
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
connector.setIdleTimeout(idleTimeout);
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scheme)
|
||||
.timeout(2 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||
.send();
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Thread.sleep(2 * idleTimeout);
|
||||
|
||||
assertTrue(client.getDestinations().stream()
|
||||
.map(Destination::getConnectionPool)
|
||||
.allMatch(ConnectionPool::isEmpty));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionIdleTimeoutIgnored() throws Exception
|
||||
{
|
||||
long idleTimeout = 1000;
|
||||
start(new Handler.Abstract()
|
||||
|
@ -522,9 +553,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Handler says it will handle the idletimeout
|
||||
// Handler says it will handle the idle timeout by ignoring it.
|
||||
request.addIdleTimeoutListener(t -> false);
|
||||
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||
// Sleep an non-integral number of idle timeouts to avoid
|
||||
// racing with the idle timeout ticking every idle period.
|
||||
TimeUnit.MILLISECONDS.sleep(idleTimeout * 3 / 2);
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -774,7 +774,7 @@ public class HttpGenerator
|
|||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(_endOfContent.toString());
|
||||
LOG.debug("endOfContent {} content-Length {}", _endOfContent.toString(), contentLength);
|
||||
|
||||
// Add transfer encoding if it is not chunking
|
||||
if (transferEncoding != null)
|
||||
|
|
|
@ -656,7 +656,7 @@ public class MultiPart
|
|||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
// TODO: it is difficult to calculate the length because
|
||||
// TODO: #10307 it is difficult to calculate the length because
|
||||
// we need to allow for customization of the headers from
|
||||
// subclasses, and then serialize all the headers to get
|
||||
// their length (handling UTF-8 values) and we don't want
|
||||
|
|
|
@ -51,7 +51,6 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
|||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -417,12 +416,6 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
return getEndPoint().getLocalSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostPort getServerAuthority()
|
||||
{
|
||||
return ConnectionMetaData.getServerAuthority(httpConfig, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
|
|
|
@ -14,11 +14,17 @@
|
|||
package org.eclipse.jetty.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.io.internal.CompoundPool;
|
||||
import org.eclipse.jetty.io.internal.QueuedPool;
|
||||
|
@ -564,4 +570,112 @@ public class ArrayByteBufferPool implements ByteBufferPool, Dumpable
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A variant of {@link ArrayByteBufferPool} that tracks buffer
|
||||
* acquires/releases, useful to identify buffer leaks.</p>
|
||||
* <p>Use {@link #getLeaks()} when the system is idle to get
|
||||
* the {@link Buffer}s that have been leaked, which contain
|
||||
* the stack trace information of where the buffer was acquired.</p>
|
||||
*/
|
||||
public static class Tracking extends ArrayByteBufferPool
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Tracking.class);
|
||||
|
||||
private final Set<Buffer> buffers = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public Tracking()
|
||||
{
|
||||
this(0, -1, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public Tracking(int minCapacity, int maxCapacity, int maxBucketSize)
|
||||
{
|
||||
this(minCapacity, maxCapacity, maxBucketSize, -1L, -1L);
|
||||
}
|
||||
|
||||
public Tracking(int minCapacity, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory)
|
||||
{
|
||||
super(minCapacity, -1, maxCapacity, maxBucketSize, maxHeapMemory, maxDirectMemory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetainableByteBuffer acquire(int size, boolean direct)
|
||||
{
|
||||
RetainableByteBuffer buffer = super.acquire(size, direct);
|
||||
Buffer wrapper = new Buffer(buffer, size);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("acquired {}", wrapper);
|
||||
buffers.add(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public Set<Buffer> getLeaks()
|
||||
{
|
||||
return buffers;
|
||||
}
|
||||
|
||||
public String dumpLeaks()
|
||||
{
|
||||
return getLeaks().stream()
|
||||
.map(Buffer::dump)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
|
||||
public class Buffer extends RetainableByteBuffer.Wrapper
|
||||
{
|
||||
private final int size;
|
||||
private final Instant acquireInstant;
|
||||
private final Throwable acquireStack;
|
||||
|
||||
private Buffer(RetainableByteBuffer wrapped, int size)
|
||||
{
|
||||
super(wrapped);
|
||||
this.size = size;
|
||||
this.acquireInstant = Instant.now();
|
||||
this.acquireStack = new Throwable();
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public Instant getAcquireInstant()
|
||||
{
|
||||
return acquireInstant;
|
||||
}
|
||||
|
||||
public Throwable getAcquireStack()
|
||||
{
|
||||
return acquireStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release()
|
||||
{
|
||||
boolean released = super.release();
|
||||
if (released)
|
||||
{
|
||||
buffers.remove(this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("released {}", this);
|
||||
}
|
||||
return released;
|
||||
}
|
||||
|
||||
public String dump()
|
||||
{
|
||||
StringWriter w = new StringWriter();
|
||||
getAcquireStack().printStackTrace(new PrintWriter(w));
|
||||
return "%s of %d bytes on %s at %s".formatted(getClass().getSimpleName(), getSize(), getAcquireInstant(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), super.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.eclipse.jetty.io.content.PathContentSource;
|
|||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.CompletableTask;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
@ -104,7 +105,9 @@ public class ContentSourceTest
|
|||
return List.of(asyncSource, byteBufferSource, transformerSource, pathSource, inputSource, inputSource2);
|
||||
}
|
||||
|
||||
/** Get the next chunk, blocking if necessary
|
||||
/**
|
||||
* Get the next chunk, blocking if necessary
|
||||
*
|
||||
* @param source The source to get the next chunk from
|
||||
* @return A non null chunk
|
||||
*/
|
||||
|
@ -113,8 +116,7 @@ public class ContentSourceTest
|
|||
Content.Chunk chunk = source.read();
|
||||
if (chunk != null)
|
||||
return chunk;
|
||||
FuturePromise<Content.Chunk> next = new FuturePromise<>();
|
||||
Runnable getNext = new Runnable()
|
||||
CompletableTask<Content.Chunk> task = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
|
@ -122,18 +124,12 @@ public class ContentSourceTest
|
|||
Content.Chunk chunk = source.read();
|
||||
if (chunk == null)
|
||||
source.demand(this);
|
||||
next.succeeded(chunk);
|
||||
else
|
||||
complete(chunk);
|
||||
}
|
||||
};
|
||||
source.demand(getNext);
|
||||
try
|
||||
{
|
||||
return next.get();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
source.demand(task);
|
||||
return task.join();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -141,8 +137,7 @@ public class ContentSourceTest
|
|||
public void testRead(Content.Source source) throws Exception
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
CountDownLatch eof = new CountDownLatch(1);
|
||||
source.demand(new Runnable()
|
||||
var task = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
|
@ -162,14 +157,14 @@ public class ContentSourceTest
|
|||
|
||||
if (chunk.isLast())
|
||||
{
|
||||
eof.countDown();
|
||||
complete(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(eof.await(10, TimeUnit.SECONDS));
|
||||
};
|
||||
source.demand(task);
|
||||
task.get(10, TimeUnit.SECONDS);
|
||||
assertThat(builder.toString(), is("onetwo"));
|
||||
}
|
||||
|
||||
|
|
|
@ -181,6 +181,12 @@ public class DeferredAuthenticationState implements AuthenticationState.Deferred
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
|
|
|
@ -67,20 +67,21 @@ public interface ConnectionMetaData extends Attributes
|
|||
|
||||
/**
|
||||
* @return The URI authority that this server represents. By default, this is the address of the network socket on
|
||||
* which the connection was accepted, but it may be wrapped to represent a virtual address.
|
||||
* which the connection was accepted, but it may be configured to a specific address.
|
||||
* @see HttpConfiguration#setServerAuthority(HostPort)
|
||||
*/
|
||||
HostPort getServerAuthority();
|
||||
|
||||
static HostPort getServerAuthority(HttpConfiguration httpConfiguration, ConnectionMetaData connectionMetaData)
|
||||
default HostPort getServerAuthority()
|
||||
{
|
||||
HttpConfiguration httpConfiguration = getHttpConfiguration();
|
||||
HostPort authority = httpConfiguration.getServerAuthority();
|
||||
if (authority != null)
|
||||
return authority;
|
||||
|
||||
SocketAddress local = connectionMetaData.getLocalSocketAddress();
|
||||
if (local instanceof InetSocketAddress inet)
|
||||
return new HostPort(inet.getHostString(), inet.getPort());
|
||||
|
||||
SocketAddress localSocketAddress = getLocalSocketAddress();
|
||||
if (localSocketAddress instanceof InetSocketAddress inetSocketAddress)
|
||||
return new HostPort(inetSocketAddress.getHostString(), inetSocketAddress.getPort());
|
||||
else if (localSocketAddress != null)
|
||||
return new HostPort(localSocketAddress.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.slf4j.LoggerFactory;
|
|||
* <p>This factory can be placed in front of any other connection factory
|
||||
* to process the proxy v1 or v2 line before the normal protocol handling</p>
|
||||
*
|
||||
* @see <a href="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt">http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt</a>
|
||||
* @see <a href="https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt">PROXY protocol</a>
|
||||
*/
|
||||
public class ProxyConnectionFactory extends DetectorConnectionFactory
|
||||
{
|
||||
|
@ -245,6 +245,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
_buffer.release();
|
||||
return unconsumed;
|
||||
}
|
||||
_buffer.release();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -564,6 +565,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
_buffer.release();
|
||||
return unconsumed;
|
||||
}
|
||||
_buffer.release();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -591,7 +593,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
SocketAddress remote;
|
||||
switch (_family)
|
||||
{
|
||||
case INET:
|
||||
case INET ->
|
||||
{
|
||||
byte[] addr = new byte[4];
|
||||
byteBuffer.get(addr);
|
||||
|
@ -602,9 +604,8 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
int dstPort = byteBuffer.getChar();
|
||||
local = new InetSocketAddress(dstAddr, dstPort);
|
||||
remote = new InetSocketAddress(srcAddr, srcPort);
|
||||
break;
|
||||
}
|
||||
case INET6:
|
||||
case INET6 ->
|
||||
{
|
||||
byte[] addr = new byte[16];
|
||||
byteBuffer.get(addr);
|
||||
|
@ -615,9 +616,8 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
int dstPort = byteBuffer.getChar();
|
||||
local = new InetSocketAddress(dstAddr, dstPort);
|
||||
remote = new InetSocketAddress(srcAddr, srcPort);
|
||||
break;
|
||||
}
|
||||
case UNIX:
|
||||
case UNIX ->
|
||||
{
|
||||
byte[] addr = new byte[108];
|
||||
byteBuffer.get(addr);
|
||||
|
@ -626,12 +626,8 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
String dst = UnixDomain.toPath(addr);
|
||||
local = UnixDomain.newSocketAddress(dst);
|
||||
remote = UnixDomain.newSocketAddress(src);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("Unsupported family " + _family);
|
||||
}
|
||||
default -> throw new IllegalStateException("Unsupported family " + _family);
|
||||
}
|
||||
proxyEndPoint = new ProxyEndPoint(endPoint, local, remote);
|
||||
|
||||
|
@ -714,37 +710,20 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
int transportAndFamily = 0xFF & byteBuffer.get();
|
||||
switch (transportAndFamily >> 4)
|
||||
{
|
||||
case 0:
|
||||
_family = Family.UNSPEC;
|
||||
break;
|
||||
case 1:
|
||||
_family = Family.INET;
|
||||
break;
|
||||
case 2:
|
||||
_family = Family.INET6;
|
||||
break;
|
||||
case 3:
|
||||
_family = Family.UNIX;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Proxy v2 bad PROXY family");
|
||||
case 0 -> _family = Family.UNSPEC;
|
||||
case 1 -> _family = Family.INET;
|
||||
case 2 -> _family = Family.INET6;
|
||||
case 3 -> _family = Family.UNIX;
|
||||
default -> throw new IOException("Proxy v2 bad PROXY family");
|
||||
}
|
||||
|
||||
Transport transport;
|
||||
switch (transportAndFamily & 0xF)
|
||||
Transport transport = switch (transportAndFamily & 0xF)
|
||||
{
|
||||
case 0:
|
||||
transport = Transport.UNSPEC;
|
||||
break;
|
||||
case 1:
|
||||
transport = Transport.STREAM;
|
||||
break;
|
||||
case 2:
|
||||
transport = Transport.DGRAM;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Proxy v2 bad PROXY family");
|
||||
}
|
||||
case 0 -> Transport.UNSPEC;
|
||||
case 1 -> Transport.STREAM;
|
||||
case 2 -> Transport.DGRAM;
|
||||
default -> throw new IOException("Proxy v2 bad PROXY family");
|
||||
};
|
||||
|
||||
_length = byteBuffer.getChar();
|
||||
|
||||
|
@ -761,6 +740,8 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
|
|||
|
||||
private void releaseAndClose()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Proxy v2 releasing buffer and closing");
|
||||
_buffer.release();
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -403,6 +403,12 @@ public interface Request extends Attributes, Content.Source
|
|||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logical name the request was sent to, which may be from the authority of the
|
||||
* request; the configured server authority; the actual network name of the server;
|
||||
* @param request The request to get the server name of
|
||||
* @return The logical server name or null if it cannot be determined.
|
||||
*/
|
||||
static String getServerName(Request request)
|
||||
{
|
||||
if (request == null)
|
||||
|
@ -414,38 +420,42 @@ public interface Request extends Attributes, Content.Source
|
|||
|
||||
HostPort authority = request.getConnectionMetaData().getServerAuthority();
|
||||
if (authority != null)
|
||||
return HostPort.normalizeHost(authority.getHost());
|
||||
return authority.getHost();
|
||||
|
||||
SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress();
|
||||
if (local instanceof InetSocketAddress)
|
||||
return HostPort.normalizeHost(((InetSocketAddress)local).getHostString());
|
||||
|
||||
return local == null ? null : local.toString();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logical port a request was received on, which may be from the authority of the request; the
|
||||
* configured server authority; the default port for the scheme; or the actual network port.
|
||||
* @param request The request to get the port of
|
||||
* @return The port for the request if it can be determined, otherwise -1
|
||||
*/
|
||||
static int getServerPort(Request request)
|
||||
{
|
||||
if (request == null)
|
||||
return -1;
|
||||
|
||||
// Does the request have an explicit port?
|
||||
HttpURI uri = request.getHttpURI();
|
||||
if (uri.hasAuthority() && uri.getPort() > 0)
|
||||
return uri.getPort();
|
||||
|
||||
HostPort authority = request.getConnectionMetaData().getServerAuthority();
|
||||
// Is there a configured server authority?
|
||||
HostPort authority = request.getConnectionMetaData().getHttpConfiguration().getServerAuthority();
|
||||
if (authority != null && authority.getPort() > 0)
|
||||
return authority.getPort();
|
||||
|
||||
if (authority == null)
|
||||
{
|
||||
SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress();
|
||||
if (local instanceof InetSocketAddress)
|
||||
return ((InetSocketAddress)local).getPort();
|
||||
}
|
||||
|
||||
// Is there a scheme with a default port?
|
||||
HttpScheme scheme = HttpScheme.CACHE.get(request.getHttpURI().getScheme());
|
||||
if (scheme != null)
|
||||
if (scheme != null && scheme.getDefaultPort() > 0)
|
||||
return scheme.getDefaultPort();
|
||||
|
||||
// Is there a local port?
|
||||
SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress();
|
||||
if (local instanceof InetSocketAddress inetSocketAddress && inetSocketAddress.getPort() > 0)
|
||||
return inetSocketAddress.getPort();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -770,11 +780,11 @@ public interface Request extends Attributes, Content.Source
|
|||
@SuppressWarnings("unchecked")
|
||||
static <T> T as(Request request, Class<T> type)
|
||||
{
|
||||
while (request instanceof Request.Wrapper wrapper)
|
||||
while (request != null)
|
||||
{
|
||||
if (type.isInstance(wrapper))
|
||||
return (T)wrapper;
|
||||
request = wrapper.getWrapped();
|
||||
if (type.isInstance(request))
|
||||
return (T)request;
|
||||
request = request instanceof Request.Wrapper wrapper ? wrapper.getWrapped() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ public class ResourceService
|
|||
return _gzipEquivalentFileExtensions;
|
||||
}
|
||||
|
||||
public void doGet(Request request, Response response, Callback callback, HttpContent content) throws Exception
|
||||
public void doGet(Request request, Response response, Callback callback, HttpContent content)
|
||||
{
|
||||
String pathInContext = Request.getPathInContext(request);
|
||||
|
||||
|
@ -523,7 +523,7 @@ public class ResourceService
|
|||
// TODO : check conditional headers.
|
||||
serveWelcome(request, response, callback, welcomeAction.target);
|
||||
case REHANDLE -> rehandleWelcome(request, response, callback, welcomeAction.target);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -683,14 +683,15 @@ public class ResourceService
|
|||
}
|
||||
|
||||
// There are multiple non-overlapping ranges, send a multipart/byteranges 206 response.
|
||||
putHeaders(response, content, NO_CONTENT_LENGTH);
|
||||
response.setStatus(HttpStatus.PARTIAL_CONTENT_206);
|
||||
String contentType = "multipart/byteranges; boundary=";
|
||||
String boundary = MultiPart.generateBoundary(null, 24);
|
||||
response.getHeaders().put(HttpHeader.CONTENT_TYPE, contentType + boundary);
|
||||
MultiPartByteRanges.ContentSource byteRanges = new MultiPartByteRanges.ContentSource(boundary);
|
||||
ranges.forEach(range -> byteRanges.addPart(new MultiPartByteRanges.Part(content.getContentTypeValue(), content.getResource().getPath(), range, contentLength)));
|
||||
byteRanges.close();
|
||||
long partsContentLength = byteRanges.getLength();
|
||||
putHeaders(response, content, partsContentLength);
|
||||
response.getHeaders().put(HttpHeader.CONTENT_TYPE, contentType + boundary);
|
||||
Content.copy(byteRanges, response, callback);
|
||||
}
|
||||
|
||||
|
|
|
@ -99,6 +99,13 @@ public interface Response extends Content.Sink
|
|||
*/
|
||||
boolean isCommitted();
|
||||
|
||||
/**
|
||||
* <p>Returns whether the last write has been initiated on the response.</p>
|
||||
*
|
||||
* @return {@code true} if {@code last==true} has been passed to {@link #write(boolean, ByteBuffer, Callback)}.
|
||||
*/
|
||||
boolean hasLastWrite();
|
||||
|
||||
/**
|
||||
* <p>Returns whether the response completed successfully.</p>
|
||||
* <p>The response HTTP status code, HTTP headers and content
|
||||
|
@ -207,13 +214,13 @@ public interface Response extends Content.Sink
|
|||
* @see Wrapper
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends Response.Wrapper> T as(Response response, Class<T> type)
|
||||
static <T extends Response> T as(Response response, Class<T> type)
|
||||
{
|
||||
while (response instanceof Response.Wrapper wrapper)
|
||||
while (response != null)
|
||||
{
|
||||
if (type.isInstance(wrapper))
|
||||
return (T)wrapper;
|
||||
response = wrapper.getWrapped();
|
||||
if (type.isInstance(response))
|
||||
return (T)response;
|
||||
response = response instanceof Response.Wrapper wrapper ? wrapper.getWrapped() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -580,6 +587,12 @@ public interface Response extends Content.Sink
|
|||
return getWrapped().isCommitted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
return getWrapped().hasLastWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
|
|
|
@ -69,8 +69,7 @@ public abstract class ReHandlingErrorHandler extends ErrorHandler
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Unable to process error {}", reRequest, e);
|
||||
if (ExceptionUtil.areNotAssociated(cause, e))
|
||||
cause.addSuppressed(e);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(cause, e);
|
||||
response.setStatus(code);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,7 +136,13 @@ public class GzipResponseAndCallback extends Response.Wrapper implements Callbac
|
|||
case NOT_COMPRESSING -> super.write(last, content, callback);
|
||||
case COMMITTING -> callback.failed(new WritePendingException());
|
||||
case COMPRESSING -> gzip(last, callback, content);
|
||||
default -> callback.failed(new IllegalStateException("state=" + _state.get()));
|
||||
default ->
|
||||
{
|
||||
if (BufferUtil.isEmpty(content))
|
||||
callback.succeeded();
|
||||
else
|
||||
callback.failed(new IllegalStateException("state=" + _state.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -473,8 +473,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
}
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(x, throwable))
|
||||
x.addSuppressed(throwable);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(x, throwable);
|
||||
}
|
||||
|
||||
// If the application has not been otherwise informed of the failure
|
||||
|
@ -1080,8 +1079,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(throwable, t))
|
||||
throwable.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(throwable, t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -1354,6 +1352,18 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
return _httpFields.isCommitted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
try (AutoLock ignored = _request._lock.lock())
|
||||
{
|
||||
if (_request._httpChannelState == null)
|
||||
return true;
|
||||
|
||||
return _request._httpChannelState._streamSendState != StreamSendState.SENDING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
|
@ -1540,8 +1550,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
|
||||
// Consume any input.
|
||||
Throwable unconsumed = stream.consumeAvailable();
|
||||
if (ExceptionUtil.areNotAssociated(unconsumed, failure))
|
||||
failure.addSuppressed(unconsumed);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(failure, unconsumed);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("failed stream.isCommitted={}, response.isCommitted={} {}", httpChannelState._stream.isCommitted(), httpChannelState._response.isCommitted(), this);
|
||||
|
@ -1689,8 +1698,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
Callback.from(() -> httpChannelState._handlerInvoker.failed(failure),
|
||||
x ->
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(failure, x))
|
||||
failure.addSuppressed(x);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(failure, x);
|
||||
httpChannelState._handlerInvoker.failed(failure);
|
||||
}));
|
||||
}
|
||||
|
@ -1758,8 +1766,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(failure, t))
|
||||
failure.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(failure, t);
|
||||
super.onError(task, failure);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -288,15 +288,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
return getEndPoint().getLocalSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostPort getServerAuthority()
|
||||
{
|
||||
HostPort authority = ConnectionMetaData.getServerAuthority(getHttpConfiguration(), this);
|
||||
if (authority == null)
|
||||
authority = new HostPort(getLocalSocketAddress().toString(), -1);
|
||||
return authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -22,6 +24,9 @@ import java.util.stream.Stream;
|
|||
|
||||
import org.eclipse.jetty.http.HttpCompliance;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.internal.HttpConnection;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -75,7 +80,25 @@ public class ForwardedRequestCustomizerTest
|
|||
server.addConnector(connector);
|
||||
|
||||
// Alternate behavior Connector
|
||||
HttpConnectionFactory httpAlt = new HttpConnectionFactory();
|
||||
HttpConnectionFactory httpAlt = new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
{
|
||||
HttpConnection connection = new HttpConnection(getHttpConfiguration(), connector, endPoint, isRecordHttpComplianceViolations())
|
||||
{
|
||||
@Override
|
||||
public SocketAddress getLocalSocketAddress()
|
||||
{
|
||||
return InetSocketAddress.createUnresolved("test", 42);
|
||||
}
|
||||
};
|
||||
connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers());
|
||||
connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers());
|
||||
return configure(connection, connector, endPoint);
|
||||
}
|
||||
};
|
||||
|
||||
httpAlt.getHttpConfiguration().setSecurePort(8443);
|
||||
httpAlt.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
|
||||
customizerAlt = new ForwardedRequestCustomizer();
|
||||
|
@ -128,27 +151,6 @@ public class ForwardedRequestCustomizerTest
|
|||
server.stop();
|
||||
}
|
||||
|
||||
public static Stream<Arguments> cases2()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of(new TestRequest("https initial authority, X-Forwarded-Proto on http, Proxy-Ssl-Id exists (setSslIsSecure==false)")
|
||||
.configureCustomizer((customizer) -> customizer.setSslIsSecure(false))
|
||||
.headers(
|
||||
"GET https://alt.example.net/foo HTTP/1.1",
|
||||
"Host: alt.example.net",
|
||||
"X-Forwarded-Proto: http",
|
||||
"Proxy-Ssl-Id: Wibble"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("alt.example.net").serverPort(80)
|
||||
.secure(false)
|
||||
.requestURL("http://alt.example.net/foo")
|
||||
.remoteAddr("0.0.0.0").remotePort(0)
|
||||
.sslSession("Wibble")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static Stream<Arguments> cases()
|
||||
{
|
||||
return Stream.of(
|
||||
|
@ -1032,7 +1034,6 @@ public class ForwardedRequestCustomizerTest
|
|||
request.configure(customizer);
|
||||
|
||||
String rawRequest = request.getRawRequest((header) -> header);
|
||||
// System.out.println(rawRequest);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(rawRequest));
|
||||
assertThat("status", response.getStatus(), is(200));
|
||||
|
@ -1052,7 +1053,6 @@ public class ForwardedRequestCustomizerTest
|
|||
.replaceFirst("X-Proxied-Https:", "Jetty-Proxied-Https:")
|
||||
.replaceFirst("Proxy-Ssl-Id:", "Jetty-Proxy-Ssl-Id:")
|
||||
.replaceFirst("Proxy-auth-cert:", "Jetty-Proxy-Auth-Cert:"));
|
||||
// System.out.println(rawRequest);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(connectorConfigured.getResponse(rawRequest));
|
||||
assertThat("status", response.getStatus(), is(200));
|
||||
|
@ -1063,6 +1063,40 @@ public class ForwardedRequestCustomizerTest
|
|||
public static Stream<Arguments> nonStandardPortCases()
|
||||
{
|
||||
return Stream.of(
|
||||
// HTTP 1.0
|
||||
Arguments.of(
|
||||
new TestRequest("HTTP/1.0 - no Host header")
|
||||
.headers(
|
||||
"GET /example HTTP/1.0"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("test").serverPort(42)
|
||||
.secure(false)
|
||||
.requestURL("http://test:42/example")
|
||||
),
|
||||
Arguments.of(
|
||||
new TestRequest("HTTP/1.0 - Empty Host header")
|
||||
.headers(
|
||||
"GET scheme:///example HTTP/1.0",
|
||||
"Host:"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("scheme").serverName(null).serverPort(42)
|
||||
.secure(false)
|
||||
.requestURL("scheme:///example")
|
||||
),
|
||||
Arguments.of(
|
||||
new TestRequest("HTTP/1.0 - Host header")
|
||||
.headers(
|
||||
"GET /example HTTP/1.0",
|
||||
"Host: server:9999"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("server").serverPort(9999)
|
||||
.secure(false)
|
||||
.requestURL("http://server:9999/example")
|
||||
),
|
||||
|
||||
// RFC7239 Tests with https.
|
||||
Arguments.of(new TestRequest("RFC7239 with https and h2")
|
||||
.headers(
|
||||
|
@ -1106,7 +1140,7 @@ public class ForwardedRequestCustomizerTest
|
|||
|
||||
/**
|
||||
* Tests against a Connector with a HttpConfiguration on non-standard ports.
|
||||
* HttpConfiguration is set to securePort of 8443
|
||||
* HttpConfiguration is set to securePort of 8443 and the local port is 42.
|
||||
*/
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("nonStandardPortCases")
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.nio.channels.SocketChannel;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.http.ByteRange;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -31,7 +30,6 @@ import org.eclipse.jetty.http.MultiPart;
|
|||
import org.eclipse.jetty.http.MultiPartByteRanges;
|
||||
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.io.content.ByteBufferContentSource;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
|
@ -49,13 +47,13 @@ public class MultiPartByteRangesTest
|
|||
{
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private LeakTrackingBufferPool byteBufferPool;
|
||||
private ArrayByteBufferPool.Tracking byteBufferPool;
|
||||
|
||||
private void start(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||
serverThreads.setName("server");
|
||||
byteBufferPool = new LeakTrackingBufferPool();
|
||||
byteBufferPool = new ArrayByteBufferPool.Tracking();
|
||||
server = new Server(serverThreads, null, byteBufferPool);
|
||||
connector = new ServerConnector(server, 1, 1);
|
||||
server.addConnector(connector);
|
||||
|
@ -67,7 +65,7 @@ public class MultiPartByteRangesTest
|
|||
public void dispose()
|
||||
{
|
||||
LifeCycle.stop(server);
|
||||
assertEquals(0, byteBufferPool.countLeaks());
|
||||
assertEquals(0, byteBufferPool.getLeaks().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -131,31 +129,4 @@ public class MultiPartByteRangesTest
|
|||
assertEquals("CDEF", Content.Source.asString(part3.getContentSource()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class LeakTrackingBufferPool extends ArrayByteBufferPool
|
||||
{
|
||||
private final AtomicInteger leaks = new AtomicInteger();
|
||||
|
||||
public int countLeaks()
|
||||
{
|
||||
return leaks.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetainableByteBuffer acquire(int size, boolean direct)
|
||||
{
|
||||
leaks.incrementAndGet();
|
||||
return new RetainableByteBuffer.Wrapper(super.acquire(size, direct))
|
||||
{
|
||||
@Override
|
||||
public boolean release()
|
||||
{
|
||||
boolean released = super.release();
|
||||
if (released)
|
||||
leaks.decrementAndGet();
|
||||
return released;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ public class BaseBuilder
|
|||
List<String> startLines = new ArrayList<>();
|
||||
for (Path path : paths)
|
||||
{
|
||||
StartLog.info("copy " + baseHome.toShortForm(path) + " into " + baseHome.toShortForm(startini));
|
||||
StartLog.info("copy %s into %s", baseHome.toShortForm(path), baseHome.toShortForm(startini));
|
||||
startLines.add("");
|
||||
startLines.add("# Config from " + baseHome.toShortForm(path));
|
||||
startLines.addAll(Files.readAllLines(path));
|
||||
|
@ -250,7 +250,7 @@ public class BaseBuilder
|
|||
|
||||
if (FS.ensureDirectoryExists(startd))
|
||||
{
|
||||
StartLog.info("mkdir " + baseHome.toShortForm(startd));
|
||||
StartLog.info("mkdir %s", baseHome.toShortForm(startd));
|
||||
modified.set(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,12 +23,9 @@ import java.nio.file.FileSystems;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
|
@ -130,7 +127,7 @@ public class FS
|
|||
if (!Files.isDirectory(path))
|
||||
{
|
||||
// not a directory (as expected)
|
||||
StartLog.warn("Not a directory: " + path);
|
||||
StartLog.warn("Not a directory: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,10 @@
|
|||
package org.eclipse.jetty.start;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -167,7 +163,7 @@ public abstract class FileInitializer
|
|||
{
|
||||
if (FS.ensureDirectoryExists(to))
|
||||
{
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(to));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(to));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -186,8 +186,8 @@ public class Main
|
|||
StartLog.error("Do not start with ${jetty.base} == ${jetty.home}!");
|
||||
else
|
||||
StartLog.error("No enabled jetty modules found!");
|
||||
StartLog.info("${jetty.home} = " + getBaseHome().getHomePath());
|
||||
StartLog.info("${jetty.base} = " + getBaseHome().getBasePath());
|
||||
StartLog.info("${jetty.home} = %s", getBaseHome().getHomePath());
|
||||
StartLog.info("${jetty.base} = %s", getBaseHome().getBasePath());
|
||||
StartLog.error("Please create and/or configure a ${jetty.base} directory.");
|
||||
usageExit(ERR_INVOKE_MAIN);
|
||||
return;
|
||||
|
@ -201,7 +201,7 @@ public class Main
|
|||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
StartLog.error("Unable to find: " + mainclass);
|
||||
StartLog.error("Unable to find: %s", mainclass);
|
||||
StartLog.debug(e);
|
||||
usageExit(ERR_INVOKE_MAIN);
|
||||
return;
|
||||
|
@ -489,7 +489,7 @@ public class Main
|
|||
final Process process = pbuilder.start();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() ->
|
||||
{
|
||||
StartLog.debug("Destroying " + process);
|
||||
StartLog.debug("Destroying %s", process);
|
||||
process.destroy();
|
||||
}));
|
||||
|
||||
|
@ -685,7 +685,7 @@ public class Main
|
|||
}
|
||||
else
|
||||
{
|
||||
StartLog.warn("Unable to find resource: " + resourceName);
|
||||
StartLog.warn("Unable to find resource: %s", resourceName);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
|
|
|
@ -42,7 +42,7 @@ public class PathFinder extends SimpleFileVisitor<Path>
|
|||
private void addHit(Path path)
|
||||
{
|
||||
String relPath = basePath.relativize(path).toString();
|
||||
StartLog.debug("Found [" + relPath + "] " + path);
|
||||
StartLog.debug("Found [%s] %s", relPath, path);
|
||||
hits.put(relPath, path);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class PathFinder extends SimpleFileVisitor<Path>
|
|||
{
|
||||
if (dirMatcher.matches(dir))
|
||||
{
|
||||
StartLog.trace("Following dir: " + dir);
|
||||
StartLog.trace("Following dir: %s", dir);
|
||||
if (includeDirsInResults && fileMatcher.matches(dir))
|
||||
{
|
||||
addHit(dir);
|
||||
|
@ -85,7 +85,7 @@ public class PathFinder extends SimpleFileVisitor<Path>
|
|||
}
|
||||
else
|
||||
{
|
||||
StartLog.trace("Skipping dir: " + dir);
|
||||
StartLog.trace("Skipping dir: %s", dir);
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ public class PathFinder extends SimpleFileVisitor<Path>
|
|||
}
|
||||
else
|
||||
{
|
||||
StartLog.trace("Ignoring file: " + file);
|
||||
StartLog.trace("Ignoring file: %s", file);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ public class PathFinder extends SimpleFileVisitor<Path>
|
|||
{
|
||||
if (!NOTIFIED_PATHS.contains(file))
|
||||
{
|
||||
StartLog.warn("skipping detected filesystem loop: " + file);
|
||||
StartLog.warn("skipping detected filesystem loop: %s", file);
|
||||
NOTIFIED_PATHS.add(file);
|
||||
}
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
|
|
|
@ -84,7 +84,7 @@ public class PathMatchers
|
|||
// use FileSystem default pattern behavior
|
||||
if (pattern.startsWith("glob:") || pattern.startsWith("regex:"))
|
||||
{
|
||||
StartLog.debug("Using Standard " + fs.getClass().getName() + " pattern: " + pattern);
|
||||
StartLog.debug("Using Standard %s pattern: %s", fs.getClass().getName(), pattern);
|
||||
return fs.getPathMatcher(pattern);
|
||||
}
|
||||
|
||||
|
@ -93,14 +93,14 @@ public class PathMatchers
|
|||
if (isAbsolute(pattern))
|
||||
{
|
||||
String pat = "glob:" + pattern;
|
||||
StartLog.debug("Using absolute path pattern: " + pat);
|
||||
StartLog.debug("Using absolute path pattern: %s", pat);
|
||||
return fs.getPathMatcher(pat);
|
||||
}
|
||||
|
||||
// Doesn't start with filesystem root, then assume the pattern
|
||||
// is a relative file path pattern.
|
||||
String pat = "glob:**/" + pattern;
|
||||
StartLog.debug("Using relative path pattern: " + pat);
|
||||
StartLog.debug("Using relative path pattern: %s", pat);
|
||||
return fs.getPathMatcher(pat);
|
||||
}
|
||||
|
||||
|
|
|
@ -428,9 +428,9 @@ public class StartArgs
|
|||
for (String rawlibref : module.getLibs())
|
||||
{
|
||||
|
||||
StartLog.debug("rawlibref = " + rawlibref);
|
||||
StartLog.debug("rawlibref = %s", rawlibref);
|
||||
String libref = environment.getProperties().expand(rawlibref);
|
||||
StartLog.debug("expanded = " + libref);
|
||||
StartLog.debug("expanded = %s", libref);
|
||||
|
||||
for (Path libpath : baseHome.getPaths(libref))
|
||||
{
|
||||
|
@ -555,6 +555,7 @@ public class StartArgs
|
|||
if (parts.contains("path"))
|
||||
{
|
||||
Classpath classpath = jettyEnvironment.getClasspath();
|
||||
StartLog.debug("classpath=%s - isJPMS=%b", classpath, isJPMS());
|
||||
if (isJPMS())
|
||||
{
|
||||
Map<Boolean, List<Path>> dirsAndFiles = StreamSupport.stream(classpath.spliterator(), false)
|
||||
|
@ -605,6 +606,7 @@ public class StartArgs
|
|||
}
|
||||
|
||||
generateJpmsArgs(cmd);
|
||||
StartLog.debug("JPMS resulting cmd=%s", cmd.toCommandLine());
|
||||
}
|
||||
else if (!classpath.isEmpty())
|
||||
{
|
||||
|
@ -1255,7 +1257,7 @@ public class StartArgs
|
|||
if (arg.startsWith("--add-to-start=") || arg.startsWith("--add-to-startd="))
|
||||
{
|
||||
String value = Props.getValue(arg);
|
||||
StartLog.warn("Option " + arg.split("=")[0] + " is deprecated! Instead use: --add-modules=%s", value);
|
||||
StartLog.warn("Option %s is deprecated! Instead use: --add-modules=%s", arg.split("=")[0], value);
|
||||
}
|
||||
startModules.addAll(Props.getValues(arg));
|
||||
run = false;
|
||||
|
|
|
@ -231,9 +231,9 @@ public class StartEnvironment
|
|||
StartLog.debug("Expanding Libs");
|
||||
for (String rawlibref : _libRefs)
|
||||
{
|
||||
StartLog.debug("rawlibref = " + rawlibref);
|
||||
StartLog.debug("rawlibref = %s", rawlibref);
|
||||
String libref = getProperties().expand(rawlibref);
|
||||
StartLog.debug("expanded = " + libref);
|
||||
StartLog.debug("expanded = %s", libref);
|
||||
|
||||
// perform path escaping (needed by windows)
|
||||
libref = libref.replaceAll("\\\\([^\\\\])", "\\\\\\\\$1");
|
||||
|
|
|
@ -42,7 +42,7 @@ public class StartDirBuilder implements BaseBuilder.Config
|
|||
this.baseHome = baseBuilder.getBaseHome();
|
||||
this.startDir = baseHome.getBasePath("start.d");
|
||||
if (FS.ensureDirectoryExists(startDir))
|
||||
StartLog.info("mkdir " + baseHome.toShortForm(startDir));
|
||||
StartLog.info("mkdir %s", baseHome.toShortForm(startDir));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -64,7 +64,7 @@ public class BaseHomeFileInitializer extends FileInitializer
|
|||
else if (FS.ensureDirectoryExists(destination))
|
||||
{
|
||||
modified = true;
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(destination));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(destination));
|
||||
}
|
||||
|
||||
copyDirectory(source, destination);
|
||||
|
@ -74,7 +74,7 @@ public class BaseHomeFileInitializer extends FileInitializer
|
|||
if (FS.ensureDirectoryExists(destination.getParent()))
|
||||
{
|
||||
modified = true;
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(destination.getParent()));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(destination.getParent()));
|
||||
}
|
||||
|
||||
if (!FS.exists(destination))
|
||||
|
|
|
@ -61,7 +61,7 @@ public abstract class DownloadFileInitializer extends FileInitializer
|
|||
}
|
||||
|
||||
if (FS.ensureDirectoryExists(destination.getParent()))
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(destination.getParent()));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(destination.getParent()));
|
||||
|
||||
StartLog.info("download %s to %s", uri, _basehome.toShortForm(destination));
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ public class LocalFileInitializer extends FileInitializer
|
|||
// Create directory
|
||||
boolean mkdir = FS.ensureDirectoryExists(destination);
|
||||
if (mkdir)
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(destination));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(destination));
|
||||
return mkdir;
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ public class MavenLocalRepoFileInitializer extends DownloadFileInitializer
|
|||
if (!FS.canReadFile(localFile))
|
||||
{
|
||||
if (FS.ensureDirectoryExists(localFile.getParent()))
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(localFile.getParent()));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(localFile.getParent()));
|
||||
download(coords, localFile);
|
||||
if (!FS.canReadFile(localFile))
|
||||
{
|
||||
|
@ -209,7 +209,7 @@ public class MavenLocalRepoFileInitializer extends DownloadFileInitializer
|
|||
if (localRepoFile != null)
|
||||
{
|
||||
if (FS.ensureDirectoryExists(destination.getParent()))
|
||||
StartLog.info("mkdir " + _basehome.toShortForm(destination.getParent()));
|
||||
StartLog.info("mkdir %s", _basehome.toShortForm(destination.getParent()));
|
||||
StartLog.info("copy %s to %s", localRepoFile, _basehome.toShortForm(destination));
|
||||
Files.copy(localRepoFile, destination);
|
||||
return true;
|
||||
|
|
|
@ -59,11 +59,16 @@ import org.eclipse.jetty.util.component.LifeCycle;
|
|||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class AbstractTest
|
||||
{
|
||||
@RegisterExtension
|
||||
public final BeforeTestExecutionCallback printMethodName = context ->
|
||||
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
|
||||
protected final HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
protected SslContextFactory.Server sslContextFactoryServer;
|
||||
protected Server server;
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.client.Connection;
|
|||
import org.eclipse.jetty.client.Destination;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.client.transport.HttpExchange;
|
||||
import org.eclipse.jetty.client.transport.internal.HttpChannelOverHTTP;
|
||||
|
@ -209,9 +208,9 @@ public class HttpChannelAssociationTest extends AbstractTest
|
|||
return new HttpConnectionOverFCGI(endPoint, destination, promise)
|
||||
{
|
||||
@Override
|
||||
protected HttpChannelOverFCGI newHttpChannel(Request request)
|
||||
protected HttpChannelOverFCGI newHttpChannel()
|
||||
{
|
||||
return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout())
|
||||
return new HttpChannelOverFCGI(this)
|
||||
{
|
||||
@Override
|
||||
public boolean associate(HttpExchange exchange)
|
||||
|
|
|
@ -478,8 +478,7 @@ public interface Callback extends Invocable
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(x, t))
|
||||
x.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(x, t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* <p>A {@link CompletableFuture} that implements {@link Runnable} to perform
|
||||
* a one-shot task that eventually completes this {@link CompletableFuture}.</p>
|
||||
* <p>Subclasses override {@link #run()} to implement the task.</p>
|
||||
* <p>Users of this class start the task execution via {@link #start()}.</p>
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>{@code
|
||||
* CompletableTask<T> task = new CompletableTask<>()
|
||||
* {
|
||||
* @Override
|
||||
* public void run()
|
||||
* {
|
||||
* try
|
||||
* {
|
||||
* // Perform some task.
|
||||
* T result = performTask();
|
||||
*
|
||||
* // Eventually complete this CompletableFuture.
|
||||
* complete(result);
|
||||
* }
|
||||
* catch (Throwable x)
|
||||
* {
|
||||
* completeExceptionally(x);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Start the task and then process the
|
||||
* // result of the task when it is complete.
|
||||
* task.start()
|
||||
* .whenComplete((result, failure) ->
|
||||
* {
|
||||
* if (failure == null)
|
||||
* {
|
||||
* // The task completed successfully.
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* // The task failed.
|
||||
* }
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of the result of the task
|
||||
*/
|
||||
public abstract class CompletableTask<T> extends CompletableFuture<T> implements Runnable
|
||||
{
|
||||
/**
|
||||
* <p>Starts the task by calling {@link #run()}
|
||||
* and returns this {@link CompletableTask}.</p>
|
||||
*
|
||||
* @return this {@link CompletableTask}
|
||||
*/
|
||||
public CompletableTask<T> start()
|
||||
{
|
||||
run();
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.eclipse.jetty.util.thread.Invocable;
|
|||
*/
|
||||
public class ExceptionUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* <p>Convert a {@link Throwable} to a specific type by casting or construction on a new instance.</p>
|
||||
*
|
||||
|
@ -178,6 +179,18 @@ public class ExceptionUtil
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suppressed exception if it is not associated.
|
||||
* @see #areNotAssociated(Throwable, Throwable)
|
||||
* @param throwable The main Throwable
|
||||
* @param suppressed The Throwable to suppress if it is not associated.
|
||||
*/
|
||||
public static void addSuppressedIfNotAssociated(Throwable throwable, Throwable suppressed)
|
||||
{
|
||||
if (areNotAssociated(throwable, suppressed))
|
||||
throwable.addSuppressed(suppressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate a Throwable with the suppressed errors and return it.
|
||||
* @param t the throwable
|
||||
|
|
|
@ -1836,8 +1836,7 @@ public final class URIUtil
|
|||
Objects.requireNonNull(resource);
|
||||
|
||||
// Only try URI for string for known schemes, otherwise assume it is a Path
|
||||
ResourceFactory resourceFactory = ResourceFactory.getBestByScheme(resource);
|
||||
return (resourceFactory != null)
|
||||
return (ResourceFactory.isSupported(resource))
|
||||
? correctFileURI(URI.create(resource))
|
||||
: Paths.get(resource).toUri();
|
||||
}
|
||||
|
|
|
@ -27,7 +27,89 @@ import org.eclipse.jetty.util.component.Container;
|
|||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
|
||||
/**
|
||||
* ResourceFactory.
|
||||
* <p>ResourceFactory is the source of new {@link Resource} instances.</p>
|
||||
*
|
||||
* <p>
|
||||
* Some {@link Resource} objects have an internal allocation / release model,
|
||||
* that the {@link ResourceFactory} is responsible for.
|
||||
* Once a {@link ResourceFactory} is stopped, the {@link Resource}
|
||||
* objects created from that {@link ResourceFactory} are released.
|
||||
* </p>
|
||||
*
|
||||
* <h2>A {@link ResourceFactory.LifeCycle} tied to a Jetty {@link org.eclipse.jetty.util.component.Container}</h2>
|
||||
* <pre>
|
||||
* ResourceFactory.LifeCycle resourceFactory = ResourceFactory.of(container);
|
||||
* Resource resource = resourceFactory.newResource(ref);
|
||||
* </pre>
|
||||
* <p>
|
||||
* The use of {@link ResourceFactory#of(Container)} results in a {@link ResourceFactory.LifeCycle} that is tied
|
||||
* to a specific Jetty {@link org.eclipse.jetty.util.component.Container} such as a {@code Server},
|
||||
* {@code ServletContextHandler}, or {@code WebAppContext}. This will free the {@code Resource}
|
||||
* instances created by the {@link org.eclipse.jetty.util.resource.ResourceFactory} once
|
||||
* the {@code container} that manages it is stopped.
|
||||
* </p>
|
||||
*
|
||||
* <h2>A {@link ResourceFactory.Closeable} that exists within a {@code try-with-resources} call</h2>
|
||||
* <pre>
|
||||
* try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable()) {
|
||||
* Resource resource = resourceFactory.newResource(ref);
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* The use of {@link ResourceFactory#closeable()} results in a {@link ResourceFactory} that only exists for
|
||||
* the duration of the {@code try-with-resources} code block, once this {@code try-with-resources} is closed,
|
||||
* all {@link Resource} objects associated with that {@link ResourceFactory} are freed as well.
|
||||
* </p>
|
||||
*
|
||||
* <h2>A {@code ResourceFactory} that lives at the JVM level</h2>
|
||||
* <pre>
|
||||
* ResourceFactory resourceFactory = ResourceFactory.root();
|
||||
* Resource resource = resourceFactory.newResource(ref);
|
||||
* </pre>
|
||||
* <p>
|
||||
* The use of {@link ResourceFactory#root()} results in a {@link ResourceFactory} that exists for
|
||||
* the life of the JVM, and the resources allocated via this {@link ResourceFactory} will not
|
||||
* be freed until the JVM exits.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Supported URI Schemes</h2>
|
||||
* <p>
|
||||
* By default, the following schemes are supported by Jetty.
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt>file</dt>
|
||||
* <dd>The standard Java {@code file:/path/to/dir/} syntax</dd>
|
||||
*
|
||||
* <dt>jar</dt>
|
||||
* <dd>The standard Java {@code jar:file:/path/to/file.jar!/} syntax</dd>
|
||||
*
|
||||
* <dt>jrt</dt>
|
||||
* <dd>The standard Java {@code jrt:module-name} syntax</dd>
|
||||
* </dl>
|
||||
* <p>
|
||||
* Special Note: An effort is made to discover any new schemes that
|
||||
* might be present at JVM startup (eg: graalvm and {@code resource:} scheme).
|
||||
* At startup Jetty will access an internal Jetty resource (found in
|
||||
* the jetty-util jar) and seeing what {@code scheme} it is using to access
|
||||
* it, and will register it with a call to
|
||||
* {@link ResourceFactory#registerResourceFactory(String, ResourceFactory)}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Supporting more Schemes</h2>
|
||||
* <p>
|
||||
* You can register a new URI scheme to a {@link ResourceFactory} implementation
|
||||
* using the {@link ResourceFactory#registerResourceFactory(String, ResourceFactory)}
|
||||
* method, which will cause all new uses of ResourceFactory to use this newly
|
||||
* registered scheme.
|
||||
* </p>
|
||||
* <pre>
|
||||
* URLResourceFactory urlResourceFactory = new URLResourceFactory();
|
||||
* urlResourceFactory.setConnectTimeout(1000);
|
||||
* ResourceFactory.registerResourceFactory("https", urlResourceFactory);
|
||||
*
|
||||
* URI web = URI.create("https://eclipse.dev/jetty/");
|
||||
* Resource resource = ResourceFactory.root().newResource(web);
|
||||
* </pre>
|
||||
*/
|
||||
public interface ResourceFactory
|
||||
{
|
||||
|
@ -39,7 +121,6 @@ public interface ResourceFactory
|
|||
* or null if none are passed.
|
||||
* The returned {@link Resource} will always return {@code true} from {@link Resource#isDirectory()}
|
||||
* @throws IllegalArgumentException if a non-directory resource is passed.
|
||||
* @see CombinedResource
|
||||
*/
|
||||
static Resource combine(List<Resource> resources)
|
||||
{
|
||||
|
@ -54,7 +135,6 @@ public interface ResourceFactory
|
|||
* or null if none are passed.
|
||||
* The returned {@link Resource} will always return {@code true} from {@link Resource#isDirectory()}
|
||||
* @throws IllegalArgumentException if a non-directory resource is passed.
|
||||
* @see CombinedResource
|
||||
*/
|
||||
static Resource combine(Resource... resources)
|
||||
{
|
||||
|
@ -65,17 +145,21 @@ public interface ResourceFactory
|
|||
* Construct a resource from a uri.
|
||||
*
|
||||
* @param uri A URI.
|
||||
* @return A Resource object.
|
||||
* @return A Resource object, or null if uri points to a location that does not exist.
|
||||
*/
|
||||
Resource newResource(URI uri);
|
||||
|
||||
/**
|
||||
* Construct a system resource from a string.
|
||||
* The resource is tried as classloader resource before being
|
||||
* treated as a normal resource.
|
||||
* <p>Construct a system resource from a string.</p>
|
||||
*
|
||||
* <p>
|
||||
* The resource is first attempted to be accessed via the {@link Thread#getContextClassLoader()}
|
||||
* before being treated as a normal resource.
|
||||
* </p>
|
||||
*
|
||||
* @param resource Resource as string representation
|
||||
* @return The new Resource
|
||||
* @return The new Resource, or null if string points to a location that does not exist
|
||||
* @throws IllegalArgumentException if string is blank
|
||||
*/
|
||||
default Resource newSystemResource(String resource)
|
||||
{
|
||||
|
@ -134,12 +218,16 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Find a classpath resource.
|
||||
* <p>Find a classpath resource.</p>
|
||||
*
|
||||
* <p>
|
||||
* The {@link Class#getResource(String)} method is used to lookup the resource. If it is not
|
||||
* found, then the {@link Loader#getResource(String)} method is used.
|
||||
* </p>
|
||||
*
|
||||
* @param resource the relative name of the resource
|
||||
* @return Resource or null
|
||||
* @return Resource, or null if string points to a location that does not exist
|
||||
* @throws IllegalArgumentException if string is blank
|
||||
*/
|
||||
default Resource newClassPathResource(String resource)
|
||||
{
|
||||
|
@ -164,9 +252,20 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Load a URL into a memory resource.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A Memory Resource is created from a the contents of
|
||||
* {@link URL#openStream()} and kept in memory from
|
||||
* that point forward. Never accessing the URL
|
||||
* again to refresh it's contents.
|
||||
* </p>
|
||||
*
|
||||
* @param url the URL to load into memory
|
||||
* @return Resource or null
|
||||
* @return Resource, or null if url points to a location that does not exist
|
||||
* @throws IllegalArgumentException if URL is null
|
||||
* @see #newClassPathResource(String)
|
||||
*/
|
||||
default Resource newMemoryResource(URL url)
|
||||
|
@ -181,7 +280,9 @@ public interface ResourceFactory
|
|||
* Construct a resource from a string.
|
||||
*
|
||||
* @param resource A URL or filename.
|
||||
* @return A Resource object.
|
||||
* @return A Resource object, or null if the string points to a location
|
||||
* that does not exist
|
||||
* @throws IllegalArgumentException if resource is invalid
|
||||
*/
|
||||
default Resource newResource(String resource)
|
||||
{
|
||||
|
@ -192,10 +293,12 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct a Resource from provided path
|
||||
* Construct a Resource from provided path.
|
||||
*
|
||||
* @param path the path
|
||||
* @return the Resource for the provided path
|
||||
* @return the Resource for the provided path, or null if the path
|
||||
* does not exist
|
||||
* @throws IllegalArgumentException if path is null
|
||||
*/
|
||||
default Resource newResource(Path path)
|
||||
{
|
||||
|
@ -206,10 +309,12 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct a possible {@link CombinedResource} from a list of URIs
|
||||
* Construct a possible combined {@code Resource} from a list of URIs.
|
||||
*
|
||||
* @param uris the URIs
|
||||
* @return the Resource for the provided path
|
||||
* @return the Resource for the provided URIs, or null if all
|
||||
* of the provided URIs do not exist
|
||||
* @throws IllegalArgumentException if list of URIs is empty or null
|
||||
*/
|
||||
default Resource newResource(List<URI> uris)
|
||||
{
|
||||
|
@ -220,10 +325,12 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link Resource} from a provided URL
|
||||
* Construct a {@link Resource} from a provided URL.
|
||||
*
|
||||
* @param url the URL
|
||||
* @return the Resource for the provided URL
|
||||
* @return the Resource for the provided URL, or null if the
|
||||
* url points to a location that does not exist
|
||||
* @throws IllegalArgumentException if url is null
|
||||
*/
|
||||
default Resource newResource(URL url)
|
||||
{
|
||||
|
@ -241,10 +348,12 @@ public interface ResourceFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link Resource} from a {@code file:} based URI that is mountable (eg: a jar file)
|
||||
* Construct a {@link Resource} from a {@code file:} based URI that is mountable (eg: a jar file).
|
||||
*
|
||||
* @param uri the URI
|
||||
* @return the Resource, mounted as a {@link java.nio.file.FileSystem}
|
||||
* @return the Resource, mounted as a {@link java.nio.file.FileSystem}, or null if
|
||||
* the uri points to a location that does not exist.
|
||||
* @throws IllegalArgumentException if provided URI is not "file" scheme.
|
||||
*/
|
||||
default Resource newJarFileResource(URI uri)
|
||||
{
|
||||
|
@ -253,6 +362,28 @@ public interface ResourceFactory
|
|||
return newResource(URIUtil.toJarFileUri(uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if provided string is supported.
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if it is supported
|
||||
*/
|
||||
static boolean isSupported(String str)
|
||||
{
|
||||
return ResourceFactoryInternals.isSupported(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if provided uri is supported.
|
||||
*
|
||||
* @param uri the uri to test
|
||||
* @return true if it is supported
|
||||
*/
|
||||
static boolean isSupported(URI uri)
|
||||
{
|
||||
return ResourceFactoryInternals.isSupported(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new ResourceFactory that can handle the specific scheme for the Resource API.
|
||||
*
|
||||
|
@ -262,12 +393,14 @@ public interface ResourceFactory
|
|||
*
|
||||
* @param scheme the scheme to support (eg: `ftp`, `http`, `resource`, etc)
|
||||
* @param resourceFactory the ResourceFactory to be responsible for the registered scheme.
|
||||
* @throws IllegalArgumentException if scheme is blank
|
||||
* @see #unregisterResourceFactory(String)
|
||||
* @see #byScheme(String)
|
||||
* @see #getBestByScheme(String)
|
||||
*/
|
||||
static void registerResourceFactory(String scheme, ResourceFactory resourceFactory)
|
||||
{
|
||||
if (StringUtil.isBlank(scheme))
|
||||
throw new IllegalArgumentException("Scheme is blank");
|
||||
|
||||
ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, resourceFactory);
|
||||
}
|
||||
|
||||
|
@ -276,53 +409,16 @@ public interface ResourceFactory
|
|||
*
|
||||
* @param scheme the scheme to unregister
|
||||
* @return the existing {@link ResourceFactory} that was registered to that scheme.
|
||||
* @throws IllegalArgumentException if scheme is blank
|
||||
* @see #registerResourceFactory(String, ResourceFactory)
|
||||
* @see #byScheme(String)
|
||||
* @see #getBestByScheme(String)
|
||||
*/
|
||||
static ResourceFactory unregisterResourceFactory(String scheme)
|
||||
{
|
||||
if (StringUtil.isBlank(scheme))
|
||||
throw new IllegalArgumentException("Scheme is blank");
|
||||
return ResourceFactoryInternals.RESOURCE_FACTORIES.remove(scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceFactory} that is registered for the specific scheme.
|
||||
*
|
||||
* <pre>{@code
|
||||
* .byScheme("jar") == ResourceFactory supporting jar
|
||||
* .byScheme("jar:file://foo.jar!/") == null // full url strings not supported)
|
||||
* }</pre>
|
||||
*
|
||||
* @param scheme the scheme to look up
|
||||
* @return the {@link ResourceFactory} responsible for the scheme, null if no {@link ResourceFactory} handles the scheme.
|
||||
* @see #registerResourceFactory(String, ResourceFactory)
|
||||
* @see #unregisterResourceFactory(String)
|
||||
* @see #getBestByScheme(String)
|
||||
*/
|
||||
static ResourceFactory byScheme(String scheme)
|
||||
{
|
||||
return ResourceFactoryInternals.RESOURCE_FACTORIES.get(scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best ResourceFactory for the provided scheme.
|
||||
*
|
||||
* <p>
|
||||
* Unlike {@link #byScheme(String)}, this supports arbitrary Strings, that might start with a supported scheme.
|
||||
* </p>
|
||||
*
|
||||
* @param scheme the scheme to look up
|
||||
* @return the ResourceFactory that best fits the provided scheme.
|
||||
* @see org.eclipse.jetty.util.Index#getBest(String)
|
||||
* @see #registerResourceFactory(String, ResourceFactory)
|
||||
* @see #unregisterResourceFactory(String)
|
||||
* @see #byScheme(String)
|
||||
*/
|
||||
static ResourceFactory getBestByScheme(String scheme)
|
||||
{
|
||||
return ResourceFactoryInternals.RESOURCE_FACTORIES.getBest(scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* The JVM wide (root) ResourceFactory.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Index;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
|
@ -93,6 +94,39 @@ class ResourceFactoryInternals
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test uri to know if a {@link ResourceFactory} is registered for it.
|
||||
*
|
||||
* @param uri the uri to test
|
||||
* @return true if a ResourceFactory is registered to support the uri
|
||||
* @see ResourceFactory#registerResourceFactory(String, ResourceFactory)
|
||||
* @see ResourceFactory#unregisterResourceFactory(String)
|
||||
* @see #isSupported(String)
|
||||
*/
|
||||
static boolean isSupported(URI uri) // TODO: boolean isSupported
|
||||
{
|
||||
if (uri == null || uri.getScheme() == null)
|
||||
return false;
|
||||
return RESOURCE_FACTORIES.get(uri.getScheme()) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test string to know if a {@link ResourceFactory} is registered for it.
|
||||
*
|
||||
* @param str the string representing the resource location
|
||||
* @return true if a ResourceFactory is registered to support the string representation
|
||||
* @see org.eclipse.jetty.util.Index#getBest(String)
|
||||
* @see ResourceFactory#registerResourceFactory(String, ResourceFactory)
|
||||
* @see ResourceFactory#unregisterResourceFactory(String)
|
||||
* @see #isSupported(URI)
|
||||
*/
|
||||
static boolean isSupported(String str)
|
||||
{
|
||||
if (StringUtil.isBlank(str))
|
||||
return false;
|
||||
return RESOURCE_FACTORIES.getBest(str) != null;
|
||||
}
|
||||
|
||||
static class Closeable implements ResourceFactory.Closeable
|
||||
{
|
||||
private final CompositeResourceFactory _compositeResourceFactory = new CompositeResourceFactory();
|
||||
|
@ -168,7 +202,7 @@ class ResourceFactoryInternals
|
|||
uri = URIUtil.correctFileURI(uri);
|
||||
}
|
||||
|
||||
ResourceFactory resourceFactory = ResourceFactory.byScheme(uri.getScheme());
|
||||
ResourceFactory resourceFactory = RESOURCE_FACTORIES.get(uri.getScheme());
|
||||
if (resourceFactory == null)
|
||||
throw new IllegalArgumentException("URI scheme not supported: " + uri);
|
||||
if (resourceFactory instanceof MountedPathResourceFactory)
|
||||
|
|
|
@ -12,7 +12,17 @@
|
|||
//
|
||||
|
||||
/**
|
||||
* Jetty Util : Common Resource Utilities
|
||||
* <p>Jetty Util : Resource Utilities</p>
|
||||
*
|
||||
* <p>
|
||||
* A {@link org.eclipse.jetty.util.resource.Resource} in Jetty is an abstraction that
|
||||
* allows for a common API to access various forms of resource sources across the Jetty
|
||||
* ecosystem.
|
||||
* </p>
|
||||
* <p>
|
||||
* A {@link org.eclipse.jetty.util.resource.Resource} is created via one of the
|
||||
* {@link org.eclipse.jetty.util.resource.ResourceFactory}{@code .newResource(...)} APIs.
|
||||
* </p>
|
||||
*/
|
||||
package org.eclipse.jetty.util.resource;
|
||||
|
||||
|
|
|
@ -149,11 +149,6 @@ public class AsyncContextState implements AsyncContext
|
|||
_state = null;
|
||||
}
|
||||
|
||||
public ServletChannelState getServletChannelState()
|
||||
{
|
||||
return state();
|
||||
}
|
||||
|
||||
public static class WrappedAsyncListener implements AsyncListener
|
||||
{
|
||||
private final AsyncListener _listener;
|
||||
|
|
|
@ -15,26 +15,17 @@ package org.eclipse.jetty.ee10.servlet;
|
|||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
@ -43,16 +34,12 @@ import jakarta.servlet.UnavailableException;
|
|||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
import org.eclipse.jetty.http.CompressedContentFormat;
|
||||
import org.eclipse.jetty.http.HttpException;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.content.FileMappingHttpContentFactory;
|
||||
import org.eclipse.jetty.http.content.HttpContent;
|
||||
|
@ -60,7 +47,6 @@ import org.eclipse.jetty.http.content.PreCompressedHttpContentFactory;
|
|||
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
|
||||
import org.eclipse.jetty.http.content.ValidatingCachingHttpContentFactory;
|
||||
import org.eclipse.jetty.http.content.VirtualHttpContentFactory;
|
||||
import org.eclipse.jetty.io.ByteBufferInputStream;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -69,9 +55,8 @@ import org.eclipse.jetty.server.Response;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
@ -79,6 +64,8 @@ import org.eclipse.jetty.util.resource.Resources;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.eclipse.jetty.util.URIUtil.encodePath;
|
||||
|
||||
/**
|
||||
* <p>The default Servlet, normally mapped to {@code /}, that handles static resources.</p>
|
||||
* <p>The following init parameters are supported:</p>
|
||||
|
@ -185,7 +172,6 @@ public class DefaultServlet extends HttpServlet
|
|||
private ServletContextHandler _contextHandler;
|
||||
private ServletResourceService _resourceService;
|
||||
private WelcomeServletMode _welcomeServletMode;
|
||||
private Resource _baseResource;
|
||||
|
||||
public ResourceService getResourceService()
|
||||
{
|
||||
|
@ -198,14 +184,14 @@ public class DefaultServlet extends HttpServlet
|
|||
_contextHandler = initContextHandler(getServletContext());
|
||||
_resourceService = new ServletResourceService(_contextHandler);
|
||||
_resourceService.setWelcomeFactory(_resourceService);
|
||||
_baseResource = _contextHandler.getBaseResource();
|
||||
Resource baseResource = _contextHandler.getBaseResource();
|
||||
|
||||
String rb = getInitParameter("baseResource", "resourceBase");
|
||||
if (rb != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_baseResource = Objects.requireNonNull(_contextHandler.newResource(rb));
|
||||
baseResource = Objects.requireNonNull(_contextHandler.newResource(rb));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -222,7 +208,7 @@ public class DefaultServlet extends HttpServlet
|
|||
if (contentFactory == null)
|
||||
{
|
||||
MimeTypes mimeTypes = _contextHandler.getMimeTypes();
|
||||
ResourceFactory resourceFactory = _baseResource != null ? ResourceFactory.of(_baseResource) : this::getResource;
|
||||
ResourceFactory resourceFactory = baseResource != null ? ResourceFactory.of(baseResource) : this::getResource;
|
||||
contentFactory = new ResourceHttpContentFactory(resourceFactory, mimeTypes);
|
||||
|
||||
// Use the servers default stylesheet unless there is one explicitly set by an init param.
|
||||
|
@ -326,7 +312,7 @@ public class DefaultServlet extends HttpServlet
|
|||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug(" .baseResource = {}", _baseResource);
|
||||
LOG.debug(" .baseResource = {}", baseResource);
|
||||
LOG.debug(" .resourceService = {}", _resourceService);
|
||||
LOG.debug(" .welcomeServletMode = {}", _welcomeServletMode);
|
||||
}
|
||||
|
@ -458,18 +444,18 @@ public class DefaultServlet extends HttpServlet
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
|
||||
{
|
||||
String includedServletPath = (String)req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
String encodedPathInContext = getEncodedPathInContext(req, includedServletPath);
|
||||
String includedServletPath = (String)httpServletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
String encodedPathInContext = getEncodedPathInContext(httpServletRequest, includedServletPath);
|
||||
boolean included = includedServletPath != null;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("doGet(req={}, resp={}) pathInContext={}, included={}", req, resp, encodedPathInContext, included);
|
||||
LOG.debug("doGet(hsReq={}, hsResp={}) pathInContext={}, included={}", httpServletRequest, httpServletResponse, encodedPathInContext, included);
|
||||
|
||||
try
|
||||
{
|
||||
HttpContent content = _resourceService.getContent(encodedPathInContext, ServletContextRequest.getServletContextRequest(req));
|
||||
HttpContent content = _resourceService.getContent(encodedPathInContext, ServletContextRequest.getServletContextRequest(httpServletRequest));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("content = {}", content);
|
||||
|
||||
|
@ -487,13 +473,31 @@ public class DefaultServlet extends HttpServlet
|
|||
}
|
||||
|
||||
// no content
|
||||
resp.sendError(404);
|
||||
httpServletResponse.sendError(404);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServletCoreRequest coreRequest = new ServletCoreRequest(req);
|
||||
ServletCoreResponse coreResponse = new ServletCoreResponse(coreRequest, resp, included);
|
||||
// lookup the core request and response as wrapped by the ServletContextHandler
|
||||
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(httpServletRequest);
|
||||
ServletContextResponse servletContextResponse = servletContextRequest.getServletContextResponse();
|
||||
ServletChannel servletChannel = servletContextRequest.getServletChannel();
|
||||
|
||||
// If the servlet request has not been wrapped,
|
||||
// we can use the core request directly,
|
||||
// otherwise wrap the servlet request as a core request
|
||||
Request coreRequest = httpServletRequest instanceof ServletApiRequest
|
||||
? servletChannel.getRequest()
|
||||
: new ServletCoreRequest(httpServletRequest);
|
||||
|
||||
// If the servlet response has been wrapped and has been written to,
|
||||
// then the servlet response must be wrapped as a core response
|
||||
// otherwise we can use the core response directly.
|
||||
boolean useServletResponse = !(httpServletResponse instanceof ServletApiResponse) || servletContextResponse.isWritingOrStreaming();
|
||||
Response coreResponse = useServletResponse
|
||||
? new ServletCoreResponse(coreRequest, httpServletResponse, included)
|
||||
: servletChannel.getResponse();
|
||||
|
||||
// If the core response is already committed then do nothing more
|
||||
if (coreResponse.isCommitted())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -501,22 +505,30 @@ public class DefaultServlet extends HttpServlet
|
|||
return;
|
||||
}
|
||||
|
||||
// Servlet Filters could be interacting with the Response already.
|
||||
if (coreResponse.isHttpServletResponseWrapped() ||
|
||||
coreResponse.isWritingOrStreaming())
|
||||
{
|
||||
content = new UnknownLengthHttpContent(content);
|
||||
}
|
||||
// Get the content length before we may wrap the content
|
||||
long contentLength = content.getContentLengthValue();
|
||||
|
||||
ServletContextResponse contextResponse = coreResponse.getServletContextResponse();
|
||||
if (contextResponse != null)
|
||||
{
|
||||
String characterEncoding = contextResponse.getRawCharacterEncoding();
|
||||
// Servlet Filters could be interacting with the Response already.
|
||||
if (useServletResponse)
|
||||
content = new UnknownLengthHttpContent(content);
|
||||
|
||||
// The character encoding may be forced
|
||||
String characterEncoding = servletContextResponse.getRawCharacterEncoding();
|
||||
if (characterEncoding != null)
|
||||
content = new ForcedCharacterEncodingHttpContent(content, characterEncoding);
|
||||
}
|
||||
|
||||
// serve content
|
||||
// If async is supported and the unwrapped content is larger than an output buffer
|
||||
if (httpServletRequest.isAsyncSupported() &&
|
||||
(contentLength < 0 || contentLength > coreRequest.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize()))
|
||||
{
|
||||
// send the content asynchronously
|
||||
AsyncContext asyncContext = httpServletRequest.startAsync();
|
||||
Callback callback = new AsyncContextCallback(asyncContext, httpServletResponse);
|
||||
_resourceService.doGet(coreRequest, coreResponse, callback, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// send the content blocking
|
||||
try (Blocker.Callback callback = Blocker.callback())
|
||||
{
|
||||
_resourceService.doGet(coreRequest, coreResponse, callback, content);
|
||||
|
@ -528,22 +540,23 @@ public class DefaultServlet extends HttpServlet
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("InvalidPathException for pathInContext: {}", encodedPathInContext, e);
|
||||
if (included)
|
||||
throw new FileNotFoundException(encodedPathInContext);
|
||||
resp.setStatus(404);
|
||||
httpServletResponse.setStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getEncodedPathInContext(HttpServletRequest req, String includedServletPath)
|
||||
{
|
||||
if (includedServletPath != null)
|
||||
return URIUtil.encodePath(getIncludedPathInContext(req, includedServletPath, !isDefaultMapping(req)));
|
||||
return encodePath(getIncludedPathInContext(req, includedServletPath, !isDefaultMapping(req)));
|
||||
else if (!isDefaultMapping(req))
|
||||
return URIUtil.encodePath(req.getPathInfo());
|
||||
return encodePath(req.getPathInfo());
|
||||
else if (req instanceof ServletApiRequest apiRequest)
|
||||
return Context.getPathInContext(req.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath());
|
||||
else
|
||||
|
@ -589,476 +602,6 @@ public class DefaultServlet extends HttpServlet
|
|||
return result;
|
||||
}
|
||||
|
||||
private static class ServletCoreRequest extends Request.Wrapper
|
||||
{
|
||||
// TODO fully implement this class and move it to the top level
|
||||
// TODO Some methods are directed to core that probably should be intercepted
|
||||
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private final HttpFields _httpFields;
|
||||
private final HttpURI _uri;
|
||||
|
||||
ServletCoreRequest(HttpServletRequest request)
|
||||
{
|
||||
super(ServletContextRequest.getServletContextRequest(request));
|
||||
_servletRequest = request;
|
||||
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements())
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
Enumeration<String> headerValues = request.getHeaders(headerName);
|
||||
while (headerValues.hasMoreElements())
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
fields.add(new HttpField(headerName, headerValue));
|
||||
}
|
||||
}
|
||||
|
||||
_httpFields = fields.asImmutable();
|
||||
String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
boolean included = includedServletPath != null;
|
||||
if (request.getDispatcherType() == DispatcherType.REQUEST)
|
||||
_uri = getWrapped().getHttpURI();
|
||||
else if (included)
|
||||
_uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(getIncludedPathInContext(request, includedServletPath, false)));
|
||||
else
|
||||
_uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
return _servletRequest.getRequestId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return _servletRequest.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.removeAttribute(name);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.setAttribute(name, attribute);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _servletRequest.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
Set<String> set = new HashSet<>();
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
set.add(e.nextElement());
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
_servletRequest.removeAttribute(e.nextElement());
|
||||
}
|
||||
}
|
||||
|
||||
private static class HttpServletResponseHttpFields implements HttpFields.Mutable
|
||||
{
|
||||
private final HttpServletResponse _response;
|
||||
|
||||
private HttpServletResponseHttpFields(HttpServletResponse response)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<HttpField> listIterator()
|
||||
{
|
||||
// The minimum requirement is to implement the listIterator, but it is inefficient.
|
||||
// Other methods are implemented for efficiency.
|
||||
final ListIterator<HttpField> list = _response.getHeaderNames().stream()
|
||||
.map(n -> new HttpField(n, _response.getHeader(n)))
|
||||
.collect(Collectors.toList())
|
||||
.listIterator();
|
||||
|
||||
return new ListIterator<>()
|
||||
{
|
||||
HttpField _last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return list.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField next()
|
||||
{
|
||||
return _last = list.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious()
|
||||
{
|
||||
return list.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField previous()
|
||||
{
|
||||
return _last = list.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex()
|
||||
{
|
||||
return list.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex()
|
||||
{
|
||||
return list.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
if (_last != null)
|
||||
{
|
||||
// This is not exactly the right semantic for repeated field names
|
||||
list.remove();
|
||||
_response.setHeader(_last.getName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(HttpField httpField)
|
||||
{
|
||||
list.set(httpField);
|
||||
_response.setHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(HttpField httpField)
|
||||
{
|
||||
list.add(httpField);
|
||||
_response.addHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(String name, String value)
|
||||
{
|
||||
_response.addHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, String value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpField field)
|
||||
{
|
||||
_response.addHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpField field)
|
||||
{
|
||||
_response.setHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, String value)
|
||||
{
|
||||
_response.setHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, String value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, List<String> list)
|
||||
{
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(list);
|
||||
boolean first = true;
|
||||
for (String s : list)
|
||||
{
|
||||
if (first)
|
||||
_response.setHeader(name, s);
|
||||
else
|
||||
_response.addHeader(name, s);
|
||||
first = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(HttpHeader header)
|
||||
{
|
||||
_response.setHeader(header.asString(), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(EnumSet<HttpHeader> fields)
|
||||
{
|
||||
for (HttpHeader header : fields)
|
||||
remove(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(String name)
|
||||
{
|
||||
_response.setHeader(name, null);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServletCoreResponse implements Response
|
||||
{
|
||||
// TODO fully implement this class and move it to the top level
|
||||
|
||||
private final HttpServletResponse _response;
|
||||
private final ServletCoreRequest _coreRequest;
|
||||
private final Response _coreResponse;
|
||||
private final HttpFields.Mutable _httpFields;
|
||||
private final boolean _included;
|
||||
|
||||
public ServletCoreResponse(ServletCoreRequest coreRequest, HttpServletResponse response, boolean included)
|
||||
{
|
||||
_coreRequest = coreRequest;
|
||||
_response = response;
|
||||
_coreResponse = ServletContextResponse.getServletContextResponse(response);
|
||||
HttpFields.Mutable fields = new HttpServletResponseHttpFields(response);
|
||||
if (included)
|
||||
{
|
||||
// If included, accept but ignore mutations.
|
||||
fields = new HttpFields.Mutable.Wrapper(fields)
|
||||
{
|
||||
@Override
|
||||
public HttpField onAddField(HttpField field)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRemoveField(HttpField field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
_httpFields = fields;
|
||||
_included = included;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields.Mutable getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
public ServletContextResponse getServletContextResponse()
|
||||
{
|
||||
return ServletContextResponse.getServletContextResponse(_response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _response.isCommitted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the HttpServletResponse is wrapped by the webapp.
|
||||
*
|
||||
* @return true if wrapped.
|
||||
*/
|
||||
public boolean isHttpServletResponseWrapped()
|
||||
{
|
||||
return (_response instanceof HttpServletResponseWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if {@link HttpServletResponse#getOutputStream()} or
|
||||
* {@link HttpServletResponse#getWriter()} has been called already
|
||||
*
|
||||
* @return true if {@link HttpServletResponse} has started to write or stream content
|
||||
*/
|
||||
public boolean isWritingOrStreaming()
|
||||
{
|
||||
ServletContextResponse servletContextResponse = Response.as(_coreResponse, ServletContextResponse.class);
|
||||
return servletContextResponse.isWritingOrStreaming();
|
||||
}
|
||||
|
||||
public boolean isWriting()
|
||||
{
|
||||
ServletContextResponse servletContextResponse = Response.as(_coreResponse, ServletContextResponse.class);
|
||||
return servletContextResponse.isWriting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
|
||||
{
|
||||
if (_included)
|
||||
last = false;
|
||||
try
|
||||
{
|
||||
if (BufferUtil.hasContent(byteBuffer))
|
||||
{
|
||||
if (isWriting())
|
||||
{
|
||||
String characterEncoding = _response.getCharacterEncoding();
|
||||
try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
|
||||
InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
|
||||
{
|
||||
IO.copy(reader, _response.getWriter());
|
||||
}
|
||||
|
||||
if (last)
|
||||
_response.getWriter().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
|
||||
if (last)
|
||||
_response.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
callback.failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getRequest()
|
||||
{
|
||||
return _coreRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus()
|
||||
{
|
||||
return _response.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int code)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{}.setStatus({})", this.getClass().getSimpleName(), code);
|
||||
if (_included)
|
||||
return;
|
||||
_response.setStatus(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<HttpFields> getTrailersSupplier()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrailersSupplier(Supplier<HttpFields> trailers)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
return _coreResponse.isCompletedSuccessfully();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_response.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _response);
|
||||
}
|
||||
}
|
||||
|
||||
private class ServletResourceService extends ResourceService implements ResourceService.WelcomeFactory
|
||||
{
|
||||
private final ServletContextHandler _servletContextHandler;
|
||||
|
@ -1214,18 +757,32 @@ public class DefaultServlet extends HttpServlet
|
|||
|
||||
private HttpServletRequest getServletRequest(Request request)
|
||||
{
|
||||
// TODO, this unwrapping is fragile
|
||||
return ((ServletCoreRequest)request)._servletRequest;
|
||||
ServletCoreRequest servletCoreRequest = Request.as(request, ServletCoreRequest.class);
|
||||
if (servletCoreRequest != null)
|
||||
return servletCoreRequest.getServletRequest();
|
||||
|
||||
ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
|
||||
if (servletContextRequest != null)
|
||||
return servletContextRequest.getServletApiRequest();
|
||||
|
||||
throw new IllegalStateException("instanceof " + request.getClass());
|
||||
}
|
||||
|
||||
private HttpServletResponse getServletResponse(Response response)
|
||||
{
|
||||
// TODO, this unwrapping is fragile
|
||||
return ((ServletCoreResponse)response)._response;
|
||||
ServletCoreResponse servletCoreResponse = Response.as(response, ServletCoreResponse.class);
|
||||
if (servletCoreResponse != null)
|
||||
return servletCoreResponse.getServletResponse();
|
||||
|
||||
ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
|
||||
if (servletContextResponse != null)
|
||||
return servletContextResponse.getServletApiResponse();
|
||||
|
||||
throw new IllegalStateException("instanceof " + response.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly)
|
||||
static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly)
|
||||
{
|
||||
String servletPath = isPathInfoOnly ? "/" : includedServletPath;
|
||||
String pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
|
||||
|
@ -1330,4 +887,45 @@ public class DefaultServlet extends HttpServlet
|
|||
*/
|
||||
EXACT
|
||||
}
|
||||
|
||||
private static class AsyncContextCallback implements Callback
|
||||
{
|
||||
private final AsyncContext _asyncContext;
|
||||
private final HttpServletResponse _response;
|
||||
|
||||
private AsyncContextCallback(AsyncContext asyncContext, HttpServletResponse response)
|
||||
{
|
||||
_asyncContext = asyncContext;
|
||||
_response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_asyncContext.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("AsyncContextCallback failed {}", _asyncContext, x);
|
||||
// It is known that this callback is only failed if the response is already committed,
|
||||
// thus we can only abort the response here.
|
||||
_response.sendError(-1);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(x, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_asyncContext.complete();
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Async get failed", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
@ -41,6 +42,7 @@ import org.eclipse.jetty.ee10.servlet.util.ServletOutputStreamWrapper;
|
|||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedResource;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
|
@ -122,16 +124,18 @@ public class Dispatcher implements RequestDispatcher
|
|||
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new ForwardRequest(httpRequest), httpResponse);
|
||||
|
||||
// If we are not async and not closed already, then close via the possibly wrapped response.
|
||||
if (!servletContextRequest.getState().isAsync() && !servletContextRequest.getHttpOutput().isClosed())
|
||||
if (!servletContextRequest.getState().isAsync() && !servletContextRequest.getServletContextResponse().hasLastWrite())
|
||||
{
|
||||
Closeable closeable;
|
||||
try
|
||||
{
|
||||
response.getOutputStream().close();
|
||||
closeable = response.getOutputStream();
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
response.getWriter().close();
|
||||
closeable = response.getWriter();
|
||||
}
|
||||
IO.close(closeable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,6 +628,24 @@ public class Dispatcher implements RequestDispatcher
|
|||
{
|
||||
// NOOP for include.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc, String msg) throws IOException
|
||||
{
|
||||
// NOOP for include.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc) throws IOException
|
||||
{
|
||||
// NOOP for include.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRedirect(String location) throws IOException
|
||||
{
|
||||
// NOOP for include.
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncRequest extends ParameterRequestWrapper
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.jetty.io.Content;
|
|||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -155,11 +156,19 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if any content has been written via the {@link jakarta.servlet.http.HttpServletResponse} API.
|
||||
*/
|
||||
public boolean isWritten()
|
||||
{
|
||||
return _written > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The bytes written via the {@link jakarta.servlet.http.HttpServletResponse} API. This
|
||||
* may differ from the bytes reported by {@link org.eclipse.jetty.server.Response#getContentBytesWritten(Response)}
|
||||
* due to buffering, compression, other interception or writes that bypass the servlet API.
|
||||
*/
|
||||
public long getWritten()
|
||||
{
|
||||
return _written;
|
||||
|
@ -445,6 +454,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
Blocker.Callback blocker = null;
|
||||
try (AutoLock l = _channelState.lock())
|
||||
{
|
||||
if (_softClose)
|
||||
return;
|
||||
|
||||
if (_onError != null)
|
||||
{
|
||||
if (_onError instanceof IOException)
|
||||
|
@ -1456,8 +1468,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(e, t))
|
||||
e.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(e, t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@ public class NoJspServlet extends HttpServlet
|
|||
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (!_warned)
|
||||
getServletContext().log("No JSP support. Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
|
||||
getServletContext().log("No JSP support. Check that the ee10-jsp module is enabled, or otherwise ensure the jsp jars are on the server classpath.");
|
||||
_warned = true;
|
||||
|
||||
response.sendError(500, "JSP support not configured");
|
||||
|
|
|
@ -100,7 +100,6 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
private static final Logger LOG = LoggerFactory.getLogger(ServletApiRequest.class);
|
||||
private final ServletContextRequest _servletContextRequest;
|
||||
private final ServletChannel _servletChannel;
|
||||
//TODO review which fields should be in ServletContextRequest
|
||||
private AsyncContextState _async;
|
||||
private String _characterEncoding;
|
||||
private int _inputState = ServletContextRequest.INPUT_NONE;
|
||||
|
@ -413,7 +412,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
public boolean isRequestedSessionIdValid()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.session() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -88,7 +88,6 @@ public class ServletChannel
|
|||
private Response _response;
|
||||
private Callback _callback;
|
||||
private boolean _expects100Continue;
|
||||
private long _written;
|
||||
|
||||
public ServletChannel(ServletContextHandler servletContextHandler, Request request)
|
||||
{
|
||||
|
@ -218,7 +217,7 @@ public class ServletChannel
|
|||
|
||||
public long getBytesWritten()
|
||||
{
|
||||
return _written;
|
||||
return Response.getContentBytesWritten(getServletContextResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -449,15 +448,13 @@ public class ServletChannel
|
|||
_request = _servletContextRequest = null;
|
||||
_response = null;
|
||||
_callback = null;
|
||||
_written = 0;
|
||||
_expects100Continue = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the servlet request. This is called on the initial dispatch and then again on any asynchronous events.
|
||||
* @return True if the channel is ready to continue handling (ie it is not suspended)
|
||||
*/
|
||||
public boolean handle()
|
||||
public void handle()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("handle {} {} ", _servletContextRequest.getHttpURI(), this);
|
||||
|
@ -553,15 +550,15 @@ public class ServletChannel
|
|||
// If the callback has already been completed we should continue in handle loop.
|
||||
// Otherwise, the callback will schedule a dispatch to handle().
|
||||
if (asyncCompletion.compareAndSet(false, true))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (cause == null)
|
||||
cause = x;
|
||||
else if (ExceptionUtil.areNotAssociated(cause, x))
|
||||
cause.addSuppressed(x);
|
||||
else
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(cause, x);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not perform ERROR dispatch, aborting", cause);
|
||||
if (_state.isResponseCommitted())
|
||||
|
@ -577,8 +574,7 @@ public class ServletChannel
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(cause, t))
|
||||
cause.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(cause, t);
|
||||
abort(cause);
|
||||
}
|
||||
}
|
||||
|
@ -617,12 +613,11 @@ public class ServletChannel
|
|||
ResponseUtils.ensureConsumeAvailableOrNotPersistent(_servletContextRequest, _servletContextRequest.getServletContextResponse());
|
||||
}
|
||||
|
||||
// RFC 7230, section 3.3.
|
||||
if (!_servletContextRequest.isHead() &&
|
||||
getServletContextResponse().getStatus() != HttpStatus.NOT_MODIFIED_304 &&
|
||||
getServletContextResponse().isContentIncomplete(_servletContextRequest.getHttpOutput().getWritten()))
|
||||
// RFC 7230, section 3.3. We do this here so that a servlet error page can be sent.
|
||||
if (!_servletContextRequest.isHead() && getServletContextResponse().getStatus() != HttpStatus.NOT_MODIFIED_304)
|
||||
{
|
||||
if (sendErrorOrAbort("Insufficient content written"))
|
||||
long written = getBytesWritten();
|
||||
if (getServletContextResponse().isContentIncomplete(written) && sendErrorOrAbort("Insufficient content written %d < %d".formatted(written, getServletContextResponse().getContentLength())))
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -649,9 +644,6 @@ public class ServletChannel
|
|||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("!handle {} {}", action, this);
|
||||
|
||||
boolean suspended = action == Action.WAIT;
|
||||
return !suspended;
|
||||
}
|
||||
|
||||
private void reopen()
|
||||
|
@ -664,7 +656,7 @@ public class ServletChannel
|
|||
* @param message the error message.
|
||||
* @return true if we have sent an error, false if we have aborted.
|
||||
*/
|
||||
public boolean sendErrorOrAbort(String message)
|
||||
private boolean sendErrorOrAbort(String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -41,7 +41,7 @@ import static jakarta.servlet.RequestDispatcher.ERROR_SERVLET_NAME;
|
|||
import static jakarta.servlet.RequestDispatcher.ERROR_STATUS_CODE;
|
||||
|
||||
/**
|
||||
* Implementation of AsyncContext interface that holds the state of request-response cycle.
|
||||
* holder of the state of request-response cycle.
|
||||
*/
|
||||
public class ServletChannelState
|
||||
{
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Components;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.HttpStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.TunnelSupport;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
import static org.eclipse.jetty.util.URIUtil.addEncodedPaths;
|
||||
import static org.eclipse.jetty.util.URIUtil.encodePath;
|
||||
|
||||
/**
|
||||
* Wrap a {@link jakarta.servlet.ServletRequest} as a core {@link Request}.
|
||||
* <p>
|
||||
* Whilst similar to a {@link Request.Wrapper}, this class is not a {@code Wrapper}
|
||||
* as callers should not be able to access {@link Wrapper#getWrapped()} and bypass
|
||||
* the {@link jakarta.servlet.ServletRequest}.
|
||||
* </p>
|
||||
* <p>
|
||||
* The current implementation does not support any read operations.
|
||||
* </p>
|
||||
*/
|
||||
class ServletCoreRequest implements Request
|
||||
{
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private final ServletContextRequest _servletContextRequest;
|
||||
private final HttpFields _httpFields;
|
||||
private final HttpURI _uri;
|
||||
|
||||
ServletCoreRequest(HttpServletRequest request)
|
||||
{
|
||||
_servletRequest = request;
|
||||
_servletContextRequest = ServletContextRequest.getServletContextRequest(_servletRequest);
|
||||
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements())
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
Enumeration<String> headerValues = request.getHeaders(headerName);
|
||||
while (headerValues.hasMoreElements())
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
fields.add(new HttpField(headerName, headerValue));
|
||||
}
|
||||
}
|
||||
|
||||
_httpFields = fields.asImmutable();
|
||||
String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
boolean included = includedServletPath != null;
|
||||
|
||||
HttpURI.Mutable builder = HttpURI.build(request.getRequestURI());
|
||||
if (included)
|
||||
builder.path(addEncodedPaths(request.getContextPath(), encodePath(DefaultServlet.getIncludedPathInContext(request, includedServletPath, false))));
|
||||
else if (request.getDispatcherType() != DispatcherType.REQUEST)
|
||||
builder.path(addEncodedPaths(request.getContextPath(), encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo()))));
|
||||
builder.query(request.getQueryString());
|
||||
_uri = builder.asImmutable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
return _servletRequest.getRequestId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return _servletRequest.getMethod();
|
||||
}
|
||||
|
||||
public HttpServletRequest getServletRequest()
|
||||
{
|
||||
return _servletRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.removeAttribute(name);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.setAttribute(name, attribute);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _servletRequest.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
Set<String> set = new HashSet<>();
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
set.add(e.nextElement());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
_servletRequest.removeAttribute(e.nextElement());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable failure)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Components getComponents()
|
||||
{
|
||||
return _servletContextRequest.getComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionMetaData getConnectionMetaData()
|
||||
{
|
||||
return _servletContextRequest.getConnectionMetaData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext()
|
||||
{
|
||||
return _servletContextRequest.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void demand(Runnable demandCallback)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getTrailers()
|
||||
{
|
||||
return _servletContextRequest.getTrailers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBeginNanoTime()
|
||||
{
|
||||
return _servletContextRequest.getBeginNanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeadersNanoTime()
|
||||
{
|
||||
return _servletContextRequest.getHeadersNanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeAvailable()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIdleTimeoutListener(Predicate<TimeoutException> onIdleTimeout)
|
||||
{
|
||||
_servletContextRequest.addIdleTimeoutListener(onIdleTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFailureListener(Consumer<Throwable> onFailure)
|
||||
{
|
||||
_servletContextRequest.addFailureListener(onFailure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunnelSupport getTunnelSupport()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHttpStreamWrapper(Function<HttpStream, HttpStream> wrapper)
|
||||
{
|
||||
_servletContextRequest.addHttpStreamWrapper(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
return Session.getSession(_servletRequest.getSession(create));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.io.ByteBufferInputStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
/**
|
||||
* A {@link HttpServletResponse} wrapped as a core {@link Response}.
|
||||
* All write operations are internally converted to blocking writes on the servlet API.
|
||||
*/
|
||||
class ServletCoreResponse implements Response
|
||||
{
|
||||
private final HttpServletResponse _response;
|
||||
private final Request _coreRequest;
|
||||
private final HttpFields.Mutable _httpFields;
|
||||
private final boolean _included;
|
||||
private final ServletContextResponse _servletContextResponse;
|
||||
|
||||
public ServletCoreResponse(Request coreRequest, HttpServletResponse response, boolean included)
|
||||
{
|
||||
_coreRequest = coreRequest;
|
||||
_response = response;
|
||||
_servletContextResponse = ServletContextResponse.getServletContextResponse(response);
|
||||
HttpFields.Mutable fields = new HttpServletResponseHttpFields(response);
|
||||
if (included)
|
||||
{
|
||||
// If included, accept but ignore mutations.
|
||||
fields = new HttpFields.Mutable.Wrapper(fields)
|
||||
{
|
||||
@Override
|
||||
public HttpField onAddField(HttpField field)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRemoveField(HttpField field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
_httpFields = fields;
|
||||
_included = included;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields.Mutable getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
public HttpServletResponse getServletResponse()
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
return _servletContextResponse.hasLastWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
return _servletContextResponse.isCompletedSuccessfully();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _response.isCommitted();
|
||||
}
|
||||
|
||||
private boolean isWriting()
|
||||
{
|
||||
return _servletContextResponse.isWriting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
|
||||
{
|
||||
if (_included)
|
||||
last = false;
|
||||
try
|
||||
{
|
||||
if (BufferUtil.hasContent(byteBuffer))
|
||||
{
|
||||
if (isWriting())
|
||||
{
|
||||
String characterEncoding = _response.getCharacterEncoding();
|
||||
try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
|
||||
InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
|
||||
{
|
||||
IO.copy(reader, _response.getWriter());
|
||||
}
|
||||
|
||||
if (last)
|
||||
_response.getWriter().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
|
||||
if (last)
|
||||
_response.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
callback.failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getRequest()
|
||||
{
|
||||
return _coreRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus()
|
||||
{
|
||||
return _response.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int code)
|
||||
{
|
||||
if (_included)
|
||||
return;
|
||||
_response.setStatus(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<HttpFields> getTrailersSupplier()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrailersSupplier(Supplier<HttpFields> trailers)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_response.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _response);
|
||||
}
|
||||
|
||||
private static class HttpServletResponseHttpFields implements HttpFields.Mutable
|
||||
{
|
||||
private final HttpServletResponse _response;
|
||||
|
||||
private HttpServletResponseHttpFields(HttpServletResponse response)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<HttpField> listIterator()
|
||||
{
|
||||
// The minimum requirement is to implement the listIterator, but it is inefficient.
|
||||
// Other methods are implemented for efficiency.
|
||||
final ListIterator<HttpField> list = _response.getHeaderNames().stream()
|
||||
.map(n -> new HttpField(n, _response.getHeader(n)))
|
||||
.collect(Collectors.toList())
|
||||
.listIterator();
|
||||
|
||||
return new ListIterator<>()
|
||||
{
|
||||
HttpField _last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return list.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField next()
|
||||
{
|
||||
return _last = list.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious()
|
||||
{
|
||||
return list.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField previous()
|
||||
{
|
||||
return _last = list.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex()
|
||||
{
|
||||
return list.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex()
|
||||
{
|
||||
return list.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
if (_last != null)
|
||||
{
|
||||
// This is not exactly the right semantic for repeated field names
|
||||
list.remove();
|
||||
_response.setHeader(_last.getName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(HttpField httpField)
|
||||
{
|
||||
list.set(httpField);
|
||||
_response.setHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(HttpField httpField)
|
||||
{
|
||||
list.add(httpField);
|
||||
_response.addHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(String name, String value)
|
||||
{
|
||||
_response.addHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, String value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpField field)
|
||||
{
|
||||
_response.addHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpField field)
|
||||
{
|
||||
_response.setHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, String value)
|
||||
{
|
||||
_response.setHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, String value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, List<String> list)
|
||||
{
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(list);
|
||||
boolean first = true;
|
||||
for (String s : list)
|
||||
{
|
||||
if (first)
|
||||
_response.setHeader(name, s);
|
||||
else
|
||||
_response.addHeader(name, s);
|
||||
first = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(HttpHeader header)
|
||||
{
|
||||
_response.setHeader(header.asString(), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(EnumSet<HttpHeader> fields)
|
||||
{
|
||||
for (HttpHeader header : fields)
|
||||
remove(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(String name)
|
||||
{
|
||||
_response.setHeader(name, null);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2035,7 +2035,7 @@ public class DefaultServletTest
|
|||
String body = response.getContent();
|
||||
|
||||
assertThat(response, containsHeaderValue("Content-Type", "multipart/byteranges"));
|
||||
assertThat(response, containsHeaderValue("Content-Length", "" + body.length()));
|
||||
// TODO #10307 assertThat(response, containsHeaderValue("Content-Length", String.valueOf(body.length())));
|
||||
|
||||
HttpField contentType = response.getField(HttpHeader.CONTENT_TYPE);
|
||||
String boundary = getContentTypeBoundary(contentType);
|
||||
|
@ -2063,7 +2063,7 @@ public class DefaultServletTest
|
|||
String body = response.getContent();
|
||||
|
||||
assertThat(response, containsHeaderValue("Content-Type", "multipart/byteranges"));
|
||||
assertThat(response, containsHeaderValue("Content-Length", "" + body.length()));
|
||||
// TODO #10307 assertThat(response, containsHeaderValue("Content-Length", String.valueOf(body.length())));
|
||||
|
||||
HttpField contentType = response.getField(HttpHeader.CONTENT_TYPE);
|
||||
String boundary = getContentTypeBoundary(contentType);
|
||||
|
@ -2093,7 +2093,7 @@ public class DefaultServletTest
|
|||
String body = response.getContent();
|
||||
|
||||
assertThat(response, containsHeaderValue("Content-Type", "multipart/byteranges"));
|
||||
assertThat(response, containsHeaderValue("Content-Length", "" + body.length()));
|
||||
// TODO #10307 assertThat(response, containsHeaderValue("Content-Length", String.valueOf(body.length())));
|
||||
|
||||
HttpField contentType = response.getField(HttpHeader.CONTENT_TYPE);
|
||||
String boundary = getContentTypeBoundary(contentType);
|
||||
|
@ -2154,7 +2154,7 @@ public class DefaultServletTest
|
|||
String body = response.getContent();
|
||||
|
||||
assertThat(response, containsHeaderValue("Content-Type", "multipart/byteranges"));
|
||||
assertThat(response, containsHeaderValue("Content-Length", "" + body.length()));
|
||||
// TODO #10307 assertThat(response, containsHeaderValue("Content-Length", String.valueOf(body.length())));
|
||||
|
||||
HttpField contentType = response.getField(HttpHeader.CONTENT_TYPE);
|
||||
String boundary = getContentTypeBoundary(contentType);
|
||||
|
@ -2183,7 +2183,7 @@ public class DefaultServletTest
|
|||
String body = response.getContent();
|
||||
|
||||
assertThat(response, containsHeaderValue("Content-Type", "multipart/byteranges"));
|
||||
assertThat(response, containsHeaderValue("Content-Length", "" + body.length()));
|
||||
// TODO #10307 assertThat(response, containsHeaderValue("Content-Length", String.valueOf(body.length())));
|
||||
|
||||
HttpField contentType = response.getField(HttpHeader.CONTENT_TYPE);
|
||||
String boundary = getContentTypeBoundary(contentType);
|
||||
|
@ -2295,7 +2295,7 @@ public class DefaultServletTest
|
|||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200));
|
||||
String body = response.getContent();
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "" + body.length()));
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, String.valueOf(body.length())));
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "text/plain;charset=UTF-8"));
|
||||
assertThat(body, containsString("Extra Info"));
|
||||
|
||||
|
@ -2308,7 +2308,7 @@ public class DefaultServletTest
|
|||
response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200));
|
||||
body = response.getContent();
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "" + body.length()));
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, String.valueOf(body.length())));
|
||||
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "image/jpeg;charset=utf-8"));
|
||||
assertThat(body, containsString("Extra Info"));
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ package org.eclipse.jetty.ee10.servlet;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
|
@ -23,6 +25,7 @@ import java.nio.file.Path;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
@ -57,6 +60,7 @@ import org.eclipse.jetty.session.SessionDataStoreFactory;
|
|||
import org.eclipse.jetty.toolchain.test.IO;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
|
@ -359,6 +363,162 @@ public class SessionHandlerTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestedSessionIdFromCookie() throws Exception
|
||||
{
|
||||
String contextPath = "/";
|
||||
String servletMapping = "/server";
|
||||
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
DefaultSessionIdManager sessionIdManager = new DefaultSessionIdManager(server);
|
||||
server.addBean(sessionIdManager, true);
|
||||
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
|
||||
server.addBean(cacheFactory);
|
||||
|
||||
SessionDataStoreFactory storeFactory = new NullSessionDataStoreFactory();
|
||||
server.addBean(storeFactory);
|
||||
|
||||
HouseKeeper housekeeper = new HouseKeeper();
|
||||
housekeeper.setIntervalSec(-1); //turn off scavenging
|
||||
sessionIdManager.setSessionHouseKeeper(housekeeper);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath(contextPath);
|
||||
server.setHandler(context);
|
||||
SessionHandler sessionHandler = new SessionHandler();
|
||||
sessionHandler.setSessionIdManager(sessionIdManager);
|
||||
sessionHandler.setMaxInactiveInterval(-1); //immortal session
|
||||
context.setSessionHandler(sessionHandler);
|
||||
|
||||
TestRequestedSessionIdServlet servlet = new TestRequestedSessionIdServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
context.addServlet(holder, servletMapping);
|
||||
|
||||
server.start();
|
||||
int port = connector.getLocalPort();
|
||||
try (StacklessLogging stackless = new StacklessLogging(SessionHandlerTest.class.getPackage()))
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
|
||||
//test with no session cookie
|
||||
String path = contextPath + (contextPath.endsWith("/") && servletMapping.startsWith("/") ? servletMapping.substring(1) : servletMapping);
|
||||
String url = "http://localhost:" + port + path;
|
||||
ContentResponse response = client.GET(url);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertThat(response.getContentAsString(), containsString("valid=false"));
|
||||
|
||||
//test with a cookie for non-existant session
|
||||
URI uri = URIUtil.toURI(URIUtil.newURI("http", "localhost", port, path, ""));
|
||||
HttpCookie cookie = HttpCookie.build(SessionHandler.__DefaultSessionCookie, "123456789").path("/").domain("localhost").build();
|
||||
client.getHttpCookieStore().add(uri, cookie);
|
||||
response = client.GET(url);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
String content = response.getContentAsString();
|
||||
assertThat(content, containsString("requestedId=123456789"));
|
||||
assertThat(content, containsString("valid=false"));
|
||||
|
||||
//Get rid of fake cookie
|
||||
client.getHttpCookieStore().clear();
|
||||
|
||||
//Make a real session
|
||||
response = client.GET(url + "?action=create");
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertNotNull(response.getHeaders().get("Set-Cookie"));
|
||||
|
||||
//Check the requestedSessionId is valid
|
||||
response = client.GET(url);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
content = response.getContentAsString();
|
||||
assertThat(content, containsString("valid=true"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestedSessionIdFromURL() throws Exception
|
||||
{
|
||||
String contextPath = "/";
|
||||
String servletMapping = "/server";
|
||||
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
DefaultSessionIdManager sessionIdManager = new DefaultSessionIdManager(server);
|
||||
server.addBean(sessionIdManager, true);
|
||||
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
|
||||
server.addBean(cacheFactory);
|
||||
|
||||
SessionDataStoreFactory storeFactory = new NullSessionDataStoreFactory();
|
||||
server.addBean(storeFactory);
|
||||
|
||||
HouseKeeper housekeeper = new HouseKeeper();
|
||||
housekeeper.setIntervalSec(-1); //turn off scavenging
|
||||
sessionIdManager.setSessionHouseKeeper(housekeeper);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath(contextPath);
|
||||
server.setHandler(context);
|
||||
SessionHandler sessionHandler = new SessionHandler();
|
||||
sessionHandler.setUsingCookies(false);
|
||||
sessionHandler.setSessionIdManager(sessionIdManager);
|
||||
sessionHandler.setMaxInactiveInterval(-1); //immortal session
|
||||
context.setSessionHandler(sessionHandler);
|
||||
|
||||
TestRequestedSessionIdServlet servlet = new TestRequestedSessionIdServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
context.addServlet(holder, servletMapping);
|
||||
|
||||
server.start();
|
||||
int port = connector.getLocalPort();
|
||||
try (StacklessLogging stackless = new StacklessLogging(SessionHandlerTest.class.getPackage()))
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
|
||||
//test with no session cookie
|
||||
String path = contextPath + (contextPath.endsWith("/") && servletMapping.startsWith("/") ? servletMapping.substring(1) : servletMapping);
|
||||
String url = "http://localhost:" + port + path;
|
||||
ContentResponse response = client.GET(url);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertThat(response.getContentAsString(), containsString("valid=false"));
|
||||
|
||||
//test with id for non-existent session
|
||||
response = client.GET(url + ";" + SessionHandler.__DefaultSessionIdPathParameterName + "=" + "123456789");
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
String content = response.getContentAsString();
|
||||
assertThat(content, containsString("requestedId=123456789"));
|
||||
assertThat(content, containsString("valid=false"));
|
||||
|
||||
//Make a real session
|
||||
response = client.GET(url + "?action=create");
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
content = response.getContentAsString();
|
||||
assertThat(content, containsString("createdId="));
|
||||
String sessionId = content.substring(content.indexOf("createdId=") + 10);
|
||||
sessionId = sessionId.trim();
|
||||
|
||||
//Check the requestedSessionId is valid
|
||||
response = client.GET(url + ";" + SessionHandler.__DefaultSessionIdPathParameterName + "=" + sessionId);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
content = response.getContentAsString();
|
||||
assertThat(content, containsString("valid=true"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -386,6 +546,26 @@ public class SessionHandlerTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class TestRequestedSessionIdServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
public String _id = null;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println("requestedId=" + request.getRequestedSessionId());
|
||||
writer.println("valid=" + request.isRequestedSessionIdValid());
|
||||
|
||||
if ("create".equals(request.getParameter("action")))
|
||||
{
|
||||
HttpSession session = request.getSession(true);
|
||||
writer.println("createdId=" + session.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MockSessionCache extends AbstractSessionCache
|
||||
{
|
||||
|
||||
|
@ -395,8 +575,9 @@ public class SessionHandlerTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
public ManagedSession doDelete(String key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -411,12 +592,6 @@ public class SessionHandlerTest
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedSession doDelete(String key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doReplace(String id, ManagedSession oldValue, ManagedSession newValue)
|
||||
{
|
||||
|
@ -429,6 +604,11 @@ public class SessionHandlerTest
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ManagedSession doComputeIfAbsent(String id, Function<String, ManagedSession> mappingFunction)
|
||||
{
|
||||
|
|
|
@ -1117,12 +1117,12 @@ public abstract class RFC2616BaseTest
|
|||
HttpTester.Response response = responses.get(0);
|
||||
String specId = "10.3 Redirection HTTP/1.1 - basic (response 1)";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/", response.get("Location"), specId);
|
||||
|
||||
response = responses.get(1);
|
||||
specId = "10.3 Redirection HTTP/1.1 - basic (response 2)";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/", response.get("Location"), specId);
|
||||
assertEquals("close", response.get("Connection"), specId);
|
||||
}
|
||||
|
||||
|
@ -1146,7 +1146,7 @@ public abstract class RFC2616BaseTest
|
|||
|
||||
String specId = "10.3 Redirection HTTP/1.0 w/content";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/R1.txt", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/R1.txt", response.get("Location"), specId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1169,7 +1169,7 @@ public abstract class RFC2616BaseTest
|
|||
|
||||
String specId = "10.3 Redirection HTTP/1.1 w/content";
|
||||
assertThat(specId + " [status]", response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertThat(specId + " [location]", response.get("Location"), is(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/R2.txt"));
|
||||
assertThat(specId + " [location]", response.get("Location"), is(getServer().getScheme() + "://localhost/tests/R2.txt"));
|
||||
assertThat(specId + " [connection]", response.get("Connection"), is("close"));
|
||||
}
|
||||
|
||||
|
|
|
@ -447,7 +447,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
|
|||
* Scan for META-INF/web-fragment.xml file in the given jar.
|
||||
*
|
||||
* @param context the context for the scan
|
||||
* @param jar the jar resource to scan for fragements in
|
||||
* @param jar the jar resource to scan for fragments in
|
||||
* @param cache the resource cache
|
||||
*/
|
||||
public void scanForFragment(WebAppContext context, Resource jar, ConcurrentHashMap<Resource, Resource> cache)
|
||||
|
|
|
@ -13,153 +13,578 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.webapp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
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.TypeUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.resource.FileSystemPool;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollators;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class MetaInfConfigurationTest
|
||||
{
|
||||
public class TestableMetaInfConfiguration extends MetaInfConfiguration
|
||||
@BeforeEach
|
||||
public void beforeEach()
|
||||
{
|
||||
List<String> _expectedContainerScanTypes;
|
||||
List<String> _expectedWebAppScanTypes;
|
||||
int _invocationCount = 0;
|
||||
|
||||
public TestableMetaInfConfiguration(List<String> expectedContainerScanTypes, List<String> expectedWebAppScanTypes)
|
||||
{
|
||||
_expectedContainerScanTypes = expectedContainerScanTypes;
|
||||
_expectedWebAppScanTypes = expectedWebAppScanTypes;
|
||||
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanJars(WebAppContext context, Collection<Resource> jars, boolean useCaches, List<String> scanTypes) throws Exception
|
||||
@AfterEach
|
||||
public void tearDown()
|
||||
{
|
||||
assertNotNull(scanTypes);
|
||||
List<String> expectedScanTypes = null;
|
||||
switch (_invocationCount)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
expectedScanTypes = _expectedContainerScanTypes;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
expectedScanTypes = _expectedWebAppScanTypes;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
fail("Too many invocations");
|
||||
}
|
||||
}
|
||||
|
||||
++_invocationCount;
|
||||
|
||||
assertNotNull(expectedScanTypes);
|
||||
assertTrue(expectedScanTypes.containsAll(scanTypes));
|
||||
assertEquals(expectedScanTypes.size(), scanTypes.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScanTypes() throws Exception
|
||||
{
|
||||
Path web25 = MavenTestingUtils.getTestResourcePathFile("web25.xml");
|
||||
Path web31 = MavenTestingUtils.getTestResourcePathFile("web31.xml");
|
||||
Path web31false = MavenTestingUtils.getTestResourcePathFile("web31false.xml");
|
||||
|
||||
//test a 2.5 webapp will not look for fragments as manually configured
|
||||
MetaInfConfiguration meta25 = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
|
||||
Arrays.asList(MetaInfConfiguration.METAINF_TLDS, MetaInfConfiguration.METAINF_RESOURCES));
|
||||
WebAppContext context25 = new WebAppContext();
|
||||
context25.setConfigurationDiscovered(false);
|
||||
context25.getMetaData().setWebDescriptor(new WebDescriptor(context25.getResourceFactory().newResource(web25)));
|
||||
context25.getContext().getServletContext().setEffectiveMajorVersion(2);
|
||||
context25.getContext().getServletContext().setEffectiveMinorVersion(5);
|
||||
meta25.preConfigure(context25);
|
||||
|
||||
//test a 2.5 webapp will look for fragments as configurationDiscovered default true
|
||||
MetaInfConfiguration meta25b = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
|
||||
MetaInfConfiguration.__allScanTypes);
|
||||
WebAppContext context25b = new WebAppContext();
|
||||
context25b.getMetaData().setWebDescriptor(new WebDescriptor(context25b.getResourceFactory().newResource(web25)));
|
||||
context25b.getContext().getServletContext().setEffectiveMajorVersion(2);
|
||||
context25b.getContext().getServletContext().setEffectiveMinorVersion(5);
|
||||
meta25b.preConfigure(context25b);
|
||||
|
||||
//test a 3.x metadata-complete webapp will not look for fragments
|
||||
MetaInfConfiguration meta31 = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
|
||||
Arrays.asList(MetaInfConfiguration.METAINF_TLDS, MetaInfConfiguration.METAINF_RESOURCES));
|
||||
WebAppContext context31 = new WebAppContext();
|
||||
context31.getMetaData().setWebDescriptor(new WebDescriptor(context31.getResourceFactory().newResource(web31)));
|
||||
context31.getContext().getServletContext().setEffectiveMajorVersion(3);
|
||||
context31.getContext().getServletContext().setEffectiveMinorVersion(1);
|
||||
meta31.preConfigure(context31);
|
||||
|
||||
//test a 3.x non metadata-complete webapp will look for fragments
|
||||
MetaInfConfiguration meta31false = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
|
||||
MetaInfConfiguration.__allScanTypes);
|
||||
WebAppContext context31false = new WebAppContext();
|
||||
context31false.setConfigurationDiscovered(true);
|
||||
context31false.getMetaData().setWebDescriptor(new WebDescriptor(context31false.getResourceFactory().newResource(web31false)));
|
||||
context31false.getContext().getServletContext().setEffectiveMajorVersion(3);
|
||||
context31false.getContext().getServletContext().setEffectiveMinorVersion(1);
|
||||
meta31false.preConfigure(context31false);
|
||||
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test examines both the classpath and the module path to find
|
||||
* container resources.
|
||||
* NOTE: the behaviour of the surefire plugin 3.0.0.M2 is different in
|
||||
* jetty-9.4.x to jetty-10.0.x (where we use module-info): in jetty-9.4.x,
|
||||
* we can use the --add-module argument to put the foo-bar-janb.jar onto the
|
||||
* module path, but this doesn't seem to work in jetty-10.0.x. So this test
|
||||
* will find foo-bar.janb.jar on the classpath, and jetty-util from the module path.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
* Test of a MetaInf scan of a Servlet 2.5 webapp, where
|
||||
* {@link WebAppContext#setConfigurationDiscovered(boolean)} set to {@code false},
|
||||
* thus not performing any Servlet 3.0+ discovery steps for {@code META-INF/web-fragment.xml}.
|
||||
* Scanning for {@code META-INF/resources} is unaffected by configuration.
|
||||
*/
|
||||
@Test
|
||||
public void testFindAndFilterContainerPathsJDK9() throws Exception
|
||||
public void testScanServlet25ConfigurationDiscoveredOff(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path webappDir = workDir.getEmptyPathDir();
|
||||
Path webinf = webappDir.resolve("WEB-INF");
|
||||
FS.ensureDirExists(webinf);
|
||||
Path webxml = webinf.resolve("web.xml");
|
||||
|
||||
String web25 = """
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
<display-name>Test 2.5 WebApp</display-name>
|
||||
</web-app>
|
||||
""";
|
||||
|
||||
Files.writeString(webxml, web25, StandardCharsets.UTF_8);
|
||||
Path libDir = webinf.resolve("lib");
|
||||
FS.ensureDirExists(libDir);
|
||||
Path fooFragmentJar = libDir.resolve("foo-fragment.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(fooFragmentJar))
|
||||
{
|
||||
Path webfragment = jarfs.getPath("/META-INF/web-fragment.xml");
|
||||
FS.ensureDirExists(webfragment.getParent());
|
||||
Files.writeString(webfragment, "<web-fragment />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path barResourceJar = libDir.resolve("bar-resources.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(barResourceJar))
|
||||
{
|
||||
Path resourcesDir = jarfs.getPath("/META-INF/resources");
|
||||
Files.createDirectories(resourcesDir);
|
||||
Path testTxt = resourcesDir.resolve("test.txt");
|
||||
Files.writeString(testTxt, "Test", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path zedTldJar = libDir.resolve("zed-tlds.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(zedTldJar))
|
||||
{
|
||||
Path tldFile = jarfs.getPath("/META-INF/zed.tld");
|
||||
Files.createDirectory(tldFile.getParent());
|
||||
Files.writeString(tldFile, "<taglib />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setServer(new Server());
|
||||
try
|
||||
{
|
||||
context.setBaseResource(context.getResourceFactory().newResource(webappDir));
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor(context.getResourceFactory().newResource(webxml)));
|
||||
context.setConfigurationDiscovered(false); // don't allow discovery of servlet 3.0+ features
|
||||
context.getContext().getServletContext().setEffectiveMajorVersion(2);
|
||||
context.getContext().getServletContext().setEffectiveMinorVersion(5);
|
||||
|
||||
MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration();
|
||||
metaInfConfiguration.preConfigure(context);
|
||||
|
||||
List<String> discoveredWebInfResources = context.getMetaData().getWebInfResources(false)
|
||||
.stream()
|
||||
.sorted(ResourceCollators.byName(true))
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedWebInfResources = {
|
||||
fooFragmentJar.toUri().toASCIIString(),
|
||||
barResourceJar.toUri().toASCIIString(),
|
||||
zedTldJar.toUri().toASCIIString()
|
||||
};
|
||||
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
|
||||
|
||||
// Since this is Servlet 2.5, and we have configuration-discovered turned off, we shouldn't see any web fragments
|
||||
Map<Resource, Resource> fragmentMap = getDiscoveredMetaInfFragments(context);
|
||||
assertThat("META-INF/web-fragment.xml discovered (servlet 2.5 and configuration-discovered turned off)", fragmentMap.size(), is(0));
|
||||
|
||||
// Even on Servlet 2.5, when we have configuration-discovered turned off, we should still see the META-INF/resources/
|
||||
Set<Resource> resourceSet = getDiscoveredMetaInfResource(context);
|
||||
assertThat(resourceSet.size(), is(1));
|
||||
List<String> discoveredResources = resourceSet
|
||||
.stream()
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedResources = {
|
||||
URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString() + "META-INF/resources/"
|
||||
};
|
||||
assertThat("META-INF/resources discovered (servlet 2.5 and configuration-discovered turned off)", discoveredResources, hasItems(expectedResources));
|
||||
|
||||
// TLDs discovered
|
||||
Set<URL> tldSet = getDiscoveredMetaInfTlds(context);
|
||||
assertThat(tldSet.size(), is(1));
|
||||
List<String> discoveredTlds = tldSet
|
||||
.stream()
|
||||
.map(URL::toExternalForm)
|
||||
.toList();
|
||||
String[] expectedTlds = {
|
||||
URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString() + "META-INF/zed.tld"
|
||||
};
|
||||
assertThat("Discovered TLDs", discoveredTlds, hasItems(expectedTlds));
|
||||
}
|
||||
finally
|
||||
{
|
||||
LifeCycle.stop(context.getResourceFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of a MetaInf scan of a Servlet 2.5 webapp, where
|
||||
* {@link WebAppContext#setConfigurationDiscovered(boolean)} is left at default (@{code true})
|
||||
* allowing the performing of Servlet 3.0+ discovery steps for {@code META-INF/web-fragment.xml} and {@code META-INF/resources}
|
||||
*/
|
||||
@Test
|
||||
public void testScanServlet25ConfigurationDiscoveredDefault(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path webappDir = workDir.getEmptyPathDir();
|
||||
Path webinf = webappDir.resolve("WEB-INF");
|
||||
FS.ensureDirExists(webinf);
|
||||
Path webxml = webinf.resolve("web.xml");
|
||||
|
||||
String web25 = """
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
<display-name>Test 2.5 WebApp</display-name>
|
||||
</web-app>
|
||||
""";
|
||||
|
||||
Files.writeString(webxml, web25, StandardCharsets.UTF_8);
|
||||
Path libDir = webinf.resolve("lib");
|
||||
FS.ensureDirExists(libDir);
|
||||
Path fooFragmentJar = libDir.resolve("foo-fragment.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(fooFragmentJar))
|
||||
{
|
||||
Path webfragment = jarfs.getPath("/META-INF/web-fragment.xml");
|
||||
FS.ensureDirExists(webfragment.getParent());
|
||||
Files.writeString(webfragment, "<web-fragment />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path barResourceJar = libDir.resolve("bar-resources.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(barResourceJar))
|
||||
{
|
||||
Path resourcesDir = jarfs.getPath("/META-INF/resources");
|
||||
Files.createDirectories(resourcesDir);
|
||||
Path testTxt = resourcesDir.resolve("test.txt");
|
||||
Files.writeString(testTxt, "Test", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path zedTldJar = libDir.resolve("zed-tlds.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(zedTldJar))
|
||||
{
|
||||
Path tldFile = jarfs.getPath("/META-INF/zed.tld");
|
||||
Files.createDirectory(tldFile.getParent());
|
||||
Files.writeString(tldFile, "<taglib />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setServer(new Server());
|
||||
try
|
||||
{
|
||||
context.setBaseResource(context.getResourceFactory().newResource(webappDir));
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor(context.getResourceFactory().newResource(webxml)));
|
||||
// context25.setConfigurationDiscovered(true); // The default value
|
||||
context.getContext().getServletContext().setEffectiveMajorVersion(2);
|
||||
context.getContext().getServletContext().setEffectiveMinorVersion(5);
|
||||
|
||||
MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration();
|
||||
metaInfConfiguration.preConfigure(context);
|
||||
|
||||
List<String> discoveredWebInfResources = context.getMetaData().getWebInfResources(false)
|
||||
.stream()
|
||||
.sorted(ResourceCollators.byName(true))
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedWebInfResources = {
|
||||
fooFragmentJar.toUri().toASCIIString(),
|
||||
barResourceJar.toUri().toASCIIString(),
|
||||
zedTldJar.toUri().toASCIIString()
|
||||
};
|
||||
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
|
||||
|
||||
// Since this is Servlet 2.5, and we have configuration-discovered turned on, we should see the META-INF/web-fragment.xml entries
|
||||
Map<Resource, Resource> fragmentMap = getDiscoveredMetaInfFragments(context);
|
||||
assertThat(fragmentMap.size(), is(1));
|
||||
List<String> discoveredFragments = fragmentMap.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getValue().getURI().toASCIIString())
|
||||
.toList();
|
||||
String[] expectedFragments = {
|
||||
URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString() + "META-INF/web-fragment.xml"
|
||||
};
|
||||
assertThat("META-INF/web-fragment.xml discovered (servlet 2.5 and configuration-discovered=true)", discoveredFragments, hasItems(expectedFragments));
|
||||
|
||||
// Since this is Servlet 2.5, and we have configuration-discovered turned on, we should see the META-INF/resources/
|
||||
Set<Resource> resourceSet = getDiscoveredMetaInfResource(context);
|
||||
assertThat(resourceSet.size(), is(1));
|
||||
List<String> discoveredResources = resourceSet
|
||||
.stream()
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedResources = {
|
||||
URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString() + "META-INF/resources/"
|
||||
};
|
||||
assertThat("META-INF/resources discovered (servlet 2.5 and configuration-discovered=true)", discoveredResources, hasItems(expectedResources));
|
||||
|
||||
// TLDs discovered
|
||||
Set<URL> tldSet = getDiscoveredMetaInfTlds(context);
|
||||
assertThat(tldSet.size(), is(1));
|
||||
List<String> discoveredTlds = tldSet
|
||||
.stream()
|
||||
.map(URL::toExternalForm)
|
||||
.toList();
|
||||
String[] expectedTlds = {
|
||||
URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString() + "META-INF/zed.tld"
|
||||
};
|
||||
assertThat("Discovered TLDs", discoveredTlds, hasItems(expectedTlds));
|
||||
}
|
||||
finally
|
||||
{
|
||||
LifeCycle.stop(context.getResourceFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of a MetaInf scan of a Servlet 3.0 webapp, metadata-complete is set to {@code false}
|
||||
*/
|
||||
@Test
|
||||
public void testScanServlet30MetadataCompleteFalse(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path webappDir = workDir.getEmptyPathDir();
|
||||
Path webinf = webappDir.resolve("WEB-INF");
|
||||
FS.ensureDirExists(webinf);
|
||||
Path webxml = webinf.resolve("web.xml");
|
||||
|
||||
String web30 = """
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
metadata-complete="false"
|
||||
version="3.0">
|
||||
<display-name>Test 3.0 WebApp</display-name>
|
||||
</web-app>
|
||||
""";
|
||||
|
||||
Files.writeString(webxml, web30, StandardCharsets.UTF_8);
|
||||
Path libDir = webinf.resolve("lib");
|
||||
FS.ensureDirExists(libDir);
|
||||
Path fooFragmentJar = libDir.resolve("foo-fragment.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(fooFragmentJar))
|
||||
{
|
||||
Path webfragment = jarfs.getPath("/META-INF/web-fragment.xml");
|
||||
FS.ensureDirExists(webfragment.getParent());
|
||||
Files.writeString(webfragment, "<web-fragment />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path barResourceJar = libDir.resolve("bar-resources.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(barResourceJar))
|
||||
{
|
||||
Path resourcesDir = jarfs.getPath("/META-INF/resources");
|
||||
Files.createDirectories(resourcesDir);
|
||||
Path testTxt = resourcesDir.resolve("test.txt");
|
||||
Files.writeString(testTxt, "Test", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path zedTldJar = libDir.resolve("zed-tlds.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(zedTldJar))
|
||||
{
|
||||
Path tldFile = jarfs.getPath("/META-INF/zed.tld");
|
||||
Files.createDirectory(tldFile.getParent());
|
||||
Files.writeString(tldFile, "<taglib />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setServer(new Server());
|
||||
try
|
||||
{
|
||||
context.setBaseResource(context.getResourceFactory().newResource(webappDir));
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor(context.getResourceFactory().newResource(webxml)));
|
||||
// context25.setConfigurationDiscovered(true); // The default value
|
||||
context.getContext().getServletContext().setEffectiveMajorVersion(3);
|
||||
context.getContext().getServletContext().setEffectiveMinorVersion(0);
|
||||
|
||||
MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration();
|
||||
metaInfConfiguration.preConfigure(context);
|
||||
|
||||
List<String> discoveredWebInfResources = context.getMetaData().getWebInfResources(false)
|
||||
.stream()
|
||||
.sorted(ResourceCollators.byName(true))
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedWebInfResources = {
|
||||
fooFragmentJar.toUri().toASCIIString(),
|
||||
barResourceJar.toUri().toASCIIString(),
|
||||
zedTldJar.toUri().toASCIIString()
|
||||
};
|
||||
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
|
||||
|
||||
// Since this is Servlet 3.0, and we have configuration-discovered turned on, we should see the META-INF/web-fragment.xml entries
|
||||
Map<Resource, Resource> fragmentMap = getDiscoveredMetaInfFragments(context);
|
||||
assertThat(fragmentMap.size(), is(1));
|
||||
List<String> discoveredFragments = fragmentMap.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getValue().getURI().toASCIIString())
|
||||
.toList();
|
||||
String[] expectedFragments = {
|
||||
URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString() + "META-INF/web-fragment.xml"
|
||||
};
|
||||
assertThat("META-INF/web-fragment.xml discovered (servlet 3.0, and metadata-complete=false, and configuration-discovered=true)", discoveredFragments, hasItems(expectedFragments));
|
||||
|
||||
// Since this is Servlet 3.0, and we have configuration-discovered turned on, we should see the META-INF/resources/
|
||||
Set<Resource> resourceSet = getDiscoveredMetaInfResource(context);
|
||||
assertThat(resourceSet.size(), is(1));
|
||||
List<String> discoveredResources = resourceSet
|
||||
.stream()
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedResources = {
|
||||
URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString() + "META-INF/resources/"
|
||||
};
|
||||
assertThat("META-INF/resources discovered (servlet 3.0, and metadata-complete=false, and configuration-discovered=true)", discoveredResources, hasItems(expectedResources));
|
||||
|
||||
// TLDs discovered
|
||||
Set<URL> tldSet = getDiscoveredMetaInfTlds(context);
|
||||
assertThat(tldSet.size(), is(1));
|
||||
List<String> discoveredTlds = tldSet
|
||||
.stream()
|
||||
.map(URL::toExternalForm)
|
||||
.toList();
|
||||
String[] expectedTlds = {
|
||||
URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString() + "META-INF/zed.tld"
|
||||
};
|
||||
assertThat("Discovered TLDs", discoveredTlds, hasItems(expectedTlds));
|
||||
}
|
||||
finally
|
||||
{
|
||||
LifeCycle.stop(context.getResourceFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of a MetaInf scan of a Servlet 3.1 webapp, metadata-complete is set to {@code true}
|
||||
*/
|
||||
@Test
|
||||
public void testScanServlet31MetadataCompleteTrue(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path webappDir = workDir.getEmptyPathDir();
|
||||
Path webinf = webappDir.resolve("WEB-INF");
|
||||
FS.ensureDirExists(webinf);
|
||||
Path webxml = webinf.resolve("web.xml");
|
||||
|
||||
String web31 = """
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
metadata-complete="true"
|
||||
version="3.1">
|
||||
<display-name>Test 3.1 WebApp</display-name>
|
||||
</web-app>
|
||||
""";
|
||||
|
||||
Files.writeString(webxml, web31, StandardCharsets.UTF_8);
|
||||
Path libDir = webinf.resolve("lib");
|
||||
FS.ensureDirExists(libDir);
|
||||
Path fooFragmentJar = libDir.resolve("foo-fragment.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(fooFragmentJar))
|
||||
{
|
||||
Path webfragment = jarfs.getPath("/META-INF/web-fragment.xml");
|
||||
FS.ensureDirExists(webfragment.getParent());
|
||||
Files.writeString(webfragment, "<web-fragment />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path barResourceJar = libDir.resolve("bar-resources.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(barResourceJar))
|
||||
{
|
||||
Path resourcesDir = jarfs.getPath("/META-INF/resources");
|
||||
Files.createDirectories(resourcesDir);
|
||||
Path testTxt = resourcesDir.resolve("test.txt");
|
||||
Files.writeString(testTxt, "Test", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path zedTldJar = libDir.resolve("zed-tlds.jar");
|
||||
try (FileSystem jarfs = createNewJarFileSystem(zedTldJar))
|
||||
{
|
||||
Path tldFile = jarfs.getPath("/META-INF/zed.tld");
|
||||
Files.createDirectory(tldFile.getParent());
|
||||
Files.writeString(tldFile, "<taglib />", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setServer(new Server());
|
||||
try
|
||||
{
|
||||
context.setBaseResource(context.getResourceFactory().newResource(webappDir));
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor(context.getResourceFactory().newResource(webxml)));
|
||||
context.getContext().getServletContext().setEffectiveMajorVersion(3);
|
||||
context.getContext().getServletContext().setEffectiveMinorVersion(1);
|
||||
|
||||
MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration();
|
||||
metaInfConfiguration.preConfigure(context);
|
||||
|
||||
List<String> discoveredWebInfResources = context.getMetaData().getWebInfResources(false)
|
||||
.stream()
|
||||
.sorted(ResourceCollators.byName(true))
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedWebInfResources = {
|
||||
fooFragmentJar.toUri().toASCIIString(),
|
||||
barResourceJar.toUri().toASCIIString(),
|
||||
zedTldJar.toUri().toASCIIString()
|
||||
};
|
||||
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
|
||||
|
||||
// Since this is Servlet 3.1, and we have metadata-complete=true, we should see no fragments
|
||||
Map<Resource, Resource> fragmentMap = getDiscoveredMetaInfFragments(context);
|
||||
assertThat("META-INF/web-fragment.xml discovered (servlet 3.1, and metadata-complete=true)", fragmentMap.size(), is(0));
|
||||
|
||||
// Even on Servlet 3.1, with metadata-complete=true, we should still see the META-INF/resources/
|
||||
Set<Resource> resourceSet = getDiscoveredMetaInfResource(context);
|
||||
assertThat(resourceSet.size(), is(1));
|
||||
List<String> discoveredResources = resourceSet
|
||||
.stream()
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
String[] expectedResources = {
|
||||
URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString() + "META-INF/resources/"
|
||||
};
|
||||
assertThat("META-INF/resources discovered (servlet 3.1 and metadata-complete=true)", discoveredResources, hasItems(expectedResources));
|
||||
|
||||
// TLDs discovered
|
||||
Set<URL> tldSet = getDiscoveredMetaInfTlds(context);
|
||||
assertThat(tldSet.size(), is(1));
|
||||
List<String> discoveredTlds = tldSet
|
||||
.stream()
|
||||
.map(URL::toExternalForm)
|
||||
.toList();
|
||||
String[] expectedTlds = {
|
||||
URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString() + "META-INF/zed.tld"
|
||||
};
|
||||
assertThat("Discovered TLDs", discoveredTlds, hasItems(expectedTlds));
|
||||
}
|
||||
finally
|
||||
{
|
||||
LifeCycle.stop(context.getResourceFactory());
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystem createNewJarFileSystem(Path jarFile) throws IOException
|
||||
{
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
URI jarUri = URIUtil.uriJarPrefix(jarFile.toUri(), "!/");
|
||||
return FileSystems.newFileSystem(jarUri, env);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test examines both the classpath and the module path to find container resources.
|
||||
* This test looks {@code foo-bar.janb.jar} on the classpath (it was added there by the surefire configuration
|
||||
* present in the {@code pom.xml}), and the {@code servlet-api} from the module path.
|
||||
*/
|
||||
@Test
|
||||
public void testGetContainerPathsWithModuleSystem() throws Exception
|
||||
{
|
||||
MetaInfConfiguration config = new MetaInfConfiguration();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setServer(new Server());
|
||||
config.preConfigure(context);
|
||||
try
|
||||
{
|
||||
context.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, ".*servlet-api-[^/]*\\.jar$|.*/foo-bar-janb.jar");
|
||||
WebAppClassLoader loader = new WebAppClassLoader(context);
|
||||
context.setClassLoader(loader);
|
||||
config.findAndFilterContainerPaths(context);
|
||||
List<Resource> containerResources = context.getMetaData().getContainerResources();
|
||||
config.preConfigure(context);
|
||||
|
||||
assertEquals(2, containerResources.size());
|
||||
for (Resource r : containerResources)
|
||||
{
|
||||
String s = r.toString();
|
||||
assertTrue(s.endsWith("foo-bar-janb.jar") || s.contains("servlet-api"));
|
||||
}
|
||||
Class janbClazz = Class.forName("foo.bar.janb.What", false, loader);
|
||||
URI janbUri = TypeUtil.getLocationOfClass(janbClazz);
|
||||
Class servletClazz = Class.forName("jakarta.servlet.Servlet", false, loader);
|
||||
URI servletUri = TypeUtil.getLocationOfClass(servletClazz);
|
||||
|
||||
List<String> discoveredContainerResources = context.getMetaData().getContainerResources()
|
||||
.stream()
|
||||
.sorted(ResourceCollators.byName(true))
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
// we "correct" the bad file URLs that come from the ClassLoader
|
||||
// to be the same as what comes from every non-classloader URL/URI.
|
||||
String[] expectedContainerResources = {
|
||||
URIUtil.correctFileURI(janbUri).toASCIIString(),
|
||||
URIUtil.correctFileURI(servletUri).toASCIIString()
|
||||
};
|
||||
assertThat("Discovered Container resources", discoveredContainerResources, hasItems(expectedContainerResources));
|
||||
}
|
||||
finally
|
||||
{
|
||||
config.postConfigure(context);
|
||||
// manually stop ResourceFactory.
|
||||
// normally this would be done via WebAppContext.stop(), but we didn't start the context.
|
||||
LifeCycle.stop(context.getResourceFactory());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Resource, Resource> getDiscoveredMetaInfFragments(WebAppContext context)
|
||||
{
|
||||
return (Map<Resource, Resource>)context.getAttribute(MetaInfConfiguration.METAINF_FRAGMENTS);
|
||||
}
|
||||
|
||||
private Set<Resource> getDiscoveredMetaInfResource(WebAppContext context)
|
||||
{
|
||||
return (Set<Resource>)context.getAttribute(MetaInfConfiguration.METAINF_RESOURCES);
|
||||
}
|
||||
|
||||
private Set<URL> getDiscoveredMetaInfTlds(WebAppContext context)
|
||||
{
|
||||
return (Set<URL>)context.getAttribute(MetaInfConfiguration.METAINF_TLDS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
|
||||
<display-name>Test 2.5 WebApp</display-name>
|
||||
|
||||
</web-app>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app
|
||||
xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
|
||||
metadata-complete="true"
|
||||
version="6.0">
|
||||
|
||||
<display-name>Test 31 WebApp</display-name>
|
||||
|
||||
</web-app>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app
|
||||
xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
|
||||
metadata-complete="false"
|
||||
version="6.0">
|
||||
|
||||
<display-name>Test 31 WebApp</display-name>
|
||||
|
||||
</web-app>
|
|
@ -1585,8 +1585,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (ExceptionUtil.areNotAssociated(e, t))
|
||||
e.addSuppressed(t);
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(e, t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -2482,6 +2482,12 @@ public class ResponseTest
|
|||
return _committed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
return _last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
|
|
|
@ -50,7 +50,6 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -87,6 +86,7 @@ public class SessionHandlerTest
|
|||
String pathInContext = request.getPathInfo();
|
||||
String[] split = pathInContext.substring(1).split("/");
|
||||
|
||||
String requestedSessionId = request.getRequestedSessionId();
|
||||
HttpSession session = request.getSession(false);
|
||||
|
||||
if (split.length > 0)
|
||||
|
@ -135,6 +135,10 @@ public class SessionHandlerTest
|
|||
}
|
||||
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("requestedSessionId=" + requestedSessionId).append('\n');
|
||||
out.append("requestedSessionIdValid=" + request.isRequestedSessionIdValid()).append('\n');
|
||||
|
||||
|
||||
if (session == null)
|
||||
out.append("No Session\n");
|
||||
else
|
||||
|
@ -309,8 +313,9 @@ public class SessionHandlerTest
|
|||
String setCookie = response.get("SET-COOKIE");
|
||||
assertThat(setCookie, containsString("Path=/"));
|
||||
String content = response.getContent();
|
||||
assertThat(content, startsWith("Session="));
|
||||
String id = content.substring(content.indexOf('=') + 1, content.indexOf('\n'));
|
||||
assertThat(content, containsString("Session="));
|
||||
String id = content.substring(content.indexOf("Session=") + 8);
|
||||
id = id.trim();
|
||||
assertThat(id, not(equalTo("oldCookieId")));
|
||||
|
||||
endPoint.addInput("""
|
||||
|
@ -326,6 +331,64 @@ public class SessionHandlerTest
|
|||
assertThat(content, containsString("Session=" + id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestedSessionIdFromCookie() throws Exception
|
||||
{
|
||||
_server.stop();
|
||||
_sessionHandler.setSessionPath(null);
|
||||
_contextHandler.setContextPath("/");
|
||||
_server.start();
|
||||
|
||||
//test with no session cookie
|
||||
LocalConnector.LocalEndPoint endPoint = _connector.connect();
|
||||
endPoint.addInput("""
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
assertThat(response.getContent(), containsString("No Session"));
|
||||
assertThat(response.getContent(), containsString("requestedSessionIdValid=false"));
|
||||
|
||||
//test with a cookie for non-existant session
|
||||
endPoint.addInput("""
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Cookie: JSESSIONID=%s
|
||||
|
||||
""".formatted("123456789"));
|
||||
response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
assertThat(response.getContent(), containsString("No Session"));
|
||||
assertThat(response.getContent(), containsString("requestedSessionIdValid=false"));
|
||||
|
||||
//Make a real session
|
||||
endPoint.addInput("""
|
||||
GET /create HTTP/1.1
|
||||
Host: localhost
|
||||
|
||||
""");
|
||||
|
||||
response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, containsString("Session="));
|
||||
String id = content.substring(content.indexOf("Session=") + 8);
|
||||
id = id.trim();
|
||||
|
||||
//Check the requestedSessionId is valid
|
||||
endPoint.addInput("""
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Cookie: JSESSIONID=%s
|
||||
|
||||
""".formatted(id));
|
||||
response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getContent(), containsString("requestedSessionIdValid=true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttribute() throws Exception
|
||||
{
|
||||
|
@ -339,8 +402,9 @@ public class SessionHandlerTest
|
|||
HttpTester.Response response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, startsWith("Session="));
|
||||
String id = content.substring(content.indexOf('=') + 1, content.indexOf('\n'));
|
||||
assertThat(content, containsString("Session="));
|
||||
String id = content.substring(content.indexOf("Session=") + 8);
|
||||
id = id.trim();
|
||||
|
||||
endPoint.addInput("""
|
||||
GET /set/attribute/value HTTP/1.1
|
||||
|
@ -380,7 +444,7 @@ public class SessionHandlerTest
|
|||
HttpTester.Response response = HttpTester.parseResponse(endPoint.getResponse());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, startsWith("Session="));
|
||||
assertThat(content, containsString("Session="));
|
||||
|
||||
String setCookie = response.get(HttpHeader.SET_COOKIE);
|
||||
String id = setCookie.substring(setCookie.indexOf("JSESSIONID=") + 11, setCookie.indexOf("; Path=/"));
|
||||
|
|
|
@ -28,7 +28,7 @@ public class NoJspServlet extends HttpServlet
|
|||
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (!_warned)
|
||||
getServletContext().log("No JSP support. Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
|
||||
getServletContext().log("No JSP support. Check that the ee9-jsp module is enabled, or otherwise ensure the jsp jars are on the server classpath.");
|
||||
_warned = true;
|
||||
|
||||
response.sendError(500, "JSP support not configured");
|
||||
|
|
|
@ -1114,12 +1114,12 @@ public abstract class RFC2616BaseTest
|
|||
HttpTester.Response response = responses.get(0);
|
||||
String specId = "10.3 Redirection HTTP/1.1 - basic (response 1)";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/", response.get("Location"), specId);
|
||||
|
||||
response = responses.get(1);
|
||||
specId = "10.3 Redirection HTTP/1.1 - basic (response 2)";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/", response.get("Location"), specId);
|
||||
assertEquals("close", response.get("Connection"), specId);
|
||||
}
|
||||
|
||||
|
@ -1143,7 +1143,7 @@ public abstract class RFC2616BaseTest
|
|||
|
||||
String specId = "10.3 Redirection HTTP/1.0 w/content";
|
||||
assertThat(specId, response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertEquals(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/R1.txt", response.get("Location"), specId);
|
||||
assertEquals(getServer().getScheme() + "://localhost/tests/R1.txt", response.get("Location"), specId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1166,7 +1166,7 @@ public abstract class RFC2616BaseTest
|
|||
|
||||
String specId = "10.3 Redirection HTTP/1.1 w/content";
|
||||
assertThat(specId + " [status]", response.getStatus(), is(HttpStatus.FOUND_302));
|
||||
assertThat(specId + " [location]", response.get("Location"), is(getServer().getScheme() + "://localhost:" + getServer().getServerPort() + "/tests/R2.txt"));
|
||||
assertThat(specId + " [location]", response.get("Location"), is(getServer().getScheme() + "://localhost/tests/R2.txt"));
|
||||
assertThat(specId + " [connection]", response.get("Connection"), is("close"));
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-annotations</artifactId>
|
||||
<artifactId>jetty-ee9-webapp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
|
|
@ -72,6 +72,11 @@
|
|||
<artifactId>jetty-jmx</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-annotations</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
14
pom.xml
14
pom.xml
|
@ -56,7 +56,7 @@
|
|||
<google.errorprone.version>2.20.0</google.errorprone.version>
|
||||
<grpc.version>1.56.1</grpc.version>
|
||||
<gson.version>2.10.1</gson.version>
|
||||
<guava.version>32.1.1-jre</guava.version>
|
||||
<guava.version>32.1.2-jre</guava.version>
|
||||
<guice.version>7.0.0</guice.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
<hazelcast.version>5.3.1</hazelcast.version>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<jboss.logging.annotations.version>2.2.1.Final</jboss.logging.annotations.version>
|
||||
<jboss.logging.processor.version>2.2.1.Final</jboss.logging.processor.version>
|
||||
<jboss.logging.version>3.5.3.Final</jboss.logging.version>
|
||||
<jboss-logmanager.version>2.3.0.Alpha1</jboss-logmanager.version>
|
||||
<jboss-logmanager.version>3.0.1.Final</jboss-logmanager.version>
|
||||
<jboss-threads.version>3.5.0.Final</jboss-threads.version>
|
||||
<jetty-assembly-descriptors.version>1.1</jetty-assembly-descriptors.version>
|
||||
<jetty.perf-helper.version>1.0.7</jetty.perf-helper.version>
|
||||
|
@ -95,7 +95,7 @@
|
|||
<jetty-test-policy.version>1.2</jetty-test-policy.version>
|
||||
<jetty.test.version>6.1</jetty.test.version>
|
||||
<jetty.xhtml.schemas-version>1.1</jetty.xhtml.schemas-version>
|
||||
<jmh.version>1.36</jmh.version>
|
||||
<jmh.version>1.37</jmh.version>
|
||||
<jna.version>5.13.0</jna.version>
|
||||
<json-simple.version>1.1.1</json-simple.version>
|
||||
<json-smart.version>2.5.0</json-smart.version>
|
||||
|
@ -103,12 +103,12 @@
|
|||
<jsp.impl.version>10.0.14</jsp.impl.version>
|
||||
<kerb-simplekdc.version>2.0.3</kerb-simplekdc.version>
|
||||
<log4j2.version>2.20.0</log4j2.version>
|
||||
<logback.version>1.4.8</logback.version>
|
||||
<logback.version>1.4.9</logback.version>
|
||||
<mariadb.version>3.1.4</mariadb.version>
|
||||
<mariadb.docker.version>10.3.6</mariadb.docker.version>
|
||||
<maven.deps.version>3.9.0</maven.deps.version>
|
||||
<maven-artifact-transfer.version>0.13.1</maven-artifact-transfer.version>
|
||||
<maven.resolver.version>1.9.14</maven.resolver.version>
|
||||
<maven.resolver.version>1.9.15</maven.resolver.version>
|
||||
<maven.version>3.9.0</maven.version>
|
||||
<mongodb.version>3.12.11</mongodb.version>
|
||||
<openpojo.version>0.9.1</openpojo.version>
|
||||
|
@ -151,7 +151,7 @@
|
|||
<!-- some maven plugins versions -->
|
||||
<asciidoctor.maven.plugin.version>2.2.4</asciidoctor.maven.plugin.version>
|
||||
<build-helper.maven.plugin.version>3.3.0</build-helper.maven.plugin.version>
|
||||
<buildnumber.maven.plugin.version>3.0.0</buildnumber.maven.plugin.version>
|
||||
<buildnumber.maven.plugin.version>3.2.0</buildnumber.maven.plugin.version>
|
||||
<depends.maven.plugin.version>1.5.0</depends.maven.plugin.version>
|
||||
<flatten.maven.plugin.version>1.3.0</flatten.maven.plugin.version>
|
||||
<groovy.version>4.0.6</groovy.version>
|
||||
|
@ -2222,7 +2222,7 @@
|
|||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<cbi-plugins.version>1.1.7</cbi-plugins.version>
|
||||
<cbi-plugins.version>1.4.2</cbi-plugins.version>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.eclipse.jetty.io.ClientConnector;
|
|||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.content.ByteBufferContentSource;
|
||||
import org.eclipse.jetty.tests.hometester.JettyHomeTester;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.PathMatchers;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
@ -1529,4 +1530,85 @@ public class DistributionTests extends AbstractJettyHomeTest
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"ee8", "ee9", "ee10"})
|
||||
public void testXmlDeployWarNotInWebapps(String env) throws Exception
|
||||
{
|
||||
Path jettyBase = newTestJettyBaseDirectory();
|
||||
String jettyVersion = System.getProperty("jettyVersion");
|
||||
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
|
||||
.jettyVersion(jettyVersion)
|
||||
.jettyBase(jettyBase)
|
||||
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||
.build();
|
||||
|
||||
int httpPort = distribution.freePort();
|
||||
|
||||
String[] argsConfig = {
|
||||
"--add-modules=http," + toEnvironment("deploy", env) + "," + toEnvironment("webapp", env)
|
||||
};
|
||||
|
||||
try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
|
||||
{
|
||||
assertTrue(runConfig.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
|
||||
assertEquals(0, runConfig.getExitValue());
|
||||
|
||||
String[] argsStart = {
|
||||
"jetty.http.port=" + httpPort,
|
||||
"jetty.httpConfig.port=" + httpPort
|
||||
};
|
||||
|
||||
// Put war into ${jetty.base}/wars/ directory
|
||||
File srcWar = distribution.resolveArtifact("org.eclipse.jetty." + env + ".demos:jetty-" + env + "-demo-simple-webapp:war:" + jettyVersion);
|
||||
Path warsDir = jettyBase.resolve("wars");
|
||||
FS.ensureDirExists(warsDir);
|
||||
Path destWar = warsDir.resolve("demo.war");
|
||||
Files.copy(srcWar.toPath(), destWar);
|
||||
|
||||
// Create XML for deployable
|
||||
String xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.%s.webapp.WebAppContext">
|
||||
<Set name="contextPath">/demo</Set>
|
||||
<Set name="war">%s</Set>
|
||||
</Configure>
|
||||
""".formatted(env, destWar.toString());
|
||||
Files.writeString(jettyBase.resolve("webapps/demo.xml"), xml, StandardCharsets.UTF_8);
|
||||
|
||||
// Specify Environment Properties for this raw XML based deployable
|
||||
String props = """
|
||||
environment=%s
|
||||
""".formatted(env);
|
||||
Files.writeString(jettyBase.resolve("webapps/demo.properties"), props, StandardCharsets.UTF_8);
|
||||
|
||||
/* The jetty.base tree should now look like this
|
||||
*
|
||||
* ${jetty.base}
|
||||
* ├── resources/
|
||||
* │ └── jetty-logging.properties
|
||||
* ├── start.d/
|
||||
* │ ├── ${env}-deploy.ini
|
||||
* │ ├── ${env}-webapp.ini
|
||||
* │ └── http.ini
|
||||
* ├── wars/
|
||||
* │ └── demo.war
|
||||
* ├── webapps/
|
||||
* │ ├── demo.properties
|
||||
* │ └── demo.xml
|
||||
* └── work/
|
||||
*/
|
||||
|
||||
try (JettyHomeTester.Run runStart = distribution.start(argsStart))
|
||||
{
|
||||
assertTrue(runStart.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
|
||||
|
||||
startHttpClient();
|
||||
ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/index.html");
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue