Merge remote-tracking branch 'origin/jetty-12.1.x' into jetty-12.1.x-websocketMethodHolder
This commit is contained in:
commit
e486b7161f
|
@ -17,11 +17,9 @@ nav:
|
|||
- modules/programming-guide/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
- run:
|
||||
command: mvn install -ntp -B -Dcollector -Pfast -am -pl documentation/jetty
|
||||
scan:
|
||||
dir: documentation/jetty/target/collector
|
||||
- run: mvn install -ntp -B -Dcollector -Pfast -am -pl documentation/jetty
|
||||
scan: ./target/collector
|
||||
- scan:
|
||||
dir: jetty-core/jetty-server/src/main/java
|
||||
files: org/eclipse/jetty/server/CustomRequestLog.java
|
||||
base: modules/code/partials
|
||||
into: modules/code/partials
|
||||
|
|
|
@ -1049,6 +1049,31 @@ public class HTTPClientDocs
|
|||
// end::setConnectionPool[]
|
||||
}
|
||||
|
||||
public void preCreateConnections() throws Exception
|
||||
{
|
||||
// tag::preCreateConnections[]
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
|
||||
// For HTTP/1.1, you need to explicitly configure to initialize connections.
|
||||
if (httpClient.getTransport() instanceof HttpClientTransportOverHTTP http1)
|
||||
http1.setInitializeConnections(true);
|
||||
|
||||
// Create a dummy request to the server you want to pre-create connections to.
|
||||
Request request = httpClient.newRequest("https://host/");
|
||||
|
||||
// Resolve the destination for that request.
|
||||
Destination destination = httpClient.resolveDestination(request);
|
||||
|
||||
// Pre-create, for example, half of the connections.
|
||||
int preCreate = httpClient.getMaxConnectionsPerDestination() / 2;
|
||||
CompletableFuture<Void> completable = destination.getConnectionPool().preCreateConnections(preCreate);
|
||||
|
||||
// Wait for the connections to be created.
|
||||
completable.get(5, TimeUnit.SECONDS);
|
||||
// end::preCreateConnections[]
|
||||
}
|
||||
|
||||
public void unixDomain() throws Exception
|
||||
{
|
||||
// tag::unixDomain[]
|
||||
|
|
|
@ -95,7 +95,7 @@ public class SessionDocs
|
|||
org.eclipse.jetty.session.SessionHandler sessionHandler = new org.eclipse.jetty.session.SessionHandler();
|
||||
sessionHandler.setSessionCookie("SIMPLE");
|
||||
sessionHandler.setUsingCookies(true);
|
||||
sessionHandler.setUsingURLs(false);
|
||||
sessionHandler.setUsingUriParameters(false);
|
||||
sessionHandler.setSessionPath("/");
|
||||
server.setHandler(sessionHandler);
|
||||
sessionHandler.setHandler(new Handler.Abstract()
|
||||
|
|
|
@ -46,3 +46,4 @@
|
|||
* Migration Guides
|
||||
** xref:migration/94-to-10.adoc[]
|
||||
** xref:migration/11-to-12.adoc[]
|
||||
** xref:migration/12.0-to-12.1.adoc[]
|
||||
|
|
|
@ -158,7 +158,7 @@ Jetty's client library provides the following `ConnectionPool` implementations:
|
|||
* `DuplexConnectionPool`, historically the first implementation, only used by the HTTP/1.1 transport.
|
||||
* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a most recently used algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).
|
||||
* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with a round-robin algorithm.
|
||||
* `RandomRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
|
||||
* `RandomConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
|
||||
|
||||
The `ConnectionPool` implementation can be customized for each destination in by setting a `ConnectionPool.Factory` on the `HttpClientTransport`:
|
||||
|
||||
|
@ -167,6 +167,34 @@ The `ConnectionPool` implementation can be customized for each destination in by
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=setConnectionPool]
|
||||
----
|
||||
|
||||
[[connection-pool-precreate-connections]]
|
||||
=== Pre-Creating Connections
|
||||
|
||||
`ConnectionPool` offers the ability to pre-create connections by calling `ConnectionPool.preCreateConnections(int)`.
|
||||
|
||||
Pre-creating the connections saves the time and processing spent to establish the TCP connection, performing the TLS handshake (if necessary) and, for HTTP/2 and HTTP/3, perform the initial protocol setup.
|
||||
This is particularly important for HTTP/2 because in the initial protocol setup the server informs the client of the maximum number of concurrent requests per connection (otherwise assumed to be just `1` by the client).
|
||||
|
||||
The scenarios where pre-creating connections is useful are, for example:
|
||||
|
||||
* Load testing, where you want to prepare the system with connections already created to avoid paying of cost of connection setup.
|
||||
* Proxying scenarios, often in conjunction with the use of `RoundRobinConnectionPool` or `RandomConnectionPool`, where the proxy creates early the connections to the backend servers.
|
||||
|
||||
This is an example of how to pre-create connections:
|
||||
|
||||
[,java,indent=0]
|
||||
----
|
||||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=preCreateConnections]
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Pre-creating connections for secure HTTP/1.1 requires you to call `HttpClientTransportOverHTTP.setInitializeConnections(true)`, otherwise only the TCP connection is established, but the TLS handshake is not initiated.
|
||||
|
||||
To initialize connections for secure HTTP/1.1, the client sends an initial `OPTIONS * HTTP/1.1` request to the server.
|
||||
The server must be able to handle this request without closing the connection (in particular it must not add the `Connection: close` header in the response).
|
||||
====
|
||||
|
||||
[[request-processing]]
|
||||
== HttpClient Request Processing
|
||||
|
||||
|
|
|
@ -99,6 +99,15 @@
|
|||
| `org.eclipse.jetty.websocket.api.**WebSocketPolicy**` | `org.eclipse.jetty.websocket.api.**Configurable**`
|
||||
|===
|
||||
|
||||
== Server-Side Web Application APIs Changes
|
||||
|
||||
Jetty 12 introduced redesigned server-side APIs for web applications.
|
||||
In Jetty 11, these APIs were based on a mix of Jakarta Servlet APIs and Jetty Handler APIs, while in Jetty 12 they are solely based on Jetty Handler APIs.
|
||||
|
||||
In Jetty 12 you can now write web applications independently of the Servlet APIs, so you can migrate Jakarta Servlets to Jetty Handlers as explained in xref:servlet-to-handler[this section].
|
||||
|
||||
If you were already using the Jetty 11 Handler APIs, you can migrate them to the Jetty 12 Handler APIs as explained in xref:api-changes-handler[this section].
|
||||
|
||||
[[servlet-to-handler]]
|
||||
== Migrate Servlets to Jetty Handlers
|
||||
|
||||
|
@ -126,6 +135,8 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-source]
|
||||
----
|
||||
|
||||
Refer also to the `Content.Source` APIs detailed in xref:arch/io.adoc#content-source[this section].
|
||||
|
||||
=== Handler Response APIs
|
||||
[,java,indent=0]
|
||||
----
|
||||
|
@ -150,9 +161,48 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-trailers]
|
||||
----
|
||||
|
||||
Refer also to the `Content.Sink` APIs detailed in xref:arch/io.adoc#content-sink[this section].
|
||||
|
||||
[[api-changes]]
|
||||
== APIs Changes
|
||||
|
||||
[[api-changes-handler]]
|
||||
=== `Handler`
|
||||
|
||||
The server-side `Handler` class, and the APIs to use for request/response processing, have been redesigned in Jetty 12.
|
||||
|
||||
The Jetty 11 `Handler` method:
|
||||
|
||||
`Handler.handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)`
|
||||
|
||||
has been changed in Jetty 12 to:
|
||||
|
||||
`Handler.handle(Request request, Response response, Callback callback)`
|
||||
|
||||
The Jetty 11 `target` parameter has been removed, and in Jetty 12 it has been replaced by the information present in `Request.getHttpURI()`.
|
||||
|
||||
In Jetty 11, ``Handler``s would mark the fact that they handled the request, and therefore are producing a response, by calling `Request.setHandled(true)`.
|
||||
In Jetty 12, this is performed by returning `true` from the `Handler.handle(\...)` method, which also requires that the `Callback` parameter must be completed, either by succeeding it or failing it.
|
||||
|
||||
In Jetty 11, the `Handler.handle(\...)` method has a blocking semantic, while in Jetty 12 the `Handler.handle(\...)` method has an asynchronous semantic, thanks to the `Callback` parameter.
|
||||
This means that you can return from the `Handler.handle(\...)` method _before_ the response has been sent, similarly to what you can do with the Servlet APIs when you call `HttpServletRequest.startAsync()`.
|
||||
Similarly, in Jetty 11 after a call to `startAsync()` you must call `AsyncContext.complete()`, while in Jetty 12 you must complete the `Callback` parameter, either by succeeding it or failing it.
|
||||
|
||||
In Jetty 11, `AbstractHandler` provides a utility class to implement `Handler`.
|
||||
In Jetty 12, use `Handler.Abstract`.
|
||||
|
||||
In Jetty 11, the APIs to deal with request or response HTTP headers are based on either Jetty's `HttpFields`, or the Servlet APIs.
|
||||
In Jetty 12, the HTTP headers API are only based on `HttpFields`.
|
||||
Please refer to the `HttpFields` link:{javadoc-url}/org/eclipse/jetty/http/HttpFields.html[javadocs] for details.
|
||||
|
||||
In Jetty 11, the request content is accessed via `Request.getInputStream()` or `HttpServletRequest.getInputStream()`.
|
||||
In Jetty 12, the `Request` object itself _is-a_ `Content.Source` that can be read as explained in xref:arch/io.adoc#content-source[this section].
|
||||
In Jetty 12, you can use `Content.Source.asInputStream(request)` to obtain an `InputStream` and minimize the changes to your code, but remember that `InputStream` only provides blocking APIs, while `Content.Source` provides non-blocking APIs.
|
||||
|
||||
In Jetty 11, the response content is accessed via `Response.getOutputStream()` or `HttpServletResponse.getOutputStream()`.
|
||||
In Jetty 12, the `Response` object itself _is-a_ `Content.Sink` that can be written as explained in xref:arch/io.adoc#content-sink[this section].
|
||||
In Jetty 12, you can use `Content.Sink.asOutputStream(response)` to obtain an `OutputStream` and minimize the changes to your code, but remember that `OutputStream` only provides blocking APIs, while `Content.Sink` provides non-blocking APIs.
|
||||
|
||||
=== `HttpClient`
|
||||
|
||||
The Jetty 11 `Request.onResponseContentDemanded(Response.DemandedContentListener)` API has been replaced by `Request.onResponseContentSource(Response.ContentSourceListener)` in Jetty 12.
|
||||
|
@ -165,6 +215,8 @@ The Jetty 12 model is a "demand+pull" model: when the content is available, the
|
|||
|
||||
For more information about the new model, see xref:arch/io.adoc#content-source[this section].
|
||||
|
||||
Jetty 12 introduced the concept of low-level transport for high-level protocols, described in xref:client/io-arch.adoc#transport[this section].
|
||||
|
||||
=== WebSocket
|
||||
|
||||
The Jetty WebSocket APIs have been vastly simplified, and brought in line with the style of other APIs.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
= Migrating from Jetty 12.0.x to Jetty 12.1.x
|
||||
|
||||
[[api-changes]]
|
||||
== APIs Changes
|
||||
|
||||
=== `IteratingCallback`
|
||||
|
||||
Class `IteratingCallback` underwent refinements that changed the behavior of the `onCompleteFailure(Throwable)` method.
|
||||
|
||||
In Jetty 12.0.x, `IteratingCallback.onCompleteFailure(Throwable)` was called as soon as a failure was reported, without waiting the completion of the asynchronous operation (despite its name containing the word "complete").
|
||||
|
||||
For example, if a write operation performed with `IteratingCallback` was pending due to TCP congestion, and a timeout happened, `onCompleteFailure(Throwable)` was called as soon as the timeout happened, without waiting for the TCP congestion to resolve.
|
||||
|
||||
In Jetty 12.1.x, the same behavior is achieved by `IteratingCallback.onFailure(Throwable)`, so applications should review their usage of `IteratingCallback` and change the overrides of `onCompleteFailure(Throwable)` to override `onFailure(Throwable)` instead.
|
|
@ -57,8 +57,7 @@ public class ConscryptHTTP2ClientTest
|
|||
sslContextFactory.setProvider("Conscrypt");
|
||||
Conscrypt.setDefaultHostnameVerifier((certs, hostname, session) -> true);
|
||||
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
try
|
||||
try (HTTP2Client client = new HTTP2Client())
|
||||
{
|
||||
client.addBean(sslContextFactory);
|
||||
client.start();
|
||||
|
@ -97,10 +96,6 @@ public class ConscryptHTTP2ClientTest
|
|||
|
||||
assertTrue(latch.await(15, TimeUnit.SECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canConnectTo(String host, int port)
|
||||
|
|
|
@ -132,17 +132,12 @@ public class ConscryptHTTP2ServerTest
|
|||
ClientConnector clientConnector = new ClientConnector();
|
||||
clientConnector.setSslContextFactory(newClientSslContextFactory());
|
||||
HTTP2Client h2Client = new HTTP2Client(clientConnector);
|
||||
HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client));
|
||||
client.start();
|
||||
try
|
||||
try (HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client)))
|
||||
{
|
||||
client.start();
|
||||
int port = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
|
||||
ContentResponse contentResponse = client.GET("https://localhost:" + port);
|
||||
assertEquals(200, contentResponse.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,7 @@ public class JDK9HTTP2ClientTest
|
|||
|
||||
Assumptions.assumeTrue(canConnectTo(host, port));
|
||||
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
try
|
||||
try (HTTP2Client client = new HTTP2Client())
|
||||
{
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
client.addBean(sslContextFactory);
|
||||
|
@ -87,10 +86,6 @@ public class JDK9HTTP2ClientTest
|
|||
|
||||
latch.await(15, TimeUnit.SECONDS);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canConnectTo(String host, int port)
|
||||
|
|
|
@ -39,10 +39,14 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
@Override
|
||||
public boolean accept(Request request, Response response)
|
||||
{
|
||||
boolean is100 = response.getStatus() == HttpStatus.CONTINUE_100;
|
||||
boolean expect100 = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
boolean handled100 = request.getAttributes().containsKey(ATTRIBUTE);
|
||||
return (is100 || expect100) && !handled100;
|
||||
if (handled100)
|
||||
return false;
|
||||
boolean is100 = response.getStatus() == HttpStatus.CONTINUE_100;
|
||||
if (is100)
|
||||
return true;
|
||||
// Also handle non-100 responses, because we need to complete the request to complete the whole exchange.
|
||||
return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -104,7 +104,7 @@ import org.slf4j.LoggerFactory;
|
|||
* }</pre>
|
||||
*/
|
||||
@ManagedObject("The HTTP client")
|
||||
public class HttpClient extends ContainerLifeCycle
|
||||
public class HttpClient extends ContainerLifeCycle implements AutoCloseable
|
||||
{
|
||||
public static final String USER_AGENT = "Jetty/" + Jetty.VERSION;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
||||
|
@ -1141,4 +1141,10 @@ public class HttpClient extends ContainerLifeCycle
|
|||
sslContextFactory = getSslContextFactory();
|
||||
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,20 +26,49 @@ public class HttpClientConnectionFactory implements ClientConnectionFactory
|
|||
/**
|
||||
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
|
||||
*/
|
||||
public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory());
|
||||
public static final Info HTTP11 = new HTTP11();
|
||||
|
||||
private boolean initializeConnections;
|
||||
|
||||
/**
|
||||
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public boolean isInitializeConnections()
|
||||
{
|
||||
return initializeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public void setInitializeConnections(boolean initialize)
|
||||
{
|
||||
this.initializeConnections = initialize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, context);
|
||||
connection.setInitialize(isInitializeConnections());
|
||||
return customize(connection, context);
|
||||
}
|
||||
|
||||
private static class HTTP11 extends Info
|
||||
/**
|
||||
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
|
||||
* <p>Applications should prefer using the constant {@link HttpClientConnectionFactory#HTTP11}, unless they
|
||||
* need to customize the associated {@link HttpClientConnectionFactory}.</p>
|
||||
*/
|
||||
public static class HTTP11 extends Info
|
||||
{
|
||||
private static final List<String> protocols = List.of("http/1.1");
|
||||
|
||||
private HTTP11(ClientConnectionFactory factory)
|
||||
public HTTP11()
|
||||
{
|
||||
this(new HttpClientConnectionFactory());
|
||||
}
|
||||
|
||||
public HTTP11(ClientConnectionFactory factory)
|
||||
{
|
||||
super(factory);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.client.Destination;
|
|||
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
|
@ -37,7 +36,7 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
public static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverHTTP.class);
|
||||
|
||||
private final ClientConnectionFactory factory = new HttpClientConnectionFactory();
|
||||
private final HttpClientConnectionFactory factory = new HttpClientConnectionFactory();
|
||||
private int headerCacheSize = 1024;
|
||||
private boolean headerCacheCaseSensitive;
|
||||
|
||||
|
@ -79,25 +78,54 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
return connection;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The maximum allowed size in bytes for an HTTP header field cache")
|
||||
/**
|
||||
* @return the max size in bytes for the HTTP header field cache
|
||||
*/
|
||||
@ManagedAttribute("The maximum allowed size in bytes for the HTTP header field cache")
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
return headerCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param headerCacheSize the max size in bytes for the HTTP header field cache
|
||||
*/
|
||||
public void setHeaderCacheSize(int headerCacheSize)
|
||||
{
|
||||
this.headerCacheSize = headerCacheSize;
|
||||
}
|
||||
|
||||
@ManagedAttribute("Whether the header field cache is case sensitive")
|
||||
/**
|
||||
* @return whether the HTTP header field cache is case-sensitive
|
||||
*/
|
||||
@ManagedAttribute("Whether the HTTP header field cache is case-sensitive")
|
||||
public boolean isHeaderCacheCaseSensitive()
|
||||
{
|
||||
return headerCacheCaseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param headerCacheCaseSensitive whether the HTTP header field cache is case-sensitive
|
||||
*/
|
||||
public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
|
||||
{
|
||||
this.headerCacheCaseSensitive = headerCacheCaseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
@ManagedAttribute("Whether newly created connections should be initialized with an OPTIONS * HTTP/1.1 request")
|
||||
public boolean isInitializeConnections()
|
||||
{
|
||||
return factory.isInitializeConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public void setInitializeConnections(boolean initialize)
|
||||
{
|
||||
factory.setInitializeConnections(initialize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import org.eclipse.jetty.client.Authentication;
|
||||
import org.eclipse.jetty.client.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.BytesRequestContent;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.HttpRequestException;
|
||||
|
@ -35,6 +34,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.io.CyclicTimeouts;
|
||||
import org.eclipse.jetty.util.Attachable;
|
||||
import org.eclipse.jetty.util.NanoTime;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -146,7 +146,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
|
||||
// Make sure the path is there
|
||||
String path = request.getPath();
|
||||
if (path.trim().length() == 0)
|
||||
if (StringUtil.isBlank(path))
|
||||
{
|
||||
path = "/";
|
||||
request.path(path);
|
||||
|
@ -191,11 +191,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
|
||||
// Add content headers.
|
||||
Request.Content content = request.getBody();
|
||||
if (content == null)
|
||||
{
|
||||
request.body(new BytesRequestContent());
|
||||
}
|
||||
else
|
||||
if (content != null)
|
||||
{
|
||||
if (!headers.contains(HttpHeader.CONTENT_TYPE))
|
||||
{
|
||||
|
@ -203,10 +199,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
if (contentType == null)
|
||||
contentType = getHttpClient().getDefaultRequestContentType();
|
||||
if (contentType != null)
|
||||
{
|
||||
HttpField field = new HttpField(HttpHeader.CONTENT_TYPE, contentType);
|
||||
request.addHeader(field);
|
||||
}
|
||||
request.addHeader(new HttpField(HttpHeader.CONTENT_TYPE, contentType));
|
||||
}
|
||||
long contentLength = content.getLength();
|
||||
if (contentLength >= 0)
|
||||
|
@ -215,6 +208,9 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
request.addHeader(new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, contentLength));
|
||||
}
|
||||
}
|
||||
// RFC 9110, section 10.1.1.
|
||||
if (content == null || content.getLength() == 0)
|
||||
request.headers(h -> h.remove(HttpHeader.EXPECT));
|
||||
|
||||
// Cookies.
|
||||
StringBuilder cookies = convertCookies(request.getCookies(), null);
|
||||
|
@ -243,7 +239,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
{
|
||||
if (builder == null)
|
||||
builder = new StringBuilder();
|
||||
if (builder.length() > 0)
|
||||
if (!builder.isEmpty())
|
||||
builder.append("; ");
|
||||
builder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
}
|
||||
|
|
|
@ -315,13 +315,35 @@ public abstract class HttpReceiver
|
|||
* Method to be invoked when response content is available to be read.
|
||||
* <p>
|
||||
* This method takes care of ensuring the {@link Content.Source} passed to
|
||||
* {@link Response.ContentSourceListener#onContentSource(Response, Content.Source)} calls the
|
||||
* demand callback.
|
||||
* {@link Response.ContentSourceListener#onContentSource(Response, Content.Source)}
|
||||
* calls the demand callback.
|
||||
* The call to the demand callback is serialized with other events.
|
||||
*/
|
||||
protected void responseContentAvailable(HttpExchange exchange)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Invoking responseContentAvailable on {}", this);
|
||||
|
||||
invoker.run(() ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Executing responseContentAvailable on {}", this);
|
||||
|
||||
if (exchange.isResponseCompleteOrTerminated())
|
||||
return;
|
||||
|
||||
responseContentAvailable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be invoked when response content is available to be read.
|
||||
* <p>
|
||||
* This method directly invokes the demand callback, assuming the caller
|
||||
* is already serialized with other events.
|
||||
*/
|
||||
protected void responseContentAvailable()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response content available on {}", this);
|
||||
contentSource.onDataAvailable();
|
||||
}
|
||||
|
||||
|
@ -344,7 +366,7 @@ public abstract class HttpReceiver
|
|||
if (!exchange.responseComplete(null))
|
||||
return;
|
||||
|
||||
invoker.run(() ->
|
||||
Runnable successTask = () ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Executing responseSuccess on {}", this);
|
||||
|
@ -365,7 +387,12 @@ public abstract class HttpReceiver
|
|||
// Mark atomically the response as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
terminateResponse(exchange);
|
||||
}, afterSuccessTask);
|
||||
};
|
||||
|
||||
if (afterSuccessTask == null)
|
||||
invoker.run(successTask);
|
||||
else
|
||||
invoker.run(successTask, afterSuccessTask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -712,9 +739,9 @@ public abstract class HttpReceiver
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onDataAvailable on {}", this);
|
||||
// The demandCallback will call read() that will itself call
|
||||
// HttpReceiver.read(boolean) so it must be called by the invoker.
|
||||
invokeDemandCallback(true);
|
||||
// The onDataAvailable() method is only ever called
|
||||
// by the invoker so avoid using the invoker again.
|
||||
invokeDemandCallback(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -760,8 +787,8 @@ public abstract class HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
// The processDemand method is only ever called by the
|
||||
// invoker so there is no need to use the latter here.
|
||||
// The processDemand() method is only ever called
|
||||
// by the invoker so avoid using the invoker again.
|
||||
invokeDemandCallback(false);
|
||||
}
|
||||
|
||||
|
@ -769,20 +796,19 @@ public abstract class HttpReceiver
|
|||
{
|
||||
Runnable demandCallback = demandCallbackRef.getAndSet(null);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Invoking demand callback on {}", this);
|
||||
if (demandCallback != null)
|
||||
LOG.debug("Invoking demand callback {} on {}", demandCallback, this);
|
||||
if (demandCallback == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (invoke)
|
||||
invoker.run(demandCallback);
|
||||
else
|
||||
demandCallback.run();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
if (invoke)
|
||||
invoker.run(demandCallback);
|
||||
else
|
||||
demandCallback.run();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -528,7 +528,7 @@ public abstract class HttpSender
|
|||
action.run();
|
||||
|
||||
// Read the request content.
|
||||
chunk = content.read();
|
||||
chunk = content != null ? content.read() : Content.Chunk.EOF;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content {} for {}", chunk, request);
|
||||
|
@ -539,6 +539,7 @@ public abstract class HttpSender
|
|||
{
|
||||
// No content after the headers, demand.
|
||||
demanded = true;
|
||||
assert content != null;
|
||||
content.demand(this::succeeded);
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import org.eclipse.jetty.client.Connection;
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.HttpUpgrader;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
|
@ -40,6 +41,7 @@ import org.eclipse.jetty.client.transport.HttpRequest;
|
|||
import org.eclipse.jetty.client.transport.IConnection;
|
||||
import org.eclipse.jetty.client.transport.SendFailure;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -61,6 +63,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
|
|||
private final LongAdder bytesIn = new LongAdder();
|
||||
private final LongAdder bytesOut = new LongAdder();
|
||||
private long idleTimeout;
|
||||
private boolean initialize;
|
||||
|
||||
public HttpConnectionOverHTTP(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
|
@ -159,12 +162,46 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
|
|||
return delegate.send(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
|
||||
*/
|
||||
public boolean isInitialize()
|
||||
{
|
||||
return initialize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
|
||||
*/
|
||||
public void setInitialize(boolean initialize)
|
||||
{
|
||||
this.initialize = initialize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
fillInterested();
|
||||
promise.succeeded(this);
|
||||
boolean initialize = isInitialize();
|
||||
if (initialize)
|
||||
{
|
||||
Destination destination = getHttpDestination();
|
||||
Request request = destination.getHttpClient().newRequest(destination.getOrigin().asString())
|
||||
.method(HttpMethod.OPTIONS)
|
||||
.path("*");
|
||||
send(request, result ->
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
promise.succeeded(this);
|
||||
else
|
||||
promise.failed(result.getFailure());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.succeeded(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -79,7 +79,9 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
else
|
||||
{
|
||||
responseContentAvailable();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -703,7 +703,7 @@ public class ConnectionPoolTest
|
|||
assertThat(connectionPool.toString(), not(nullValue()));
|
||||
}
|
||||
|
||||
private static class ConnectionPoolFactory
|
||||
public static class ConnectionPoolFactory
|
||||
{
|
||||
private final String name;
|
||||
private final ConnectionPool.Factory factory;
|
||||
|
|
|
@ -31,12 +31,11 @@ public class HttpClientJMXTest
|
|||
@Test
|
||||
public void testHttpClientName() throws Exception
|
||||
{
|
||||
String name = "foo";
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.setName(name);
|
||||
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
String name = "foo";
|
||||
httpClient.setName(name);
|
||||
|
||||
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
|
||||
// Adding MBeanContainer as a bean will trigger the registration of MBeans.
|
||||
|
@ -59,9 +58,5 @@ public class HttpClientJMXTest
|
|||
assertEquals(name, oName.getKeyProperty("context"));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
}
|
||||
else
|
||||
{
|
||||
responseContentAvailable();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,6 +109,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
|
||||
void content(Content.Chunk chunk)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return;
|
||||
if (this.chunk != null)
|
||||
throw new IllegalStateException();
|
||||
// Retain the chunk because it is stored for later reads.
|
||||
|
|
|
@ -50,8 +50,6 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP2.class);
|
||||
|
||||
private final Runnable onDataAvailableTask = new Invocable.ReadyTask(Invocable.InvocationType.NON_BLOCKING, this::responseContentAvailable);
|
||||
|
||||
public HttpReceiverOverHTTP2(HttpChannel channel)
|
||||
{
|
||||
super(channel);
|
||||
|
@ -213,7 +211,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return null;
|
||||
return onDataAvailableTask;
|
||||
return new Invocable.ReadyTask(Invocable.InvocationType.NON_BLOCKING, () -> responseContentAvailable(exchange));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -100,7 +100,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
*} </pre>
|
||||
*/
|
||||
@ManagedObject
|
||||
public class HTTP2Client extends ContainerLifeCycle
|
||||
public class HTTP2Client extends ContainerLifeCycle implements AutoCloseable
|
||||
{
|
||||
private final ClientConnector connector;
|
||||
private int inputBufferSize = IO.DEFAULT_BUFFER_SIZE;
|
||||
|
@ -493,4 +493,10 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
<skip>${h2spec.skip}</skip>
|
||||
<junitPackage>org.eclipse.jetty.h2spec</junitPackage>
|
||||
<skipNoDockerAvailable>true</skipNoDockerAvailable>
|
||||
<reportsDirectory>${project.build.directory}/h2spec-reports</reportsDirectory>
|
||||
<excludeSpecs>
|
||||
<excludeSpec>3.5 - Sends invalid connection preface</excludeSpec>
|
||||
</excludeSpecs>
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
|
@ -33,6 +34,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.eclipse.jetty.client.AsyncRequestContent;
|
||||
import org.eclipse.jetty.client.CompletableResponseListener;
|
||||
import org.eclipse.jetty.client.Connection;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
|
@ -103,27 +106,26 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
public void testPropertiesAreForwarded() throws Exception
|
||||
{
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
|
||||
Executor executor = new QueuedThreadPool();
|
||||
httpClient.setExecutor(executor);
|
||||
httpClient.setConnectTimeout(13);
|
||||
httpClient.setIdleTimeout(17);
|
||||
httpClient.setUseInputDirectByteBuffers(false);
|
||||
httpClient.setUseOutputDirectByteBuffers(false);
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client)))
|
||||
{
|
||||
Executor executor = new QueuedThreadPool();
|
||||
httpClient.setExecutor(executor);
|
||||
httpClient.setConnectTimeout(13);
|
||||
httpClient.setIdleTimeout(17);
|
||||
httpClient.setUseInputDirectByteBuffers(false);
|
||||
httpClient.setUseOutputDirectByteBuffers(false);
|
||||
|
||||
httpClient.start();
|
||||
|
||||
assertTrue(http2Client.isStarted());
|
||||
assertSame(httpClient.getExecutor(), http2Client.getExecutor());
|
||||
assertSame(httpClient.getScheduler(), http2Client.getScheduler());
|
||||
assertSame(httpClient.getByteBufferPool(), http2Client.getByteBufferPool());
|
||||
assertEquals(httpClient.getConnectTimeout(), http2Client.getConnectTimeout());
|
||||
assertEquals(httpClient.getIdleTimeout(), http2Client.getIdleTimeout());
|
||||
assertEquals(httpClient.isUseInputDirectByteBuffers(), http2Client.isUseInputDirectByteBuffers());
|
||||
assertEquals(httpClient.isUseOutputDirectByteBuffers(), http2Client.isUseOutputDirectByteBuffers());
|
||||
|
||||
httpClient.stop();
|
||||
httpClient.start();
|
||||
|
||||
assertTrue(http2Client.isStarted());
|
||||
assertSame(httpClient.getExecutor(), http2Client.getExecutor());
|
||||
assertSame(httpClient.getScheduler(), http2Client.getScheduler());
|
||||
assertSame(httpClient.getByteBufferPool(), http2Client.getByteBufferPool());
|
||||
assertEquals(httpClient.getConnectTimeout(), http2Client.getConnectTimeout());
|
||||
assertEquals(httpClient.getIdleTimeout(), http2Client.getIdleTimeout());
|
||||
assertEquals(httpClient.isUseInputDirectByteBuffers(), http2Client.isUseInputDirectByteBuffers());
|
||||
assertEquals(httpClient.isUseOutputDirectByteBuffers(), http2Client.isUseOutputDirectByteBuffers());
|
||||
}
|
||||
assertTrue(http2Client.isStopped());
|
||||
}
|
||||
|
||||
|
@ -790,22 +792,53 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
assertThat(onContentSourceErrorRef.get(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestContentResponseContent() throws Exception
|
||||
{
|
||||
start(new Handler.Abstract()
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, org.eclipse.jetty.server.Response response, Callback callback)
|
||||
{
|
||||
Content.copy(request, response, callback);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
AsyncRequestContent content = new AsyncRequestContent();
|
||||
var request = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||
.method(HttpMethod.POST)
|
||||
.body(content);
|
||||
CompletableFuture<ContentResponse> completable = new CompletableResponseListener(request).send();
|
||||
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
content.write(false, ByteBuffer.allocate(512), Callback.NOOP);
|
||||
Thread.sleep(10);
|
||||
}
|
||||
content.close();
|
||||
|
||||
ContentResponse response = completable.get(15, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("external")
|
||||
public void testExternalServer() throws Exception
|
||||
{
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
HTTP2Client http2Client = new HTTP2Client(clientConnector);
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
clientConnector.setSslContextFactory(sslContextFactory);
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
|
||||
Executor executor = new QueuedThreadPool();
|
||||
clientConnector.setExecutor(executor);
|
||||
httpClient.start();
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client)))
|
||||
{
|
||||
Executor executor = new QueuedThreadPool();
|
||||
clientConnector.setExecutor(executor);
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
clientConnector.setSslContextFactory(sslContextFactory);
|
||||
httpClient.start();
|
||||
|
||||
ContentResponse response = httpClient.GET("https://webtide.com/");
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
httpClient.stop();
|
||||
ContentResponse response = httpClient.GET("https://webtide.com/");
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,10 +73,9 @@ public class ResponseTrailerTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
http2Client.start();
|
||||
try
|
||||
try (HTTP2Client http2Client = new HTTP2Client())
|
||||
{
|
||||
http2Client.start();
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
InetSocketAddress address = new InetSocketAddress(host, port);
|
||||
|
@ -116,9 +115,5 @@ public class ResponseTrailerTest extends AbstractTest
|
|||
assertNotNull(frame);
|
||||
assertTrue(frame.getMetaData().isResponse());
|
||||
}
|
||||
finally
|
||||
{
|
||||
http2Client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
|
|||
if (exchange == null)
|
||||
return;
|
||||
|
||||
responseContentAvailable();
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -125,7 +125,7 @@ import org.slf4j.LoggerFactory;
|
|||
* <p>HTTP/3+QUIC support is experimental and not suited for production use.
|
||||
* APIs may change incompatibly between releases.</p>
|
||||
*/
|
||||
public class HTTP3Client extends ContainerLifeCycle
|
||||
public class HTTP3Client extends ContainerLifeCycle implements AutoCloseable
|
||||
{
|
||||
public static final String CLIENT_CONTEXT_KEY = HTTP3Client.class.getName();
|
||||
public static final String SESSION_LISTENER_CONTEXT_KEY = CLIENT_CONTEXT_KEY + ".listener";
|
||||
|
@ -217,4 +217,10 @@ public class HTTP3Client extends ContainerLifeCycle
|
|||
{
|
||||
return container.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,19 +54,13 @@ public class ExternalServerTest
|
|||
SslContextFactory.Client sslClient = new SslContextFactory.Client();
|
||||
ClientQuicConfiguration quicConfig = new ClientQuicConfiguration(sslClient, null);
|
||||
HTTP3Client client = new HTTP3Client(quicConfig);
|
||||
HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3(client);
|
||||
HttpClient httpClient = new HttpClient(transport);
|
||||
httpClient.start();
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP3(client)))
|
||||
{
|
||||
httpClient.start();
|
||||
URI uri = URI.create("https://maven-central-eu.storage-download.googleapis.com/maven2/org/apache/maven/maven-parent/38/maven-parent-38.pom");
|
||||
ContentResponse response = httpClient.newRequest(uri).send();
|
||||
assertThat(response.getContentAsString(), containsString("<artifactId>maven-parent</artifactId>"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -75,10 +69,9 @@ public class ExternalServerTest
|
|||
{
|
||||
SslContextFactory.Client sslClient = new SslContextFactory.Client();
|
||||
ClientQuicConfiguration quicConfig = new ClientQuicConfiguration(sslClient, null);
|
||||
HTTP3Client client = new HTTP3Client(quicConfig);
|
||||
client.start();
|
||||
try
|
||||
try (HTTP3Client client = new HTTP3Client(quicConfig))
|
||||
{
|
||||
client.start();
|
||||
// Well-known HTTP/3 servers to try.
|
||||
// HostPort hostPort = new HostPort("maven-central-eu.storage-download.googleapis.com:443");
|
||||
HostPort hostPort = new HostPort("google.com:443");
|
||||
|
@ -137,9 +130,5 @@ public class ExternalServerTest
|
|||
|
||||
assertTrue(requestLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,14 +444,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
return AuthenticationState.SEND_FAILURE;
|
||||
}
|
||||
|
||||
// TODO: No session API to work this out?
|
||||
/*
|
||||
if (request.isRequestedSessionIdFromURL())
|
||||
String sessionIdFrom = (String)request.getAttribute("org.eclipse.jetty.session.RequestedSession.sessionIdFrom");
|
||||
if (sessionIdFrom != null && !sessionIdFrom.startsWith("cookie"))
|
||||
{
|
||||
sendError(req, res, cb, "Session ID must be a cookie to support OpenID authentication");
|
||||
return Authentication.SEND_FAILURE;
|
||||
sendError(request, response, cb, "Session ID must be a cookie to support OpenID authentication");
|
||||
return AuthenticationState.SEND_FAILURE;
|
||||
}
|
||||
*/
|
||||
|
||||
// Handle a request for authentication.
|
||||
if (isJSecurityCheck(uri))
|
||||
|
|
|
@ -393,11 +393,12 @@ public abstract class ProxyHandler extends Handler.Abstract
|
|||
|
||||
private boolean hasContent(Request clientToProxyRequest)
|
||||
{
|
||||
if (clientToProxyRequest.getLength() > 0)
|
||||
long contentLength = clientToProxyRequest.getLength();
|
||||
if (contentLength == 0)
|
||||
return false;
|
||||
if (contentLength > 0)
|
||||
return true;
|
||||
HttpFields headers = clientToProxyRequest.getHeaders();
|
||||
return headers.get(HttpHeader.CONTENT_TYPE) != null ||
|
||||
headers.get(HttpHeader.TRANSFER_ENCODING) != null;
|
||||
return clientToProxyRequest.getHeaders().get(HttpHeader.TRANSFER_ENCODING) != null;
|
||||
}
|
||||
|
||||
private boolean expects100Continue(Request clientToProxyRequest)
|
||||
|
|
|
@ -396,5 +396,4 @@ public class JAASLdapLoginServiceTest extends AbstractLdapTestUnit
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -165,6 +165,7 @@ public class HttpConfiguration implements Dumpable
|
|||
_redirectUriCompliance = config._redirectUriCompliance;
|
||||
_serverAuthority = config._serverAuthority;
|
||||
_localAddress = config._localAddress;
|
||||
_maxUnconsumedRequestContentReads = config._maxUnconsumedRequestContentReads;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -501,8 +502,9 @@ public class HttpConfiguration implements Dumpable
|
|||
|
||||
/**
|
||||
* @return the set of HTTP methods of requests that can be decoded as
|
||||
* {@code x-www-form-urlencoded} content.
|
||||
* {@code application/x-www-form-urlencoded} content.
|
||||
*/
|
||||
@ManagedAttribute("The methods that support application/x-www-form-urlencoded content")
|
||||
public Set<String> getFormEncodedMethods()
|
||||
{
|
||||
return _formEncodedMethods.keySet();
|
||||
|
@ -587,6 +589,7 @@ public class HttpConfiguration implements Dumpable
|
|||
_minResponseDataRate = bytesPerSecond;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The HTTP compliance mode")
|
||||
public HttpCompliance getHttpCompliance()
|
||||
{
|
||||
return _httpCompliance;
|
||||
|
@ -597,6 +600,7 @@ public class HttpConfiguration implements Dumpable
|
|||
_httpCompliance = httpCompliance;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The URI compliance mode")
|
||||
public UriCompliance getUriCompliance()
|
||||
{
|
||||
return _uriCompliance;
|
||||
|
@ -624,6 +628,7 @@ public class HttpConfiguration implements Dumpable
|
|||
* @return The CookieCompliance used for parsing request {@code Cookie} headers.
|
||||
* @see #getResponseCookieCompliance()
|
||||
*/
|
||||
@ManagedAttribute("The HTTP request cookie compliance mode")
|
||||
public CookieCompliance getRequestCookieCompliance()
|
||||
{
|
||||
return _requestCookieCompliance;
|
||||
|
@ -641,6 +646,7 @@ public class HttpConfiguration implements Dumpable
|
|||
* @return The CookieCompliance used for generating response {@code Set-Cookie} headers
|
||||
* @see #getRequestCookieCompliance()
|
||||
*/
|
||||
@ManagedAttribute("The HTTP response cookie compliance mode")
|
||||
public CookieCompliance getResponseCookieCompliance()
|
||||
{
|
||||
return _responseCookieCompliance;
|
||||
|
@ -657,6 +663,7 @@ public class HttpConfiguration implements Dumpable
|
|||
/**
|
||||
* @return the {@link MultiPartCompliance} used for validating multipart form syntax.
|
||||
*/
|
||||
@ManagedAttribute("The multipart/form-data compliance mode")
|
||||
public MultiPartCompliance getMultiPartCompliance()
|
||||
{
|
||||
return _multiPartCompliance;
|
||||
|
@ -784,7 +791,7 @@ public class HttpConfiguration implements Dumpable
|
|||
*
|
||||
* @return Returns the connection server authority (name/port) or null
|
||||
*/
|
||||
@ManagedAttribute("The server authority if none provided by requests")
|
||||
@ManagedAttribute("The server authority override")
|
||||
public HostPort getServerAuthority()
|
||||
{
|
||||
return _serverAuthority;
|
||||
|
@ -852,6 +859,7 @@ public class HttpConfiguration implements Dumpable
|
|||
"requestHeaderSize=" + _requestHeaderSize,
|
||||
"responseHeaderSize=" + _responseHeaderSize,
|
||||
"headerCacheSize=" + _headerCacheSize,
|
||||
"headerCacheCaseSensitive=" + _headerCacheCaseSensitive,
|
||||
"secureScheme=" + _secureScheme,
|
||||
"securePort=" + _securePort,
|
||||
"idleTimeout=" + _idleTimeout,
|
||||
|
@ -861,12 +869,21 @@ public class HttpConfiguration implements Dumpable
|
|||
"delayDispatchUntilContent=" + _delayDispatchUntilContent,
|
||||
"persistentConnectionsEnabled=" + _persistentConnectionsEnabled,
|
||||
"maxErrorDispatches=" + _maxErrorDispatches,
|
||||
"useInputDirectByteBuffers=" + _useInputDirectByteBuffers,
|
||||
"useOutputDirectByteBuffers=" + _useOutputDirectByteBuffers,
|
||||
"minRequestDataRate=" + _minRequestDataRate,
|
||||
"minResponseDataRate=" + _minResponseDataRate,
|
||||
"httpCompliance=" + _httpCompliance,
|
||||
"uriCompliance=" + _uriCompliance,
|
||||
"redirectUriCompliance=" + _redirectUriCompliance,
|
||||
"requestCookieCompliance=" + _requestCookieCompliance,
|
||||
"responseCookieCompliance=" + _responseCookieCompliance,
|
||||
"multiPartCompliance=" + _multiPartCompliance,
|
||||
"notifyRemoteAsyncErrors=" + _notifyRemoteAsyncErrors,
|
||||
"relativeRedirectAllowed=" + _relativeRedirectAllowed
|
||||
"relativeRedirectAllowed=" + _relativeRedirectAllowed,
|
||||
"serverAuthority=" + _serverAuthority,
|
||||
"localAddress=" + _localAddress,
|
||||
"maxUnconsumedRequestContentReads=" + _maxUnconsumedRequestContentReads
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1117,8 +1117,6 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
*/
|
||||
public static class ChannelResponse implements Response, Callback
|
||||
{
|
||||
private static final CompletableFuture<Void> UNEXPECTED_100_CONTINUE = CompletableFuture.failedFuture(new IllegalStateException("100 not expected"));
|
||||
private static final CompletableFuture<Void> COMMITTED_100_CONTINUE = CompletableFuture.failedFuture(new IllegalStateException("Committed"));
|
||||
private final ChannelRequest _request;
|
||||
private final ResponseHttpFields _httpFields;
|
||||
protected int _status;
|
||||
|
@ -1408,12 +1406,14 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
if (status == HttpStatus.CONTINUE_100)
|
||||
{
|
||||
if (!httpChannelState._expects100Continue)
|
||||
return UNEXPECTED_100_CONTINUE;
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("100 not expected"));
|
||||
if (_request.getLength() == 0)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
httpChannelState._expects100Continue = false;
|
||||
}
|
||||
|
||||
if (_httpFields.isCommitted())
|
||||
return status == HttpStatus.CONTINUE_100 ? COMMITTED_100_CONTINUE : CompletableFuture.failedFuture(new IllegalStateException("Committed"));
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Committed"));
|
||||
if (_writeCallback != null)
|
||||
return CompletableFuture.failedFuture(new WritePendingException());
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
@ -981,24 +982,6 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
_usingUriParameters = usingUriParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #isUsingUriParameters()} instead, will be removed in Jetty 12.1.0
|
||||
*/
|
||||
@Deprecated(since = "12.0.1", forRemoval = true)
|
||||
public boolean isUsingURLs()
|
||||
{
|
||||
return isUsingUriParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #setUsingUriParameters(boolean)} instead, will be removed in Jetty 12.1.0
|
||||
*/
|
||||
@Deprecated(since = "12.0.1", forRemoval = true)
|
||||
public void setUsingURLs(boolean usingURLs)
|
||||
{
|
||||
setUsingUriParameters(usingURLs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Session, using the requested session id if possible.
|
||||
* @param request the inbound request
|
||||
|
@ -1229,7 +1212,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
{
|
||||
//Cookie[] cookies = request.getCookies();
|
||||
List<HttpCookie> cookies = Request.getCookies(request);
|
||||
if (cookies != null && cookies.size() > 0)
|
||||
if (!cookies.isEmpty())
|
||||
{
|
||||
final String sessionCookie = getSessionCookie();
|
||||
for (HttpCookie cookie : cookies)
|
||||
|
@ -1279,7 +1262,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
}
|
||||
|
||||
if (ids == null)
|
||||
return NO_REQUESTED_SESSION;
|
||||
return RequestedSession.NO_REQUESTED_SESSION;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Got Session IDs {} from cookies {}", ids, cookieIds);
|
||||
|
@ -1319,8 +1302,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
{
|
||||
//we already have a valid session and now have a duplicate ID for it
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(duplicateSession(
|
||||
requestedSessionId, true, requestedSessionIdFromCookie,
|
||||
LOG.debug(duplicateSession(requestedSessionId, requestedSessionIdFromCookie,
|
||||
id, false, i < cookieIds));
|
||||
}
|
||||
else
|
||||
|
@ -1350,26 +1332,27 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
}
|
||||
|
||||
throw new BadMessageException(duplicateSession(
|
||||
requestedSessionId, true, requestedSessionIdFromCookie,
|
||||
requestedSessionId, requestedSessionIdFromCookie,
|
||||
id, true, i < cookieIds));
|
||||
}
|
||||
else if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug(duplicateSession(
|
||||
requestedSessionId, true, requestedSessionIdFromCookie,
|
||||
requestedSessionId, requestedSessionIdFromCookie,
|
||||
id, false, i < cookieIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RequestedSession((session != null && session.isValid()) ? session : null, requestedSessionId, requestedSessionIdFromCookie);
|
||||
return new RequestedSession((session != null && session.isValid()) ? session : null, requestedSessionId,
|
||||
requestedSessionIdFromCookie ? RequestedSession.ID_FROM_COOKIE : RequestedSession.ID_FROM_URI_PARAMETER);
|
||||
}
|
||||
|
||||
private static String duplicateSession(String id0, boolean valid0, boolean cookie0, String id1, boolean valid1, boolean cookie1)
|
||||
private static String duplicateSession(String id0, boolean fromCookie0, String id1, boolean valid1, boolean fromCookie1)
|
||||
{
|
||||
return "Duplicate sessions: %s[%s,%s] & %s[%s,%s]".formatted(
|
||||
id0, valid0 ? "valid" : "unknown", cookie0 ? "cookie" : "param",
|
||||
id1, valid1 ? "valid" : "unknown", cookie1 ? "cookie" : "param");
|
||||
id0, "valid", fromCookie0 ? RequestedSession.ID_FROM_COOKIE : RequestedSession.ID_FROM_URI_PARAMETER,
|
||||
id1, valid1 ? "valid" : "unknown", fromCookie1 ? RequestedSession.ID_FROM_COOKIE : RequestedSession.ID_FROM_URI_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1379,12 +1362,89 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
{
|
||||
_sessionCache.shutdown();
|
||||
}
|
||||
|
||||
public record RequestedSession(ManagedSession session, String sessionId, boolean sessionIdFromCookie)
|
||||
{
|
||||
}
|
||||
|
||||
private static final RequestedSession NO_REQUESTED_SESSION = new RequestedSession(null, null, false);
|
||||
/**
|
||||
* Details of the requested session.
|
||||
* Session implementations should make an instance of this record available as a hidden (not in name set) request
|
||||
* attribute for the name "org.eclipse.jetty.session.AbstractSessionManager$RequestedSession"
|
||||
* @param session The {@link Session} associated with the ID, which may have been invalidated or changed ID since the
|
||||
* request was received; or {@code null} if no session existed matching the requested ID.
|
||||
* @param sessionId The requested session ID.
|
||||
* @param sessionIdFrom A {@link String} representing the source of the session ID. Common values include:
|
||||
* {@link #ID_FROM_COOKIE} or {@link #ID_FROM_URI_PARAMETER} if there is no ID.
|
||||
*/
|
||||
public record RequestedSession(ManagedSession session, String sessionId, String sessionIdFrom)
|
||||
{
|
||||
public static final RequestedSession NO_REQUESTED_SESSION = new RequestedSession(null, null, null);
|
||||
public static final String ATTRIBUTE = "org.eclipse.jetty.session.RequestedSession";
|
||||
public static final String ID_FROM_COOKIE = "cookie";
|
||||
public static final String ID_FROM_URI_PARAMETER = "uri";
|
||||
|
||||
/**
|
||||
* Get the {@code RequestedSession} by attribute
|
||||
* @param request The attributes to query
|
||||
* @return The found {@code RequestedSession} or {@link #NO_REQUESTED_SESSION} if none found. Never {@code null}.
|
||||
*/
|
||||
public static RequestedSession byAttribute(Attributes request)
|
||||
{
|
||||
RequestedSession requestedSession = (RequestedSession)request.getAttribute(ATTRIBUTE);
|
||||
return requestedSession == null ? NO_REQUESTED_SESSION : requestedSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name An attribute name
|
||||
* @return {@code true} if the attribute name is applicable to a requested session.
|
||||
* @see #getAttribute(String)
|
||||
*/
|
||||
public static boolean isApplicableAttribute(String name)
|
||||
{
|
||||
return name != null && name.startsWith(ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes asssociated with this requested session:
|
||||
* <ul>
|
||||
* <li>`org.eclipse.jetty.session.RequestedSession` this instance.</li>
|
||||
* <li>`org.eclipse.jetty.session.RequestedSession.session` the {@link #session()}.</li>
|
||||
* <li>`org.eclipse.jetty.session.RequestedSession.sessionId` the {@link #sessionId()}.</li>
|
||||
* <li>`org.eclipse.jetty.session.RequestedSession.sessionIdFrom` the {@link #sessionIdFrom()}.</li>
|
||||
* </ul>
|
||||
* @param name An attributed name
|
||||
* @return the attribute value or {@code null}
|
||||
*/
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (name == null || name.length() < ATTRIBUTE.length())
|
||||
return null;
|
||||
|
||||
if (ATTRIBUTE.equals(name))
|
||||
return this;
|
||||
|
||||
if (name.startsWith(ATTRIBUTE) && name.charAt(ATTRIBUTE.length()) == '.')
|
||||
{
|
||||
return switch (name.substring(ATTRIBUTE.length() + 1))
|
||||
{
|
||||
case "session" -> session();
|
||||
case "sessionId" -> sessionId();
|
||||
case "sessionIdFrom" -> sessionIdFrom();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this {@code RequestedSession} ID is from a particular session source
|
||||
* @param source A {@link String} representing the source of the session ID. Common values include:
|
||||
* {@link #ID_FROM_COOKIE} or {@link #ID_FROM_URI_PARAMETER} if there is no ID.
|
||||
* @return {@code True} iff this {@code RequestedSession} ID is from the source.
|
||||
*/
|
||||
public boolean isSessionIdFrom(String source)
|
||||
{
|
||||
return source != null && source.equals(sessionIdFrom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A session cookie is marked as secure IFF any of the following conditions are true:
|
||||
|
|
|
@ -82,10 +82,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
return null;
|
||||
}
|
||||
|
||||
private class SessionRequest extends Request.Wrapper
|
||||
public class SessionRequest extends Request.Wrapper
|
||||
{
|
||||
private final AtomicReference<ManagedSession> _session = new AtomicReference<>();
|
||||
private String _requestedSessionId;
|
||||
RequestedSession _requestedSession;
|
||||
private Response _response;
|
||||
|
||||
public SessionRequest(Request request)
|
||||
|
@ -103,6 +103,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
return _session.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (RequestedSession.isApplicableAttribute(name))
|
||||
return _requestedSession.getAttribute(name);
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
|
@ -113,7 +121,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
|
||||
if (session == null && create)
|
||||
{
|
||||
newSession(this, _requestedSessionId, this::setManagedSession);
|
||||
newSession(this, _requestedSession.sessionId(), this::setManagedSession);
|
||||
session = _session.get();
|
||||
HttpCookie cookie = getSessionCookie(session, getConnectionMetaData().isSecure());
|
||||
if (cookie != null)
|
||||
|
@ -126,10 +134,8 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
public boolean process(Handler handler, Response response, Callback callback) throws Exception
|
||||
{
|
||||
_response = response;
|
||||
|
||||
RequestedSession requestedSession = resolveRequestedSessionId(this);
|
||||
_requestedSessionId = requestedSession.sessionId();
|
||||
ManagedSession session = requestedSession.session();
|
||||
_requestedSession = resolveRequestedSessionId(this);
|
||||
ManagedSession session = _requestedSession.session();
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -148,8 +149,21 @@ public class SessionHandlerTest
|
|||
{
|
||||
if (session.isNew())
|
||||
out.append("New\n");
|
||||
|
||||
RequestedSession requestedSession = RequestedSession.byAttribute(request);
|
||||
|
||||
out.append("RequestedSessionIdFromCookie: ")
|
||||
.append(requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_COOKIE))
|
||||
.append('\n');
|
||||
out.append("RequestedSessionIdFromURL: ")
|
||||
.append(requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_URI_PARAMETER))
|
||||
.append('\n');
|
||||
for (String name : session.getAttributeNameSet())
|
||||
out.append("Attribute ").append(name).append(" = ").append(session.getAttribute(name)).append('\n');
|
||||
out.append("Attribute ")
|
||||
.append(name)
|
||||
.append(" = ")
|
||||
.append(session.getAttribute(name))
|
||||
.append('\n');
|
||||
out.append("URI [")
|
||||
.append(session.encodeURI(request, "/some/path", request.getHeaders().contains(HttpHeader.COOKIE)))
|
||||
.append("]");
|
||||
|
@ -499,6 +513,8 @@ public class SessionHandlerTest
|
|||
assertThat(response.getStatus(), equalTo(200));
|
||||
content = response.getContent();
|
||||
assertThat(content, containsString("Session=" + id.substring(0, id.indexOf(".node0"))));
|
||||
assertThat(content, containsString("RequestedSessionIdFromCookie: true"));
|
||||
assertThat(content, containsString("RequestedSessionIdFromURL: false"));
|
||||
assertThat(content, containsString("URI [/some/path]")); // Cookies known to be in use
|
||||
|
||||
// Get with parameter
|
||||
|
@ -513,6 +529,8 @@ public class SessionHandlerTest
|
|||
assertThat(response.getStatus(), equalTo(200));
|
||||
content = response.getContent();
|
||||
assertThat(content, containsString("Session=" + id.substring(0, id.indexOf(".node0"))));
|
||||
assertThat(content, containsString("RequestedSessionIdFromCookie: false"));
|
||||
assertThat(content, containsString("RequestedSessionIdFromURL: true"));
|
||||
assertThat(content, containsString("URI [/some/path;session_id=%s]".formatted(id))); // Cookies not in use
|
||||
|
||||
// Get with both, but param wrong
|
||||
|
|
|
@ -687,8 +687,9 @@ public class StartArgs
|
|||
|
||||
// TODO module path
|
||||
|
||||
for (Prop property : environment.getProperties())
|
||||
cmd.addArg(property.key, property.value);
|
||||
Props props = environment.getProperties();
|
||||
for (Prop property : props)
|
||||
cmd.addArg(property.key, props.expand(property.value));
|
||||
|
||||
for (Path xmlFile : environment.getXmlFiles())
|
||||
cmd.addArg(xmlFile.toAbsolutePath().toString());
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.start;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
@ -24,7 +23,6 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -67,7 +65,7 @@ public class MainTest
|
|||
public void testListConfig() throws Exception
|
||||
{
|
||||
List<String> cmdLineArgs = new ArrayList<>();
|
||||
File testJettyHome = MavenTestingUtils.getTestResourceDir("dist-home");
|
||||
Path testJettyHome = MavenPaths.findTestResourceDir("dist-home");
|
||||
cmdLineArgs.add("user.dir=" + testJettyHome);
|
||||
cmdLineArgs.add("-Duser.dir=foo"); // used to test "source" display on "Java Environment"
|
||||
cmdLineArgs.add("jetty.home=" + testJettyHome);
|
||||
|
@ -99,8 +97,8 @@ public class MainTest
|
|||
public void testUnknownDistroCommand() throws Exception
|
||||
{
|
||||
List<String> cmdLineArgs = new ArrayList<>();
|
||||
File testJettyHome = MavenTestingUtils.getTestResourceDir("dist-home");
|
||||
Path testJettyBase = MavenTestingUtils.getTargetTestingPath("base-example-unknown");
|
||||
Path testJettyHome = MavenPaths.findTestResourceDir("dist-home");
|
||||
Path testJettyBase = MavenPaths.targetTestDir("base-example-unknown");
|
||||
FS.ensureDirectoryExists(testJettyBase);
|
||||
Path zedIni = testJettyBase.resolve("start.d/zed.ini");
|
||||
FS.ensureDirectoryExists(zedIni.getParent());
|
||||
|
@ -159,7 +157,7 @@ public class MainTest
|
|||
{
|
||||
List<String> cmdLineArgs = new ArrayList<>();
|
||||
|
||||
Path homePath = MavenTestingUtils.getTestResourcePathDir("dist-home").toRealPath();
|
||||
Path homePath = MavenPaths.findTestResourceDir("dist-home");
|
||||
cmdLineArgs.add("jetty.home=" + homePath);
|
||||
cmdLineArgs.add("user.dir=" + homePath);
|
||||
|
||||
|
|
|
@ -16,30 +16,35 @@ package org.eclipse.jetty.start;
|
|||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class PropertyDump
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.printf("PropertyDump%n");
|
||||
|
||||
Predicate<String> nameSelectionPredicate =
|
||||
(name) ->
|
||||
name.startsWith("test.") ||
|
||||
name.startsWith("jetty.");
|
||||
|
||||
// As System Properties
|
||||
Properties props = System.getProperties();
|
||||
Enumeration<?> names = props.propertyNames();
|
||||
while (names.hasMoreElements())
|
||||
{
|
||||
String name = (String)names.nextElement();
|
||||
// only interested in "test." prefixed properties
|
||||
if (name.startsWith("test."))
|
||||
{
|
||||
System.out.printf("System %s=%s%n", name, props.getProperty(name));
|
||||
}
|
||||
}
|
||||
props.stringPropertyNames()
|
||||
.stream()
|
||||
.filter(nameSelectionPredicate)
|
||||
.sorted()
|
||||
.forEach((name) ->
|
||||
System.out.printf("System %s=%s%n", name, props.getProperty(name)));
|
||||
|
||||
// As File Argument
|
||||
for (String arg : args)
|
||||
{
|
||||
System.out.printf("Arg [%s]%n", arg);
|
||||
if (arg.endsWith(".properties"))
|
||||
{
|
||||
Properties aprops = new Properties();
|
||||
|
@ -47,15 +52,13 @@ public class PropertyDump
|
|||
try (FileReader reader = new FileReader(propFile))
|
||||
{
|
||||
aprops.load(reader);
|
||||
Enumeration<?> anames = aprops.propertyNames();
|
||||
while (anames.hasMoreElements())
|
||||
{
|
||||
String name = (String)anames.nextElement();
|
||||
if (name.startsWith("test."))
|
||||
{
|
||||
System.out.printf("%s %s=%s%n", propFile.getName(), name, aprops.getProperty(name));
|
||||
}
|
||||
}
|
||||
Collections.list(aprops.propertyNames())
|
||||
.stream()
|
||||
.map(Objects::toString)
|
||||
.filter(nameSelectionPredicate)
|
||||
.sorted()
|
||||
.forEach((name) ->
|
||||
System.out.printf("%s %s=%s%n", propFile.getName(), name, aprops.getProperty(name)));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
|
@ -20,19 +20,26 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.IO;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class PropertyPassingTest
|
||||
{
|
||||
private static class ConsoleCapture implements Runnable
|
||||
|
@ -86,10 +93,12 @@ public class PropertyPassingTest
|
|||
}
|
||||
}
|
||||
|
||||
public WorkDir workDir;
|
||||
|
||||
@Test
|
||||
public void testAsJvmArg() throws IOException, InterruptedException
|
||||
{
|
||||
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
|
@ -100,7 +109,7 @@ public class PropertyPassingTest
|
|||
// addDebug(commands);
|
||||
commands.add("-Dtest.foo=bar"); // TESTING THIS
|
||||
commands.add(getStartJarBin());
|
||||
commands.add(bogusXml.getAbsolutePath());
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
@ -112,7 +121,7 @@ public class PropertyPassingTest
|
|||
@Test
|
||||
public void testAsCommandLineArg() throws IOException, InterruptedException
|
||||
{
|
||||
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
|
@ -123,7 +132,7 @@ public class PropertyPassingTest
|
|||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add("test.foo=bar"); // TESTING THIS
|
||||
commands.add(bogusXml.getAbsolutePath());
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
@ -135,7 +144,7 @@ public class PropertyPassingTest
|
|||
@Test
|
||||
public void testAsDashDCommandLineArg() throws IOException, InterruptedException
|
||||
{
|
||||
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
|
@ -146,7 +155,7 @@ public class PropertyPassingTest
|
|||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add("-Dtest.foo=bar"); // TESTING THIS
|
||||
commands.add(bogusXml.getAbsolutePath());
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
@ -155,16 +164,143 @@ public class PropertyPassingTest
|
|||
assertThat(output, containsString("test.foo=bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandPropertyArg() throws IOException, InterruptedException
|
||||
{
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
commands.add(getJavaBin());
|
||||
commands.add("-Dmain.class=" + PropertyDump.class.getName());
|
||||
commands.add("-Dtest.dir=/opt/dists/jetty");
|
||||
commands.add("-cp");
|
||||
commands.add(getClassPath());
|
||||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add("test.config=${test.dir}/etc/config.ini"); // TESTING THIS
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
||||
// Test for values
|
||||
assertThat(output, containsString("test.config=/opt/dists/jetty/etc/config.ini"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandPropertyDArg() throws IOException, InterruptedException
|
||||
{
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
commands.add(getJavaBin());
|
||||
commands.add("-Dmain.class=" + PropertyDump.class.getName());
|
||||
commands.add("-Dtest.dir=/opt/dists/jetty");
|
||||
commands.add("-cp");
|
||||
commands.add(getClassPath());
|
||||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add("-Dtest.config=${test.dir}/etc/config.ini"); // TESTING THIS
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
||||
// Test for values
|
||||
assertThat(output, containsString("test.config=/opt/dists/jetty/etc/config.ini"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandPropertyStartIni() throws IOException, InterruptedException
|
||||
{
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
Path base = workDir.getEmptyPathDir();
|
||||
Path ini = base.resolve("start.d/config.ini");
|
||||
FS.ensureDirectoryExists(ini.getParent());
|
||||
String iniBody = """
|
||||
# Enabling a single module (that does nothing) to let start.jar run
|
||||
--module=empty
|
||||
# TESTING THIS (it should expand the ${jetty.base} portion
|
||||
test.config=${jetty.base}/etc/config.ini
|
||||
""";
|
||||
Files.writeString(ini, iniBody, StandardCharsets.UTF_8);
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
commands.add(getJavaBin());
|
||||
commands.add("-Dmain.class=" + PropertyDump.class.getName());
|
||||
commands.add("-Djetty.base=" + base);
|
||||
commands.add("-cp");
|
||||
commands.add(getClassPath());
|
||||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
||||
// Test for values
|
||||
Path expectedPath = base.resolve("etc/config.ini");
|
||||
assertThat(output, containsString("test.config=" + expectedPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandEnvProperty() throws IOException, InterruptedException
|
||||
{
|
||||
Path bogusXml = MavenPaths.findTestResourceFile("bogus.xml");
|
||||
Path base = workDir.getEmptyPathDir();
|
||||
Path module = base.resolve("modules/env-config.mod");
|
||||
FS.ensureDirectoryExists(module.getParent());
|
||||
String moduleBody = """
|
||||
[environment]
|
||||
eex
|
||||
|
||||
[ini-template]
|
||||
# configuration option
|
||||
# test.config=${jetty.home}/etc/eex-config.ini
|
||||
""";
|
||||
Files.writeString(module, moduleBody, StandardCharsets.UTF_8);
|
||||
Path ini = base.resolve("start.d/config.ini");
|
||||
FS.ensureDirectoryExists(ini.getParent());
|
||||
String iniBody = """
|
||||
# Enabling a single module (that does nothing) to let start.jar run
|
||||
--module=env-config
|
||||
# TESTING THIS (it should expand the ${jetty.base} portion
|
||||
test.config=${jetty.base}/etc/config.ini
|
||||
""";
|
||||
Files.writeString(ini, iniBody, StandardCharsets.UTF_8);
|
||||
|
||||
// Setup command line
|
||||
List<String> commands = new ArrayList<>();
|
||||
commands.add(getJavaBin());
|
||||
commands.add("-Dmain.class=" + PropertyDump.class.getName());
|
||||
commands.add("-Djetty.base=" + base);
|
||||
commands.add("-cp");
|
||||
commands.add(getClassPath());
|
||||
// addDebug(commands);
|
||||
commands.add(getStartJarBin());
|
||||
commands.add(bogusXml.toAbsolutePath().toString());
|
||||
|
||||
// Run command, collect output
|
||||
String output = collectRunOutput(commands);
|
||||
|
||||
// Test for values
|
||||
Path expectedPath = base.resolve("etc/config.ini");
|
||||
assertThat(output, containsString("test.config=" + expectedPath));
|
||||
}
|
||||
|
||||
private String getClassPath()
|
||||
{
|
||||
StringBuilder cp = new StringBuilder();
|
||||
String pathSep = System.getProperty("path.separator");
|
||||
cp.append(MavenTestingUtils.getProjectDir("target/classes"));
|
||||
cp.append(pathSep);
|
||||
cp.append(MavenTestingUtils.getProjectDir("target/test-classes"));
|
||||
cp.append(pathSep);
|
||||
cp.append(MavenTestingUtils.getProjectDir("target/jetty-util"));
|
||||
return cp.toString();
|
||||
return String.join(
|
||||
File.pathSeparator,
|
||||
List.of(
|
||||
MavenPaths.projectBase().resolve("target/classes").toString(),
|
||||
MavenPaths.projectBase().resolve("target/test-classes").toString(),
|
||||
MavenPaths.projectBase().resolve("target/jetty-util").toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected void addDebug(List<String> commands)
|
||||
|
@ -180,11 +316,10 @@ public class PropertyPassingTest
|
|||
{
|
||||
cline.append(command).append(" ");
|
||||
}
|
||||
System.out.println("Command line: " + cline);
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(commands);
|
||||
// Set PWD
|
||||
builder.directory(MavenTestingUtils.getTestResourceDir("empty.home"));
|
||||
builder.directory(MavenPaths.findTestResourceDir("empty.home").toFile());
|
||||
Process pid = builder.start();
|
||||
|
||||
ConsoleCapture stdOutPump = new ConsoleCapture("STDOUT", pid.getInputStream()).start();
|
||||
|
@ -193,6 +328,7 @@ public class PropertyPassingTest
|
|||
int exitCode = pid.waitFor();
|
||||
if (exitCode != 0)
|
||||
{
|
||||
System.out.println("Command line: " + cline);
|
||||
System.out.printf("STDERR: [" + stdErrPump.getConsoleOutput() + "]%n");
|
||||
System.out.printf("STDOUT: [" + stdOutPump.getConsoleOutput() + "]%n");
|
||||
assertThat("Exit code", exitCode, is(0));
|
||||
|
|
|
@ -290,6 +290,12 @@ public class AbstractTest
|
|||
}
|
||||
|
||||
protected void startClient(Transport transport) throws Exception
|
||||
{
|
||||
prepareClient(transport);
|
||||
client.start();
|
||||
}
|
||||
|
||||
protected void prepareClient(Transport transport) throws Exception
|
||||
{
|
||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||
clientThreads.setName("client");
|
||||
|
@ -298,7 +304,6 @@ public class AbstractTest
|
|||
client.setByteBufferPool(clientBufferPool);
|
||||
client.setExecutor(clientThreads);
|
||||
client.setSocketAddressResolver(new SocketAddressResolver.Sync());
|
||||
client.start();
|
||||
}
|
||||
|
||||
public AbstractConnector newConnector(Transport transport, Server server)
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.test.client.transport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.fcgi.server.internal.ServerFCGIConnection;
|
||||
import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.quic.server.ServerQuicConnection;
|
||||
import org.eclipse.jetty.server.internal.HttpConnection;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
public class ConnectionPoolTest extends AbstractTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
public void testPreCreateConnections(Transport transport) throws Exception
|
||||
{
|
||||
prepareServer(transport, new EmptyServerHandler());
|
||||
ConnectionListener serverConnections = new ConnectionListener();
|
||||
connector.addBean(serverConnections);
|
||||
server.start();
|
||||
|
||||
startClient(transport);
|
||||
client.setMaxConnectionsPerDestination(8);
|
||||
if (transport == Transport.HTTPS)
|
||||
((HttpClientTransportOverHTTP)client.getTransport()).setInitializeConnections(true);
|
||||
|
||||
var request = client.newRequest(newURI(transport));
|
||||
Destination destination = client.resolveDestination(request);
|
||||
destination.getConnectionPool().preCreateConnections(client.getMaxConnectionsPerDestination())
|
||||
.get(5, TimeUnit.SECONDS);
|
||||
|
||||
// Verify that connections have been created.
|
||||
List<Connection> connections = switch (transport)
|
||||
{
|
||||
case HTTP, HTTPS -> serverConnections.filter(HttpConnection.class);
|
||||
case H2C, H2 -> serverConnections.filter(HTTP2ServerConnection.class);
|
||||
case H3 -> serverConnections.filter(ServerQuicConnection.class);
|
||||
case FCGI -> serverConnections.filter(ServerFCGIConnection.class);
|
||||
};
|
||||
assertThat(connections, not(empty()));
|
||||
|
||||
// Verify that TLS was performed.
|
||||
List<Connection> sslConnections = switch (transport)
|
||||
{
|
||||
case HTTP, H2C, FCGI, H3 -> null;
|
||||
case HTTPS, H2 -> serverConnections.filter(SslConnection.class);
|
||||
};
|
||||
if (sslConnections != null)
|
||||
{
|
||||
assertThat(sslConnections.size(), greaterThan(0));
|
||||
sslConnections.forEach(c -> assertThat(c.getBytesIn(), greaterThan(0L)));
|
||||
sslConnections.forEach(c -> assertThat(c.getBytesOut(), greaterThan(0L)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConnectionListener implements Connection.Listener
|
||||
{
|
||||
private final List<Connection> connections = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onOpened(Connection connection)
|
||||
{
|
||||
connections.add(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(Connection connection)
|
||||
{
|
||||
connections.remove(connection);
|
||||
}
|
||||
|
||||
private List<Connection> filter(Class<? extends Connection> klass)
|
||||
{
|
||||
return connections.stream()
|
||||
.filter(klass::isInstance)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -118,20 +118,15 @@ public class UnixDomainTest
|
|||
|
||||
// Use the deprecated APIs for backwards compatibility testing.
|
||||
ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
|
||||
httpClient.start();
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)))
|
||||
{
|
||||
httpClient.start();
|
||||
ContentResponse response = httpClient.newRequest(uri)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,18 +148,17 @@ public class UnixDomainTest
|
|||
}
|
||||
});
|
||||
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
|
||||
Origin proxyOrigin = new Origin(
|
||||
"http",
|
||||
new Origin.Address("localhost", fakeProxyPort),
|
||||
null,
|
||||
new Origin.Protocol(List.of("http/1.1"), false),
|
||||
new Transport.TCPUnix(unixDomainPath)
|
||||
);
|
||||
httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyOrigin, null));
|
||||
httpClient.start();
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic()))
|
||||
{
|
||||
Origin proxyOrigin = new Origin(
|
||||
"http",
|
||||
new Origin.Address("localhost", fakeProxyPort),
|
||||
null,
|
||||
new Origin.Protocol(List.of("http/1.1"), false),
|
||||
new Transport.TCPUnix(unixDomainPath)
|
||||
);
|
||||
httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyOrigin, null));
|
||||
httpClient.start();
|
||||
ContentResponse response = httpClient.newRequest("localhost", fakeServerPort)
|
||||
.transport(new Transport.TCPUnix(unixDomainPath))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
|
@ -172,10 +166,6 @@ public class UnixDomainTest
|
|||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -215,10 +205,9 @@ public class UnixDomainTest
|
|||
}
|
||||
});
|
||||
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
|
||||
httpClient.start();
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic()))
|
||||
{
|
||||
httpClient.start();
|
||||
// Try PROXYv1 with the PROXY information retrieved from the EndPoint.
|
||||
// PROXYv1 does not support the UNIX family.
|
||||
ContentResponse response1 = httpClient.newRequest("localhost", 0)
|
||||
|
@ -241,10 +230,6 @@ public class UnixDomainTest
|
|||
|
||||
assertEquals(HttpStatus.OK_200, response2.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -32,6 +32,10 @@ import org.eclipse.jetty.util.component.Dumpable;
|
|||
/**
|
||||
* Attributes.
|
||||
* Interface commonly used for storing attributes.
|
||||
* <p>
|
||||
* Some attributes may be "hidden" attributes, in that they are only found by an explicit call to
|
||||
* {@link #getAttribute(String)} and they do not otherwise appear in {@link #getAttributeNameSet()}
|
||||
* or {@link #asAttributeMap()}.
|
||||
*/
|
||||
public interface Attributes
|
||||
{
|
||||
|
@ -51,7 +55,10 @@ public interface Attributes
|
|||
Object setAttribute(String name, Object attribute);
|
||||
|
||||
/**
|
||||
* Get an attribute
|
||||
* Get an attribute by name.
|
||||
* Some attributes may be "hidden" attributes, in that they are only found by an explicit call to
|
||||
* {@code getAttribute(String)} and they do not otherwise appear in {@link #getAttributeNameSet()}
|
||||
* or {@link #asAttributeMap()}.
|
||||
* @param name the attribute to get
|
||||
* @return the value of the attribute, or {@code null} if no such attribute exists
|
||||
*/
|
||||
|
|
|
@ -657,21 +657,7 @@ public class StringUtil
|
|||
*/
|
||||
public static boolean isBlank(String str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int len = str.length();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (!Character.isWhitespace(str.codePointAt(i)))
|
||||
{
|
||||
// found a non-whitespace, we can stop searching now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// only whitespace
|
||||
return true;
|
||||
return str == null || str.isBlank();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -727,21 +713,7 @@ public class StringUtil
|
|||
*/
|
||||
public static boolean isNotBlank(String str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int len = str.length();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (!Character.isWhitespace(str.codePointAt(i)))
|
||||
{
|
||||
// found a non-whitespace, we can stop searching now
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// only whitespace
|
||||
return false;
|
||||
return !isBlank(str);
|
||||
}
|
||||
|
||||
public static boolean isHex(String str, int offset, int length)
|
||||
|
|
|
@ -50,7 +50,7 @@ import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class WebSocketClient extends ContainerLifeCycle implements Configurable, WebSocketContainer
|
||||
public class WebSocketClient extends ContainerLifeCycle implements Configurable, WebSocketContainer, AutoCloseable
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebSocketClient.class);
|
||||
private final WebSocketCoreClient coreClient;
|
||||
|
@ -403,6 +403,12 @@ public class WebSocketClient extends ContainerLifeCycle implements Configurable,
|
|||
super.doStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -30,8 +30,7 @@ public class HttpClientInitTest
|
|||
@Test
|
||||
public void testDefaultInit() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try
|
||||
try (WebSocketClient client = new WebSocketClient())
|
||||
{
|
||||
client.start();
|
||||
HttpClient httpClient = client.getHttpClient();
|
||||
|
@ -43,27 +42,20 @@ public class HttpClientInitTest
|
|||
QueuedThreadPool threadPool = (QueuedThreadPool)executor;
|
||||
assertThat("QueuedThreadPool.name", threadPool.getName(), startsWith("WebSocket@"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualInit() throws Exception
|
||||
{
|
||||
HttpClient http = new HttpClient();
|
||||
{
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
threadPool.setName("ManualWSClient@" + http.hashCode());
|
||||
http.setExecutor(threadPool);
|
||||
http.setConnectTimeout(7777);
|
||||
}
|
||||
QueuedThreadPool httpThreadPool = new QueuedThreadPool();
|
||||
httpThreadPool.setName("ManualWSClient@" + http.hashCode());
|
||||
http.setExecutor(httpThreadPool);
|
||||
http.setConnectTimeout(7777);
|
||||
|
||||
WebSocketClient client = new WebSocketClient(http);
|
||||
client.addBean(http);
|
||||
try
|
||||
try (WebSocketClient client = new WebSocketClient(http))
|
||||
{
|
||||
client.addBean(http);
|
||||
client.start();
|
||||
HttpClient httpClient = client.getHttpClient();
|
||||
assertThat("HttpClient exists", httpClient, notNullValue());
|
||||
|
@ -75,9 +67,5 @@ public class HttpClientInitTest
|
|||
QueuedThreadPool threadPool = (QueuedThreadPool)executor;
|
||||
assertThat("QueuedThreadPool.name", threadPool.getName(), startsWith("ManualWSClient@"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,22 +83,18 @@ public class ClientSessionsTest
|
|||
@Test
|
||||
public void testBasicEchoFromClient() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
|
||||
CountDownLatch onSessionCloseLatch = new CountDownLatch(1);
|
||||
|
||||
client.addSessionListener(new WebSocketSessionListener()
|
||||
try (WebSocketClient client = new WebSocketClient())
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketSessionClosed(Session session)
|
||||
CountDownLatch onSessionCloseLatch = new CountDownLatch(1);
|
||||
client.addSessionListener(new WebSocketSessionListener()
|
||||
{
|
||||
onSessionCloseLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketSessionClosed(Session session)
|
||||
{
|
||||
onSessionCloseLatch.countDown();
|
||||
}
|
||||
});
|
||||
client.start();
|
||||
CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint();
|
||||
client.setIdleTimeout(Duration.ofSeconds(10));
|
||||
|
||||
|
@ -136,9 +132,5 @@ public class ClientSessionsTest
|
|||
Collection<Session> open = client.getOpenSessions();
|
||||
assertThat("(After Close) Open Sessions.size", open.size(), is(0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,28 +125,19 @@ public class WebSocketClientTest
|
|||
@ValueSource(booleans = {false, true})
|
||||
public void testCustomizeExecutorDirectly(boolean startHttpClient) throws Exception
|
||||
{
|
||||
Executor executor = Executors.newFixedThreadPool(50);
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.setExecutor(executor);
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
Executor executor = Executors.newFixedThreadPool(50);
|
||||
httpClient.setExecutor(executor);
|
||||
if (startHttpClient)
|
||||
httpClient.start();
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
try
|
||||
|
||||
try (WebSocketClient webSocketClient = new WebSocketClient(httpClient))
|
||||
{
|
||||
webSocketClient.start();
|
||||
Executor wsExecutor = webSocketClient.getExecutor();
|
||||
assertSame(executor, wsExecutor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
webSocketClient.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,25 +149,15 @@ public class WebSocketClientTest
|
|||
Executor executor = Executors.newFixedThreadPool(50);
|
||||
clientConnector.setExecutor(executor);
|
||||
HttpClientTransport transport = new HttpClientTransportOverHTTP(clientConnector);
|
||||
HttpClient httpClient = new HttpClient(transport);
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient(transport))
|
||||
{
|
||||
httpClient.start();
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
try
|
||||
try (WebSocketClient webSocketClient = new WebSocketClient(httpClient))
|
||||
{
|
||||
webSocketClient.start();
|
||||
Executor inuseExecutor = webSocketClient.getExecutor();
|
||||
assertSame(executor, inuseExecutor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
webSocketClient.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -242,6 +242,10 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
if ("war".equalsIgnoreCase(artifact.getType()))
|
||||
return false;
|
||||
|
||||
//The dependency cannot be a pom
|
||||
if ("pom".equalsIgnoreCase(artifact.getType()))
|
||||
return false;
|
||||
|
||||
//The dependency cannot be scope provided (those should be added to the plugin classpath)
|
||||
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
|
||||
return false;
|
||||
|
|
|
@ -456,9 +456,12 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
|
||||
protected boolean hasContent(HttpServletRequest clientRequest)
|
||||
{
|
||||
return clientRequest.getContentLength() > 0 ||
|
||||
clientRequest.getContentType() != null ||
|
||||
clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
long contentLength = clientRequest.getContentLengthLong();
|
||||
if (contentLength == 0)
|
||||
return false;
|
||||
if (contentLength > 0)
|
||||
return true;
|
||||
return clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
}
|
||||
|
||||
protected boolean expects100Continue(HttpServletRequest request)
|
||||
|
|
|
@ -20,7 +20,9 @@ import java.io.OutputStream;
|
|||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -80,6 +82,7 @@ import org.eclipse.jetty.http.HttpHeaderValue;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
|
@ -143,7 +146,7 @@ public class ProxyServletTest
|
|||
server.addConnector(serverConnector);
|
||||
|
||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath();
|
||||
String keyStorePath = MavenTestingUtils.getTestResourcePathFile("server_keystore.p12").toString();
|
||||
sslContextFactory.setKeyStorePath(keyStorePath);
|
||||
sslContextFactory.setKeyStorePassword("storepwd");
|
||||
tlsServerConnector = new ServerConnector(server, new SslConnectionFactory(
|
||||
|
@ -427,7 +430,7 @@ public class ProxyServletTest
|
|||
{
|
||||
// Create a 6 MiB file
|
||||
final int length = 6 * 1024;
|
||||
Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
|
||||
Path targetTestsDir = MavenTestingUtils.getTargetTestingPath();
|
||||
Files.createDirectories(targetTestsDir);
|
||||
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
|
||||
byte[] kb = new byte[1024];
|
||||
|
@ -1703,4 +1706,80 @@ public class ProxyServletTest
|
|||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueContentLengthZero(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueEmptyChunkedContent(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, true);
|
||||
}
|
||||
|
||||
private void testExpect100ContinueNoRequestContent(Class<? extends ProxyServlet> proxyServletClass, boolean chunked) throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
});
|
||||
startProxy(proxyServletClass);
|
||||
|
||||
String authority = "localhost:" + serverConnector.getLocalPort();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", proxyConnector.getLocalPort())))
|
||||
{
|
||||
String request;
|
||||
if (chunked)
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
0
|
||||
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Content-Length: 0
|
||||
|
||||
""";
|
||||
}
|
||||
request = request.replace("$A", authority);
|
||||
client.write(StandardCharsets.UTF_8.encode(request));
|
||||
|
||||
HttpTester.Input input = HttpTester.from(client);
|
||||
HttpTester.Response response1 = HttpTester.parseResponse(input);
|
||||
if (chunked)
|
||||
{
|
||||
assertEquals(HttpStatus.CONTINUE_100, response1.getStatus());
|
||||
HttpTester.Response response2 = HttpTester.parseResponse(input);
|
||||
assertEquals(HttpStatus.OK_200, response2.getStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertEquals(HttpStatus.OK_200, response1.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ import org.eclipse.jetty.server.HttpCookieUtils;
|
|||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession;
|
||||
import org.eclipse.jetty.session.ManagedSession;
|
||||
import org.eclipse.jetty.session.SessionManager;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -492,7 +492,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public String getRequestedSessionId()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession == null ? null : requestedSession.sessionId();
|
||||
}
|
||||
|
||||
|
@ -551,7 +551,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdValid()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
HttpSession session = getSession(false);
|
||||
SessionManager manager = getServletRequestInfo().getSessionManager();
|
||||
return requestedSession != null &&
|
||||
|
@ -565,15 +565,15 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdFromCookie()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.sessionIdFromCookie();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_COOKIE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdFromURL()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_URI_PARAMETER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -330,6 +330,8 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
|||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (AbstractSessionManager.RequestedSession.isApplicableAttribute(name))
|
||||
return _requestedSession.getAttribute(name);
|
||||
return _attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -708,27 +708,33 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
private class NonServletSessionRequest extends Request.Wrapper
|
||||
{
|
||||
private final Response _response;
|
||||
private RequestedSession _session;
|
||||
private RequestedSession _requestedSession;
|
||||
|
||||
public NonServletSessionRequest(Request request, Response response, RequestedSession requestedSession)
|
||||
{
|
||||
super(request);
|
||||
_response = response;
|
||||
_session = requestedSession;
|
||||
_requestedSession = requestedSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (AbstractSessionManager.RequestedSession.isApplicableAttribute(name))
|
||||
return _requestedSession.getAttribute(name);
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
ManagedSession session = _session.session();
|
||||
ManagedSession session = _requestedSession.session();
|
||||
|
||||
if (session != null || !create)
|
||||
return session;
|
||||
|
||||
newSession(getWrapped(), _session.sessionId(), ms ->
|
||||
_session = new RequestedSession(ms, _session.sessionId(), true));
|
||||
|
||||
session = _session.session();
|
||||
newSession(getWrapped(), _requestedSession.sessionId(), ms -> _requestedSession = new RequestedSession(ms, _requestedSession.sessionId(), _requestedSession.sessionIdFrom()));
|
||||
session = _requestedSession.session();
|
||||
if (session == null)
|
||||
throw new IllegalStateException("Create session failed");
|
||||
|
||||
|
@ -740,7 +746,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
|
||||
ManagedSession getManagedSession()
|
||||
{
|
||||
return _session.session();
|
||||
return _requestedSession.session();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Random;
|
||||
|
@ -45,11 +46,13 @@ import org.eclipse.jetty.client.ContinueProtocolHandler;
|
|||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.client.Response;
|
||||
import org.eclipse.jetty.client.Result;
|
||||
import org.eclipse.jetty.client.StringRequestContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
|
@ -165,7 +168,6 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
.body(content)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
}
|
||||
|
||||
assertNotNull(response);
|
||||
|
@ -235,14 +237,14 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentRespond417ExpectationFailed(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueWithContentRespondError(transport, 417);
|
||||
testExpect100ContinueWithContentRespondError(transport, HttpStatus.EXPECTATION_FAILED_417);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentRespond413RequestEntityTooLarge(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueWithContentRespondError(transport, 413);
|
||||
testExpect100ContinueWithContentRespondError(transport, HttpStatus.PAYLOAD_TOO_LARGE_413);
|
||||
}
|
||||
|
||||
private void testExpect100ContinueWithContentRespondError(Transport transport, int error) throws Exception
|
||||
|
@ -798,6 +800,64 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentLengthZeroExpectIsRemoved(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
assertEquals(0, request.getContentLengthLong());
|
||||
// The Expect header must have been removed by the client.
|
||||
assertNull(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
|
||||
.body(new StringRequestContent(""))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueWithContentLengthZero() throws Exception
|
||||
{
|
||||
startServer(Transport.HTTP, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
assertEquals(0, request.getContentLengthLong());
|
||||
assertNotNull(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
|
||||
// Trigger the 100-Continue logic.
|
||||
// The 100 continue will not be sent, since there is no request content.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
assertEquals(-1, input.read());
|
||||
}
|
||||
});
|
||||
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", ((NetworkConnector)connector).getLocalPort())))
|
||||
{
|
||||
String request = """
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Expect: 100-Continue
|
||||
Content-Length: 0
|
||||
|
||||
""";
|
||||
client.write(StandardCharsets.UTF_8.encode(request));
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueWithTwoResponsesInOneRead() throws Exception
|
||||
{
|
||||
|
|
|
@ -14,13 +14,7 @@
|
|||
package org.eclipse.jetty.ee10.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -29,7 +23,6 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.eclipse.jetty.toolchain.test.PathMatchers;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -39,6 +32,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.io.FileMatchers.anExistingDirectory;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
@ -91,7 +85,7 @@ public class TempDirTest
|
|||
WebAppContext webAppContext = new WebAppContext();
|
||||
server.setHandler(webAppContext);
|
||||
Path tmpDir = path.resolve("foo_did_not_exist");
|
||||
assertThat(Files.exists(tmpDir), is(false));
|
||||
assertThat(tmpDir.toFile(), not(anExistingDirectory()));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
@ -125,7 +119,7 @@ public class TempDirTest
|
|||
WebInfConfiguration webInfConfiguration = new WebInfConfiguration();
|
||||
webInfConfiguration.resolveTempDirectory(webAppContext);
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(tempDirectory.exists(), is(true));
|
||||
assertThat(tempDirectory, anExistingDirectory());
|
||||
assertThat(tempDirectory.getParentFile().toPath(), PathMatchers.isSame(tmpDir));
|
||||
}
|
||||
|
||||
|
@ -152,25 +146,13 @@ public class TempDirTest
|
|||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
//Test that if jetty is creating a tmp dir for the webapp, it is different on
|
||||
//restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
|
@ -185,26 +167,14 @@ public class TempDirTest
|
|||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
//Test that if we explicitly configure the temp dir, it is the same after restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
Path configuredTmpDir = workDir.getPath().resolve("tmp");
|
||||
Path configuredTmpDir = workDir.getEmptyPathDir().resolve("tmp");
|
||||
webAppContext.setTempDirectory(configuredTmpDir.toFile());
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
|
@ -213,6 +183,7 @@ public class TempDirTest
|
|||
assertNotNull(webAppContext.getTempDirectory());
|
||||
webAppContext.start();
|
||||
assertThat(tempDirectory.toPath(), PathMatchers.isSame(webAppContext.getTempDirectory().toPath()));
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -220,28 +191,36 @@ public class TempDirTest
|
|||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
_server.stop();
|
||||
assertThat("Temp dir exists", !Files.exists(tempDirectory.toPath()));
|
||||
assertThat(tempDirectory, not(anExistingDirectory()));
|
||||
assertNull(webAppContext.getTempDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path myTempDir = workDir.getEmptyPathDir().resolve("my-temp-dir");
|
||||
FS.ensureDirExists(myTempDir);
|
||||
|
||||
//Tell jetty what the temp dir is for the webapp
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
webAppContext.setTempDirectory(myTempDir.toFile());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(tempDirectory));
|
||||
assertThat(tempDirectory.toPath(), is(myTempDir));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
@ -28,6 +29,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
|
@ -46,7 +48,9 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -247,6 +251,30 @@ public class WebInfConfigurationTest
|
|||
assertTrue(Files.exists(unpackedWebInfDir.resolve("WEB-INF").resolve("lib").resolve("alpha.jar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveTempDirectory(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testPath = MavenPaths.targetTestDir("testSimple");
|
||||
FS.ensureDirExists(testPath);
|
||||
FS.ensureEmpty(testPath);
|
||||
|
||||
_server = new Server();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setContextPath("/");
|
||||
Path warPath = createWar(testPath, "test.war");
|
||||
context.setExtractWAR(true);
|
||||
context.setWar(warPath.toUri().toURL().toString());
|
||||
_server.setHandler(context);
|
||||
_server.start();
|
||||
File tmpDir = context.getTempDirectory();
|
||||
assertNotNull(tmpDir);
|
||||
|
||||
Path tmpPath = tmpDir.toPath();
|
||||
Path lastName = tmpPath.getName(tmpPath.getNameCount() - 1);
|
||||
assertThat(lastName.toString(), startsWith("jetty-test_war-_-any-"));
|
||||
assertThat(context.getAttribute(ServletContext.TEMPDIR), is(tmpDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that for each of the expected jar names (stripped of any path info),
|
||||
* there is only 1 actual jar url in the context classloader
|
||||
|
|
|
@ -242,6 +242,10 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
if ("war".equalsIgnoreCase(artifact.getType()))
|
||||
return false;
|
||||
|
||||
//The dependency cannot be a pom
|
||||
if ("pom".equalsIgnoreCase(artifact.getType()))
|
||||
return false;
|
||||
|
||||
//The dependency cannot be scope provided (those should be added to the plugin classpath)
|
||||
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
|
||||
return false;
|
||||
|
|
|
@ -456,9 +456,12 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
|
||||
protected boolean hasContent(HttpServletRequest clientRequest)
|
||||
{
|
||||
return clientRequest.getContentLength() > 0 ||
|
||||
clientRequest.getContentType() != null ||
|
||||
clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
long contentLength = clientRequest.getContentLengthLong();
|
||||
if (contentLength == 0)
|
||||
return false;
|
||||
if (contentLength > 0)
|
||||
return true;
|
||||
return clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
}
|
||||
|
||||
protected boolean expects100Continue(HttpServletRequest request)
|
||||
|
|
|
@ -20,7 +20,9 @@ import java.io.OutputStream;
|
|||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -80,13 +82,14 @@ import org.eclipse.jetty.http.HttpHeaderValue;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -143,7 +146,7 @@ public class ProxyServletTest
|
|||
server.addConnector(serverConnector);
|
||||
|
||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath();
|
||||
String keyStorePath = MavenPaths.findTestResourceFile("server_keystore.p12").toString();
|
||||
sslContextFactory.setKeyStorePath(keyStorePath);
|
||||
sslContextFactory.setKeyStorePassword("storepwd");
|
||||
tlsServerConnector = new ServerConnector(server, new SslConnectionFactory(
|
||||
|
@ -427,7 +430,7 @@ public class ProxyServletTest
|
|||
{
|
||||
// Create a 6 MiB file
|
||||
final int length = 6 * 1024;
|
||||
Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
|
||||
Path targetTestsDir = MavenPaths.targetTests();
|
||||
Files.createDirectories(targetTestsDir);
|
||||
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
|
||||
byte[] kb = new byte[1024];
|
||||
|
@ -1703,4 +1706,80 @@ public class ProxyServletTest
|
|||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueContentLengthZero(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueEmptyChunkedContent(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, true);
|
||||
}
|
||||
|
||||
private void testExpect100ContinueNoRequestContent(Class<? extends ProxyServlet> proxyServletClass, boolean chunked) throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
});
|
||||
startProxy(proxyServletClass);
|
||||
|
||||
String authority = "localhost:" + serverConnector.getLocalPort();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", proxyConnector.getLocalPort())))
|
||||
{
|
||||
String request;
|
||||
if (chunked)
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
0
|
||||
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Content-Length: 0
|
||||
|
||||
""";
|
||||
}
|
||||
request = request.replace("$A", authority);
|
||||
client.write(StandardCharsets.UTF_8.encode(request));
|
||||
|
||||
HttpTester.Input input = HttpTester.from(client);
|
||||
HttpTester.Response response1 = HttpTester.parseResponse(input);
|
||||
if (chunked)
|
||||
{
|
||||
assertEquals(HttpStatus.CONTINUE_100, response1.getStatus());
|
||||
HttpTester.Response response2 = HttpTester.parseResponse(input);
|
||||
assertEquals(HttpStatus.OK_200, response2.getStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertEquals(HttpStatus.OK_200, response1.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ import org.eclipse.jetty.server.HttpCookieUtils;
|
|||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession;
|
||||
import org.eclipse.jetty.session.ManagedSession;
|
||||
import org.eclipse.jetty.session.SessionManager;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -492,7 +492,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public String getRequestedSessionId()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession == null ? null : requestedSession.sessionId();
|
||||
}
|
||||
|
||||
|
@ -551,7 +551,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdValid()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
HttpSession session = getSession(false);
|
||||
SessionManager manager = getServletRequestInfo().getSessionManager();
|
||||
return requestedSession != null &&
|
||||
|
@ -565,15 +565,15 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdFromCookie()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.sessionIdFromCookie();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_COOKIE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdFromURL()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
|
||||
RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.isSessionIdFrom(RequestedSession.ID_FROM_URI_PARAMETER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -330,6 +330,8 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
|||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (AbstractSessionManager.RequestedSession.class.getName().equals(name))
|
||||
return _requestedSession;
|
||||
return _attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -752,27 +752,33 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
private class NonServletSessionRequest extends Request.Wrapper
|
||||
{
|
||||
private final Response _response;
|
||||
private RequestedSession _session;
|
||||
private RequestedSession _requestedSession;
|
||||
|
||||
public NonServletSessionRequest(Request request, Response response, RequestedSession requestedSession)
|
||||
{
|
||||
super(request);
|
||||
_response = response;
|
||||
_session = requestedSession;
|
||||
_requestedSession = requestedSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (AbstractSessionManager.RequestedSession.isApplicableAttribute(name))
|
||||
return _requestedSession.getAttribute(name);
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
ManagedSession session = _session.session();
|
||||
ManagedSession session = _requestedSession.session();
|
||||
|
||||
if (session != null || !create)
|
||||
return session;
|
||||
|
||||
newSession(getWrapped(), _session.sessionId(), ms ->
|
||||
_session = new RequestedSession(ms, _session.sessionId(), true));
|
||||
|
||||
session = _session.session();
|
||||
newSession(getWrapped(), _requestedSession.sessionId(), ms -> _requestedSession = new RequestedSession(ms, _requestedSession.sessionId(), _requestedSession.sessionIdFrom()));
|
||||
session = _requestedSession.session();
|
||||
if (session == null)
|
||||
throw new IllegalStateException("Create session failed");
|
||||
|
||||
|
@ -784,7 +790,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
|||
|
||||
ManagedSession getManagedSession()
|
||||
{
|
||||
return _session.session();
|
||||
return _requestedSession.session();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Random;
|
||||
|
@ -45,11 +46,13 @@ import org.eclipse.jetty.client.ContinueProtocolHandler;
|
|||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.client.Response;
|
||||
import org.eclipse.jetty.client.Result;
|
||||
import org.eclipse.jetty.client.StringRequestContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
|
@ -856,6 +859,64 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentLengthZeroExpectIsRemoved(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
assertEquals(0, request.getContentLengthLong());
|
||||
// The Expect header must have been removed by the client.
|
||||
assertNull(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
|
||||
.body(new StringRequestContent(""))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueWithContentLengthZero() throws Exception
|
||||
{
|
||||
startServer(Transport.HTTP, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
assertEquals(0, request.getContentLengthLong());
|
||||
assertNotNull(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
|
||||
// Trigger the 100-Continue logic.
|
||||
// The 100 continue will not be sent, since there is no request content.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
assertEquals(-1, input.read());
|
||||
}
|
||||
});
|
||||
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", ((NetworkConnector)connector).getLocalPort())))
|
||||
{
|
||||
String request = """
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Expect: 100-Continue
|
||||
Content-Length: 0
|
||||
|
||||
""";
|
||||
client.write(StandardCharsets.UTF_8.encode(request));
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExpectRespond100Continue() throws Exception
|
||||
{
|
||||
|
|
|
@ -18,11 +18,8 @@ import java.io.IOException;
|
|||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
@ -39,7 +36,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebInfConfiguration.class);
|
||||
|
||||
public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
|
||||
public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase";
|
||||
public static final String ORIGINAL_RESOURCE_BASE = "org.eclipse.jetty.webapp.originalResourceBase";
|
||||
|
||||
|
@ -89,10 +85,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
@Override
|
||||
public void deconfigure(WebAppContext context) throws Exception
|
||||
{
|
||||
//if it wasn't explicitly configured by the user, then unset it
|
||||
if (!(context.getAttribute(TEMPDIR_CONFIGURED) instanceof Boolean tmpdirConfigured && tmpdirConfigured))
|
||||
context.setTempDirectory(null);
|
||||
|
||||
//reset the base resource back to what it was before we did any unpacking of resources
|
||||
Resource originalBaseResource = (Resource)context.removeAttribute(ORIGINAL_RESOURCE_BASE);
|
||||
context.setBaseResource(originalBaseResource);
|
||||
|
@ -133,7 +125,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
File tempDirectory = context.getTempDirectory();
|
||||
if (tempDirectory != null)
|
||||
{
|
||||
context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -148,37 +139,14 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
return;
|
||||
}
|
||||
|
||||
makeTempDirectory(context.getServer().getContext().getTempDirectory(), context);
|
||||
context.makeTempDirectory();
|
||||
}
|
||||
|
||||
@Deprecated (forRemoval = true, since = "12.0.12")
|
||||
public void makeTempDirectory(File parent, WebAppContext context)
|
||||
throws Exception
|
||||
{
|
||||
if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
|
||||
throw new IllegalStateException("Parent for temp dir not configured correctly: " + (parent == null ? "null" : "writeable=" + parent.canWrite()));
|
||||
|
||||
boolean persistent = context.isTempDirectoryPersistent() || "work".equals(parent.toPath().getFileName().toString());
|
||||
|
||||
//Create a name for the webapp
|
||||
String temp = getCanonicalNameForWebAppTmpDir(context);
|
||||
File tmpDir;
|
||||
if (persistent)
|
||||
{
|
||||
//if it is to be persisted, make sure it will be the same name
|
||||
//by not using File.createTempFile, which appends random digits
|
||||
tmpDir = new File(parent, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ensure dir will always be unique by having classlib generate random path name
|
||||
tmpDir = Files.createTempDirectory(parent.toPath(), temp).toFile();
|
||||
tmpDir.deleteOnExit();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Set temp dir {}", tmpDir);
|
||||
context.setTempDirectory(tmpDir);
|
||||
context.setTempDirectoryPersistent(persistent);
|
||||
context.makeTempDirectory();
|
||||
}
|
||||
|
||||
public void unpack(WebAppContext context) throws IOException
|
||||
|
@ -395,91 +363,20 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
*
|
||||
* @param context the context to get the canonical name from
|
||||
* @return the canonical name for the webapp temp directory
|
||||
* @deprecated this method is no longer used
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "12.0.12")
|
||||
public static String getCanonicalNameForWebAppTmpDir(WebAppContext context)
|
||||
{
|
||||
StringBuilder canonicalName = new StringBuilder();
|
||||
canonicalName.append("jetty-");
|
||||
|
||||
//get the host and the port from the first connector
|
||||
Server server = context.getServer();
|
||||
if (server != null)
|
||||
{
|
||||
Connector[] connectors = server.getConnectors();
|
||||
|
||||
if (connectors.length > 0)
|
||||
{
|
||||
//Get the host
|
||||
String host = null;
|
||||
int port = 0;
|
||||
if (connectors[0] instanceof NetworkConnector connector)
|
||||
{
|
||||
host = connector.getHost();
|
||||
port = connector.getLocalPort();
|
||||
if (port < 0)
|
||||
port = connector.getPort();
|
||||
}
|
||||
if (host == null)
|
||||
host = "0.0.0.0";
|
||||
canonicalName.append(host);
|
||||
canonicalName.append("-");
|
||||
canonicalName.append(port);
|
||||
canonicalName.append("-");
|
||||
}
|
||||
}
|
||||
|
||||
// Resource base
|
||||
try
|
||||
{
|
||||
Resource resource = context.getBaseResource();
|
||||
if (resource == null)
|
||||
{
|
||||
if (StringUtil.isBlank(context.getWar()))
|
||||
throw new IllegalStateException("No resourceBase or war set for context");
|
||||
|
||||
// Set dir or WAR to resource
|
||||
resource = context.newResource(context.getWar());
|
||||
}
|
||||
|
||||
String resourceBaseName = getResourceBaseName(resource);
|
||||
canonicalName.append(resourceBaseName);
|
||||
canonicalName.append("-");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Can't get resource base name", e);
|
||||
|
||||
canonicalName.append("-"); // empty resourceBaseName segment
|
||||
}
|
||||
|
||||
//Context name
|
||||
String contextPath = context.getContextPath();
|
||||
contextPath = contextPath.replace('/', '_');
|
||||
contextPath = contextPath.replace('\\', '_');
|
||||
canonicalName.append(contextPath);
|
||||
|
||||
//Virtual host (if there is one)
|
||||
canonicalName.append("-");
|
||||
List<String> vhosts = context.getVirtualHosts();
|
||||
if (vhosts == null || vhosts.size() <= 0)
|
||||
canonicalName.append("any");
|
||||
else
|
||||
canonicalName.append(vhosts.get(0));
|
||||
|
||||
// sanitize
|
||||
for (int i = 0; i < canonicalName.length(); i++)
|
||||
{
|
||||
char c = canonicalName.charAt(i);
|
||||
if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c) < 0)
|
||||
canonicalName.setCharAt(i, '.');
|
||||
}
|
||||
|
||||
canonicalName.append("-");
|
||||
|
||||
return StringUtil.sanitizeFileSystemName(canonicalName.toString());
|
||||
return context.getCanonicalNameForTmpDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource the Resource for which to extract a short name
|
||||
* @return extract a short name for the resource
|
||||
* @deprecated this method is no longer needed
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "12.0.12")
|
||||
protected static String getResourceBaseName(Resource resource)
|
||||
{
|
||||
// Use File System and File interface if present
|
||||
|
|
|
@ -14,13 +14,7 @@
|
|||
package org.eclipse.jetty.ee11.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -29,9 +23,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.eclipse.jetty.toolchain.test.PathMatchers;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
@ -40,6 +32,9 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.io.FileMatchers.anExistingDirectory;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class TempDirTest
|
||||
|
@ -90,7 +85,7 @@ public class TempDirTest
|
|||
WebAppContext webAppContext = new WebAppContext();
|
||||
server.setHandler(webAppContext);
|
||||
Path tmpDir = path.resolve("foo_did_not_exist");
|
||||
assertThat(Files.exists(tmpDir), is(false));
|
||||
assertThat(tmpDir.toFile(), not(anExistingDirectory()));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
@ -124,7 +119,7 @@ public class TempDirTest
|
|||
WebInfConfiguration webInfConfiguration = new WebInfConfiguration();
|
||||
webInfConfiguration.resolveTempDirectory(webAppContext);
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(tempDirectory.exists(), is(true));
|
||||
assertThat(tempDirectory, is(anExistingDirectory()));
|
||||
assertThat(tempDirectory.getParentFile().toPath(), PathMatchers.isSame(tmpDir));
|
||||
}
|
||||
|
||||
|
@ -151,65 +146,81 @@ public class TempDirTest
|
|||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
//Test that if jetty is creating a tmp dir for the webapp, it is different on
|
||||
//restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
webAppContext.stop();
|
||||
assertNull(webAppContext.getTempDirectory());
|
||||
webAppContext.start();
|
||||
assertThat(tempDirectory.toPath(), not(PathMatchers.isSame(webAppContext.getTempDirectory().toPath())));
|
||||
}
|
||||
|
||||
@Disabled ("Enable after issue 11548 fixed")
|
||||
@Test
|
||||
public void testSameTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
//Test that if we explicitly configure the temp dir, it is the same after restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
Path configuredTmpDir = workDir.getPath().resolve("tmp");
|
||||
Path configuredTmpDir = workDir.getEmptyPathDir().resolve("tmp");
|
||||
webAppContext.setTempDirectory(configuredTmpDir.toFile());
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(tempDirectory.toPath(), PathMatchers.isSame(configuredTmpDir));
|
||||
webAppContext.stop();
|
||||
assertNotNull(webAppContext.getTempDirectory());
|
||||
webAppContext.start();
|
||||
assertThat(tempDirectory.toPath(), PathMatchers.isSame(webAppContext.getTempDirectory().toPath()));
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTempDirDeleted(WorkDir workDir) throws Exception
|
||||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
_server.stop();
|
||||
assertThat(tempDirectory, not(anExistingDirectory()));
|
||||
assertNull(webAppContext.getTempDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path myTempDir = workDir.getEmptyPathDir().resolve("my-temp-dir");
|
||||
FS.ensureDirExists(myTempDir);
|
||||
|
||||
//Tell jetty what the temp dir is for the webapp
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
webAppContext.setTempDirectory(myTempDir.toFile());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(tempDirectory));
|
||||
assertThat(tempDirectory.toPath(), is(myTempDir));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee11.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
@ -28,6 +29,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
|
@ -46,7 +48,9 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -247,6 +251,30 @@ public class WebInfConfigurationTest
|
|||
assertTrue(Files.exists(unpackedWebInfDir.resolve("WEB-INF").resolve("lib").resolve("alpha.jar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveTempDirectory(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testPath = MavenPaths.targetTestDir("testSimple");
|
||||
FS.ensureDirExists(testPath);
|
||||
FS.ensureEmpty(testPath);
|
||||
|
||||
_server = new Server();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setContextPath("/");
|
||||
Path warPath = createWar(testPath, "test.war");
|
||||
context.setExtractWAR(true);
|
||||
context.setWar(warPath.toUri().toURL().toString());
|
||||
_server.setHandler(context);
|
||||
_server.start();
|
||||
File tmpDir = context.getTempDirectory();
|
||||
assertNotNull(tmpDir);
|
||||
|
||||
Path tmpPath = tmpDir.toPath();
|
||||
Path lastName = tmpPath.getName(tmpPath.getNameCount() - 1);
|
||||
assertThat(lastName.toString(), startsWith("jetty-test_war-_-any-"));
|
||||
assertThat(context.getAttribute(ServletContext.TEMPDIR), is(tmpDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that for each of the expected jar names (stripped of any path info),
|
||||
* there is only 1 actual jar url in the context classloader
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
|
@ -2465,7 +2466,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
private ManagedSession _managedSession;
|
||||
private List<ManagedSession> _managedSessions;
|
||||
|
||||
AbstractSessionManager.RequestedSession _requestedSession;
|
||||
AbstractSessionManager.RequestedSession _requestedSession = AbstractSessionManager.RequestedSession.NO_REQUESTED_SESSION;
|
||||
|
||||
protected CoreContextRequest(org.eclipse.jetty.server.Request wrapped,
|
||||
ScopedContext context,
|
||||
|
@ -2565,7 +2566,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
*/
|
||||
public void setRequestedSession(AbstractSessionManager.RequestedSession requestedSession)
|
||||
{
|
||||
_requestedSession = requestedSession;
|
||||
_requestedSession = requestedSession == null ? AbstractSessionManager.RequestedSession.NO_REQUESTED_SESSION : requestedSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (AbstractSessionManager.RequestedSession.class.getName().equals(name))
|
||||
return _requestedSession;
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2652,7 +2661,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
if (_sessionManager == null)
|
||||
throw new IllegalStateException("No SessionManager");
|
||||
|
||||
_sessionManager.newSession(this, _requestedSession == null ? null : _requestedSession.sessionId(), this::setManagedSession);
|
||||
_sessionManager.newSession(this, _requestedSession.sessionId(), this::setManagedSession);
|
||||
|
||||
if (_managedSession == null)
|
||||
throw new IllegalStateException("Create session failed");
|
||||
|
@ -2687,10 +2696,21 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
|
||||
@Override
|
||||
public Resource getResourceForTempDirName()
|
||||
{
|
||||
return ContextHandler.this.getNestedResourceForTempDirName();
|
||||
}
|
||||
|
||||
private Resource getSuperResourceForTempDirName()
|
||||
{
|
||||
return super.getResourceForTempDirName();
|
||||
}
|
||||
|
||||
public void setTempDirectory(File dir)
|
||||
{
|
||||
super.setTempDirectory(dir);
|
||||
setAttribute(ServletContext.TEMPDIR, super.getTempDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContextPath(String contextPath)
|
||||
{
|
||||
|
@ -2852,4 +2872,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getNestedResourceForTempDirName()
|
||||
{
|
||||
return getCoreContextHandler().getSuperResourceForTempDirName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ import org.eclipse.jetty.server.FormFields;
|
|||
import org.eclipse.jetty.server.HttpCookieUtils;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession;
|
||||
import org.eclipse.jetty.session.ManagedSession;
|
||||
import org.eclipse.jetty.session.SessionManager;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
|
@ -1245,7 +1245,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getRequestedSessionId()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
return requestedSession == null ? null : requestedSession.sessionId();
|
||||
}
|
||||
|
||||
|
@ -1522,8 +1522,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdFromCookie()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.sessionIdFromCookie();
|
||||
return _coreRequest.getRequestedSession().isSessionIdFrom(RequestedSession.ID_FROM_COOKIE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1536,14 +1535,13 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isRequestedSessionIdFromURL()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
|
||||
return _coreRequest.getRequestedSession().isSessionIdFrom(RequestedSession.ID_FROM_URI_PARAMETER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdValid()
|
||||
{
|
||||
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
RequestedSession requestedSession = _coreRequest.getRequestedSession();
|
||||
SessionManager sessionManager = _coreRequest.getSessionManager();
|
||||
ManagedSession managedSession = _coreRequest.getManagedSession();
|
||||
return requestedSession != null &&
|
||||
|
|
|
@ -556,7 +556,7 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
|||
currentSession = currentRequestedSession.session();
|
||||
}
|
||||
else
|
||||
currentRequestedSession = new AbstractSessionManager.RequestedSession(currentSession, currentSession.getId(), false /*TODO!!!*/);
|
||||
currentRequestedSession = new AbstractSessionManager.RequestedSession(currentSession, currentSession.getId(), null /*TODO!!!*/);
|
||||
|
||||
coreRequest.setManagedSession(currentSession);
|
||||
coreRequest.setRequestedSession(currentRequestedSession);
|
||||
|
|
|
@ -67,7 +67,7 @@ import org.eclipse.jetty.server.NetworkConnector;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.TunnelSupport;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession;
|
||||
import org.eclipse.jetty.session.DefaultSessionCache;
|
||||
import org.eclipse.jetty.session.DefaultSessionIdManager;
|
||||
import org.eclipse.jetty.session.ManagedSession;
|
||||
|
@ -1613,7 +1613,7 @@ public class ResponseTest
|
|||
|
||||
ContextHandler.CoreContextRequest coreRequest = response.getHttpChannel().getCoreRequest();
|
||||
coreRequest.setSessionManager(sessionHandler.getSessionManager());
|
||||
coreRequest.setRequestedSession(new AbstractSessionManager.RequestedSession(null, "12345", false));
|
||||
coreRequest.setRequestedSession(new RequestedSession(null, "12345", RequestedSession.ID_FROM_URI_PARAMETER));
|
||||
assertNotNull(request.getSession(true));
|
||||
assertThat(request.getSession(false).getId(), is("12345"));
|
||||
|
||||
|
@ -1724,7 +1724,7 @@ public class ResponseTest
|
|||
ContextHandler.CoreContextRequest coreRequest = response.getHttpChannel().getCoreRequest();
|
||||
coreRequest.setSessionManager(sessionHandler.getSessionManager());
|
||||
ManagedSession session = sessionHandler.getSessionManager().getManagedSession("12345");
|
||||
coreRequest.setRequestedSession(new AbstractSessionManager.RequestedSession(session, "12345", cookie));
|
||||
coreRequest.setRequestedSession(new RequestedSession(session, "12345", cookie ? RequestedSession.ID_FROM_COOKIE : RequestedSession.ID_FROM_URI_PARAMETER));
|
||||
if (session == null)
|
||||
request.getSession(true);
|
||||
|
||||
|
@ -1793,7 +1793,7 @@ public class ResponseTest
|
|||
request.setContext(_context._apiContext, "/info");
|
||||
|
||||
ContextHandler.CoreContextRequest coreRequest = response.getHttpChannel().getCoreRequest();
|
||||
coreRequest.setRequestedSession(new AbstractSessionManager.RequestedSession(null, "12345", i > 2));
|
||||
coreRequest.setRequestedSession(new RequestedSession(null, "12345", i > 2 ? RequestedSession.ID_FROM_COOKIE : RequestedSession.ID_FROM_URI_PARAMETER));
|
||||
SessionHandler handler = new SessionHandler();
|
||||
|
||||
NullSessionDataStore dataStore = new NullSessionDataStore();
|
||||
|
|
|
@ -459,9 +459,12 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
|
||||
protected boolean hasContent(HttpServletRequest clientRequest)
|
||||
{
|
||||
return clientRequest.getContentLength() > 0 ||
|
||||
clientRequest.getContentType() != null ||
|
||||
clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
long contentLength = clientRequest.getContentLengthLong();
|
||||
if (contentLength == 0)
|
||||
return false;
|
||||
if (contentLength > 0)
|
||||
return true;
|
||||
return clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
}
|
||||
|
||||
protected boolean expects100Continue(HttpServletRequest request)
|
||||
|
|
|
@ -20,7 +20,9 @@ import java.io.OutputStream;
|
|||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -79,6 +81,7 @@ import org.eclipse.jetty.http.HttpHeaderValue;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
|
@ -1701,4 +1704,80 @@ public class ProxyServletTest
|
|||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueContentLengthZero(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testExpect100ContinueEmptyChunkedContent(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
testExpect100ContinueNoRequestContent(proxyServletClass, true);
|
||||
}
|
||||
|
||||
private void testExpect100ContinueNoRequestContent(Class<? extends ProxyServlet> proxyServletClass, boolean chunked) throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
});
|
||||
startProxy(proxyServletClass);
|
||||
|
||||
String authority = "localhost:" + serverConnector.getLocalPort();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", proxyConnector.getLocalPort())))
|
||||
{
|
||||
String request;
|
||||
if (chunked)
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
0
|
||||
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
request = """
|
||||
POST http://$A/ HTTP/1.1
|
||||
Host: $A
|
||||
Expect: 100-Continue
|
||||
Content-Length: 0
|
||||
|
||||
""";
|
||||
}
|
||||
request = request.replace("$A", authority);
|
||||
client.write(StandardCharsets.UTF_8.encode(request));
|
||||
|
||||
HttpTester.Input input = HttpTester.from(client);
|
||||
HttpTester.Response response1 = HttpTester.parseResponse(input);
|
||||
if (chunked)
|
||||
{
|
||||
assertEquals(HttpStatus.CONTINUE_100, response1.getStatus());
|
||||
HttpTester.Response response2 = HttpTester.parseResponse(input);
|
||||
assertEquals(HttpStatus.OK_200, response2.getStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertEquals(HttpStatus.OK_200, response1.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1162,7 +1162,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
public void setTempDirectory(File dir)
|
||||
{
|
||||
getCoreContextHandler().setTempDirectory(dir);
|
||||
setAttribute(ServletContext.TEMPDIR, getCoreContextHandler().getTempDirectory());
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "temporary directory location", readonly = true)
|
||||
|
@ -1181,9 +1180,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
return getCoreContextHandler().getCanonicalNameForTmpDir();
|
||||
}
|
||||
|
||||
protected Resource getResourceForTempDirName()
|
||||
@Override
|
||||
public Resource getNestedResourceForTempDirName()
|
||||
{
|
||||
Resource resource = getCoreContextHandler().getResourceForTempDirName();
|
||||
Resource resource = super.getNestedResourceForTempDirName();
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
|
|
|
@ -14,13 +14,7 @@
|
|||
package org.eclipse.jetty.ee9.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -29,27 +23,32 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.eclipse.jetty.toolchain.test.PathMatchers;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.resource.FileSystemPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.io.FileMatchers.anExistingDirectory;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class TempDirTest
|
||||
{
|
||||
public static void tearDown()
|
||||
|
||||
private Server _server;
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() throws Exception
|
||||
{
|
||||
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
|
||||
if (_server != null)
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,11 +85,11 @@ public class TempDirTest
|
|||
public void attributeWithNonExistentDirectory(String type, WorkDir workDir) throws Exception
|
||||
{
|
||||
Path jettyBase = workDir.getEmptyPathDir();
|
||||
Server server = new Server();
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
server.setHandler(webAppContext);
|
||||
_server.setHandler(webAppContext);
|
||||
Path tmpDir = jettyBase.resolve("foo_did_not_exist");
|
||||
assertFalse(Files.exists(tmpDir));
|
||||
assertThat(tmpDir.toFile(), not(anExistingDirectory()));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
@ -115,23 +114,23 @@ public class TempDirTest
|
|||
{
|
||||
Path jettyBase = workDir.getEmptyPathDir();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
Server server = new Server();
|
||||
webAppContext.setServer(server);
|
||||
_server = new Server();
|
||||
webAppContext.setServer(_server);
|
||||
Path tmpDir = jettyBase.resolve("temp_test");
|
||||
FS.ensureDirExists(tmpDir);
|
||||
server.setTempDirectory(tmpDir.toFile());
|
||||
_server.setTempDirectory(tmpDir.toFile());
|
||||
|
||||
// Test we have correct value as the webapp temp directory.
|
||||
WebInfConfiguration webInfConfiguration = new WebInfConfiguration();
|
||||
webInfConfiguration.resolveTempDirectory(webAppContext);
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertTrue(tempDirectory.exists());
|
||||
assertThat(tempDirectory, anExistingDirectory());
|
||||
assertThat(tempDirectory.getParentFile().toPath(), is(tmpDir));
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(webAppContext.getTempDirectory()));
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>${jetty.base}</code> directory exists and has a subdirectory called work
|
||||
* so webappContent#tempDirectory is created under <code>java.io.tmpdir</code>
|
||||
*/
|
||||
@Test
|
||||
public void jettyBaseWorkExists(WorkDir workDirExt) throws Exception
|
||||
|
@ -140,12 +139,13 @@ public class TempDirTest
|
|||
Path workDir = jettyBase.resolve("work");
|
||||
FS.ensureDirExists(workDir);
|
||||
WebInfConfiguration webInfConfiguration = new WebInfConfiguration();
|
||||
Server server = new Server();
|
||||
server.setTempDirectory(workDir.toFile());
|
||||
_server = new Server();
|
||||
_server.setTempDirectory(workDir.toFile());
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
server.setHandler(webAppContext);
|
||||
_server.setHandler(webAppContext);
|
||||
webInfConfiguration.resolveTempDirectory(webAppContext);
|
||||
assertThat(webAppContext.getTempDirectory().getParentFile().toPath(), PathMatchers.isSame(workDir));
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(webAppContext.getTempDirectory()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,29 +153,84 @@ public class TempDirTest
|
|||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
|
||||
// Use ZipFS so that we can create paths that are just "/"
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
IO.copyDir(testWebappDir, root);
|
||||
}
|
||||
|
||||
//Let jetty create the tmp dir on the fly
|
||||
Server server = new Server();
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
|
||||
server.setHandler(webAppContext);
|
||||
server.start();
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
server.stop();
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(webAppContext.getTempDirectory()));
|
||||
_server.stop();
|
||||
assertNull(webAppContext.getTempDirectory());
|
||||
assertThat("Temp dir exists", !Files.exists(tempDirectory.toPath()));
|
||||
assertThat(tempDirectory, not(anExistingDirectory()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path myTempDir = workDir.getEmptyPathDir().resolve("my-temp-dir");
|
||||
FS.ensureDirExists(myTempDir);
|
||||
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
|
||||
//Tell jetty what the temp dir is for the webapp
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
webAppContext.setTempDirectory(myTempDir.toFile());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(webAppContext.getAttribute(ServletContext.TEMPDIR), is(tempDirectory));
|
||||
assertThat(tempDirectory.toPath(), is(myTempDir));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
|
||||
//Test that if jetty is creating a tmp dir for the webapp, it is different on
|
||||
//restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
webAppContext.stop();
|
||||
assertNull(webAppContext.getTempDirectory());
|
||||
webAppContext.start();
|
||||
assertThat(tempDirectory.toPath(), not(PathMatchers.isSame(webAppContext.getTempDirectory().toPath())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameTempDir(WorkDir workDir) throws Exception
|
||||
{
|
||||
// Create war on the fly
|
||||
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
|
||||
//Test that if we explicitly configure the temp dir, it is the same after restart
|
||||
_server = new Server();
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
Path configuredTmpDir = workDir.getPath().resolve("tmp");
|
||||
webAppContext.setTempDirectory(configuredTmpDir.toFile());
|
||||
webAppContext.setWar(testWebappDir.toFile().getAbsolutePath());
|
||||
_server.setHandler(webAppContext);
|
||||
_server.start();
|
||||
File tempDirectory = webAppContext.getTempDirectory();
|
||||
assertThat(tempDirectory.toPath(), PathMatchers.isSame(configuredTmpDir));
|
||||
webAppContext.stop();
|
||||
assertNotNull(webAppContext.getTempDirectory());
|
||||
webAppContext.start();
|
||||
assertThat(tempDirectory.toPath(), PathMatchers.isSame(webAppContext.getTempDirectory().toPath()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee9.webapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
@ -28,6 +29,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
|
@ -45,9 +47,12 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -243,6 +248,30 @@ public class WebInfConfigurationTest
|
|||
assertTrue(Files.exists(unpackedWebInfDir.resolve("WEB-INF").resolve("lib").resolve("alpha.jar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveTempDirectory(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testPath = MavenPaths.targetTestDir("testSimple");
|
||||
FS.ensureDirExists(testPath);
|
||||
FS.ensureEmpty(testPath);
|
||||
|
||||
_server = new Server();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setContextPath("/");
|
||||
Path warPath = createWar(testPath, "test.war");
|
||||
context.setExtractWAR(true);
|
||||
context.setWar(warPath.toUri().toURL().toString());
|
||||
_server.setHandler(context);
|
||||
_server.start();
|
||||
File tmpDir = context.getTempDirectory();
|
||||
assertNotNull(tmpDir);
|
||||
|
||||
Path tmpPath = tmpDir.toPath();
|
||||
Path lastName = tmpPath.getName(tmpPath.getNameCount() - 1);
|
||||
assertThat(lastName.toString(), startsWith("jetty-test_war-_-any-"));
|
||||
assertThat(context.getAttribute(ServletContext.TEMPDIR), is(tmpDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that for each of the expected jar names (stripped of any path info),
|
||||
* there is only 1 actual jar url in the context classloader
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -287,7 +287,7 @@
|
|||
<gson.version>2.11.0</gson.version>
|
||||
<guava.version>33.2.1-jre</guava.version>
|
||||
<guice.version>7.0.0</guice.version>
|
||||
<h2spec.maven.plugin.version>1.0.12</h2spec.maven.plugin.version>
|
||||
<h2spec.maven.plugin.version>1.0.13</h2spec.maven.plugin.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
<hazelcast.version>5.4.0</hazelcast.version>
|
||||
<hibernate.search.version>7.1.1.Final</hibernate.search.version>
|
||||
|
|
Loading…
Reference in New Issue